2010-07-21 13 views
9

Existen casos en los que una fuente de biblioteca está disponible y tiene que soportar parámetros variables en general, pero en la práctica estos parámetros son comúnmente constantes.Detección constante en tiempo de compilación en C++

Entonces puede ser posible optimizar cosas mediante el manejo especial de parámetros constantes (por ejemplo, usar matrices estáticas en lugar de asignación de montón), pero para eso es necesario determinar si algo es una constante primero (o quizás definir algunas macros, pero es menos conveniente).

Así que aquí hay una implementación en funcionamiento.

Actualización: también aquí: http://codepad.org/ngP7Kt1V

  1. ¿Es realmente un C++ válida?
  2. ¿Hay alguna manera de deshacerse de estas macros? (Is_const() no puede ser una función porque la dependencia función no funcionará en la expresión tamaño de la matriz;. También que no puede ser una plantilla porque que no aceptará un parámetro variable, ya sea)

Actualización: Aquí hay una actualización con algo más parecido al uso previsto. El compilador no generará ningún código para la rama if(N==0) si N no es 0. De la misma manera podemos cambiar a estructuras de datos completamente diferentes si lo deseamos. Claro que no es perfecto, pero es por eso que he publicado esta pregunta.


#include <stdio.h>

struct chkconst { struct Temp { Temp(int x) {} }; static char chk2(void*) { return 0; } static int chk2(Temp ) { return 0; } }; #define is_const_0(X) (sizeof(chkconst::chk2(X))<sizeof(int)) #define is_const_0i(X) (sizeof(chkconst::chk2(X))>sizeof(char)) #define is_const(X) is_const_0((X)^((X)&0x7FFFFFFF)) #define const_bit(X1,bit) (is_const_0i((X1)&(1<<bit))<<bit) #define const_nibl(X1,bit) const_bit(X1,bit) | const_bit(X1,(bit+1)) | const_bit(X1,(bit+2)) | const_bit(X1,(bit+3)) #define const_byte(X1,bit) const_nibl(X1,bit) | const_nibl(X1,(bit+4)) #define const_word(X1,bit) const_byte(X1,bit) | const_byte(X1,(bit+8)) #define const_uint(X1) const_word(X1,0) | const_word(X1,16) #define const_switch_word(X1, X2) (is_const(X1) ? const_word(X1,0) : X2) #define const_switch_uint(X1, X2) (is_const(X1) ? const_uint(X1) : X2) const int X1 = 222; const int X2 = printf("") + 333; char Y1[ const_switch_word(X1,256) ]; char Y2[ const_switch_word(X2,256) ]; template< int N > void test(int N1) { char _buf[N>0?N:1]; char* buf = _buf; if(N==0) { buf = new char[N1]; } printf("%08X %3i %3i\n", buf, N, N1); } #define testwrap(N) test< const_switch_word(N,0) >(N) int main(void) { printf("%i %i %i\n", X1, is_const(X1), sizeof(Y1)); printf("%i %i %i\n", X2, is_const(X2), sizeof(Y2)); testwrap(X1); testwrap(X2); }
+0

'is_const()' que funciona para x> = 0 solamente, sin embargo, el truco (Agrega resultado en tiempo de compilación no definido) trabaja con 'is_const (X) | is_const (-X) 'también, teniendo así is_const trabajando solo para' all x: x! = INT_MIN'. –

+0

Tenga en cuenta que 'sizeof (int)' y 'sizeof (char)' no se garantiza que sean diferentes (y hay procesadores de la vida real donde son los mismos), por lo que debe usar algo como 'char [2]'. (Por otro lado, veo constantes codificadas, así que supongo que la portabilidad no es una preocupación). – ymett

+0

Gran código, gran idea (creo que la fuente original es http://encode.ru/threads/396-C-compile-time -constant-detection?). He adaptado el código is_const para que sea un poco más portátil (tamaño de temas Char, INT_MAX utilizado), para manejar todos los valores de entrada posibles, y he creado una versión no-gcc más simple - ver http://stackoverflow.com/questions/7658060/ can-i-use-assume-hint-to-elide-a-call-if-an-edge-condition-is-known-at-compile/7658363 # 7658363 – Suma

Respuesta

0

Si usted puede pasar en un parámetro de plantilla, entonces se garantiza que sea un constexpr (término de la Norma para las expresiones en tiempo de compilación). Si no se pasa por el parámetro de la plantilla, entonces no es un constexto. No hay forma de evitar esto.

Lo que sería mucho más fácil es rodar manualmente una pila asignada clase de matriz de longitud variable utilizando alloca. Esto garantizará la asignación de la pila para las matrices, independientemente de si son estáticas o no. Además, puede obtener gran parte de la misma funcionalidad de iteración de un vector/boost :: array.

 #define MAKE_VLA(type, identifier, size) VLA< (type) > identifier (alloca((size) * sizeof (type)), (size)); 
     template<typename T> class VLA { 
      int count; 
      T* memory; 
      VLA(const VLA& other); 
     public: 
      // Types 
      typedef T* pointer; 
      typedef T& reference; 
      typedef const T* const_pointer; 
      typedef const T& const_reference; 
      typedef T value_type; 
      typedef std::size_t size_type; 
      class iterator { 
       mutable T* ptr; 
       iterator(T* newptr) 
        : ptr(newptr) {} 
      public: 
       iterator(const iterator& ref) 
        : ptr(ref.ptr) {} 

       operator pointer() { return ptr; } 
       operator const pointer() const { return ptr; } 

       reference operator*() { return *ptr; } 
       const reference operator*() const { return *ptr; } 

       pointer operator->() { return ptr; } 
       const pointer operator->() const { return ptr; } 

       iterator& operator=(const iterator& other) const { 
        ptr = iterator.ptr; 
       } 

       bool operator==(const iterator& other) { 
        return ptr == other.ptr; 
       } 
       bool operator!=(const iterator& other) { 
        return ptr != other.ptr; 
       } 

       iterator& operator++() const { 
        ptr++; 
        return *this; 
       } 
       iterator operator++(int) const { 
        iterator retval(ptr); 
        ptr++; 
        return retval; 
       } 
       iterator& operator--() const { 
        ptr--; 
        return *this; 
       } 
       iterator operator--(int) const { 
        iterator retval(ptr); 
        ptr--; 
        return retval; 
       } 

       iterator operator+(int x) const { 
        return iterator(&ptr[x]); 
       } 
       iterator operator-(int x) const { 
        return iterator(&ptr[-x]); 
       } 
      }; 
      typedef const iterator const_iterator; 
      class reverse_iterator { 
       mutable T* ptr; 
       reverse_iterator(T* newptr) 
        : ptr(newptr) {} 
      public: 
       reverse_iterator(const reverse_iterator& ref) 
        : ptr(ref.ptr) {} 

       operator pointer() { return ptr; } 
       operator const pointer() const { return ptr; } 

       reference operator*() { return *ptr; } 
       const reference operator*() const { return *ptr; } 

       pointer operator->() { return ptr; } 
       const pointer operator->() const { return ptr; } 

       reverse_iterator& operator=(const reverse_iterator& other) const { 
        ptr = reverse_iterator.ptr; 
       } 
       bool operator==(const reverse_iterator& other) { 
        return ptr == other.ptr; 
       } 
       bool operator!=(const reverse_iterator& other) { 
        return ptr != other.ptr; 
       } 

       reverse_iterator& operator++() const { 
        ptr--; 
        return *this; 
       } 
       reverse_iterator operator++(int) const { 
        reverse_iterator retval(ptr); 
        ptr--; 
        return retval; 
       } 
       reverse_iterator& operator--() const { 
        ptr++; 
        return *this; 
       } 
       reverse_iterator operator--(int) const { 
        reverse_iterator retval(ptr); 
        ptr++; 
        return retval; 
       } 

       reverse_iterator operator+(int x) const { 
        return reverse_iterator(&ptr[-x]); 
       } 
       reverse_iterator operator-(int x) const { 
        return reverse_iterator(&ptr[x]); 
       } 
      }; 
      typedef const reverse_iterator const_reverse_iterator; 
      typedef unsigned int difference_type; 

      // Functions 
      ~VLA() { 
       for(int i = 0; i < count; i++) 
        memory[i].~T(); 
      } 
      VLA(void* stackmemory, int size) 
       : memory((T*)stackmemory), count(size) { 
        for(int i = 0; i < count; i++) 
         new (&memory[i]) T(); 
      } 

      reference at(size_type pos) { 
       return (reference)memory[pos]; 
      } 
      const_reference at(size_type pos) { 
       return (const reference)memory[pos]; 
      } 
      reference back() { 
       return (reference)memory[count - 1]; 
      } 
      const_reference back() const { 
       return (const reference)memory[count - 1]; 
      } 

      iterator begin() { 
       return iterator(memory); 
      } 
      const_iterator begin() const { 
       return iterator(memory); 
      } 

      const_iterator cbegin() const { 
       return begin(); 
      } 

      const_iterator cend() const { 
       return end(); 
      } 

      const_reverse_iterator crbegin() const { 
       return rbegin(); 
      } 

      const_reverse_iterator crend() const { 
       return rend(); 
      } 

      pointer data() { 
       return memory; 
      } 
      const_pointer data() const { 
       return memory; 
      } 

      iterator end() { 
       return iterator(&memory[count]); 
      } 
      const_iterator end() const { 
       return iterator(&memory[count]); 
      } 

      reference front() { 
       return memory[0]; 
      } 
      const_reference front() const { 
       return memory[0]; 
      } 

      reverse_iterator rbegin() { 
       return reverse_iterator(&memory[count - 1]); 
      } 
      const_reverse_iterator rbegin() const { 
       return const_reverse_iterator(&memory[count - 1]); 
      } 
      reverse_iterator rend() { 
       return reverse_iterator(memory[-1]); 
      } 
      const_reverse_iterator rend() const { 
       return reverse_iterator(memory[-1]); 
      } 

      size_type size() { 
       return count; 
      } 

      reference operator[](int index) { 
       return memory[index]; 
      } 
      const reference operator[](int index) const { 
       return memory[index]; 
      } 
     }; 

Tenga en cuenta que no he hecho probado este código, pero sería mucho más fácil de agarrar, usar y mantener que para mantener esa monstruosidad en su OP.

+0

Bueno, mi código implementa una forma de pasar una función de (posible) variable como un parámetro de plantilla. Además, esto no se trata realmente de asignar tablas en la pila, sino de cambiar a una versión de biblioteca optimizada cuando parece que (tal vez algunos) parámetros se conocen en tiempo de compilación. – Shelwien

+0

@Shelwien: Es cierto que su código tiene un potencial que el mío no tiene. Sin embargo, mi código también tiene mucho potencial que el tuyo no tiene. – Puppy

+0

Claro, gracias por compartir su código, pero no está realmente relacionado con el tema :) – Shelwien

1

is_const debe ser más confiable. En gcc-4.4 por ejemplo, los siguientes:

int k=0; 
printf("%d\n",is_const(k),is_const(k>0)); 

grabados:

0,1 

GCC es bastante ambiciosos expresiones plegado constantes que no son expresiones constantes integrales por las palabras de la norma. Un potencialmente mejor definición de is_const podría ser:

#define is_const(B)\ 
(sizeof(chkconst::chk2(0+!!(B))) != sizeof(chkconst::chk2(0+!(B)))) 

Aparte de eso, su técnica es impresionante, porque por fin puedo escribir una macro SUPER_ASSERT que se comprueba durante la compilación si la expresión afirmación si el tiempo de compilación y en tiempo de ejecución de otro modo :

#define SUPER_ASSERT(X) {BOOST_STATIC_ASSERT(const_switch_uint(X,1));assert(X);} 

Voy a analizar esa cosa const_switch_xxx() más adelante.No tengo idea de cómo implementar de otra manera, el truco deconstruir/reconstruir es brillante.

+1

¿No es esto una violación del estándar por parte de gcc? No puedo encontrar ninguna fraseología que le permita interpretar, por ejemplo. 'k-k' (donde' k' es una variable de tipo 'unsigned int') como una expresión de constante integral y, por lo tanto, como una constante de puntero nulo, aunque siempre se evalúa como cero. – jpalecek

+0

Nota: como le preocupa detectar solo el tiempo de compilación cero, la implementación de SUPER_ASSERT puede ser mucho más simple, sin const_switch y problemas de compatibilidad con gcc: #define SUPER_ASSERT (X) {BOOST_STATIC_ASSERT (! Is_const_0 (X)); assert (X) ;} – Suma

1

Si está trabajando con GCC, use __builtin_constant_p para decirle si algo es una constante de tiempo de compilación. La documentación incluye ejemplos como

static const int table[] = { 
    __builtin_constant_p (EXPRESSION) ? (EXPRESSION) : -1, 
    /* ... */ 
}; 
+0

Lo intenté, pero es diferente, igual que la plantilla de impulso correspondiente - mi ejemplo no funciona si redefino is_const 0 y is_const_0i usando ese builtin - const_switch * los resultados son incorrectos y el ejemplo de la plantilla no se compila en absoluto. También necesito que sea compatible con MSC e IntelC de todos modos, además de gcc. – Shelwien

Cuestiones relacionadas