2010-07-21 4 views
9

Ci sono casi in cui è disponibile un'origine della libreria e deve supportare in generale i parametri variabili, ma in pratica questi parametri sono comunemente costanti.Rilevamento costante in fase di compilazione C++

Quindi può essere possibile ottimizzare le cose mediante la gestione speciale di parametri costanti (ad esempio utilizzare array statici anziché allocazione heap), ma per questo è necessario determinare se qualcosa è una costante prima (o forse definire alcune macro, ma è meno conveniente).

Quindi ecco un'implementazione funzionante.

Aggiornamento: anche qui: http://codepad.org/ngP7Kt1V

  1. E 'davvero una valida C++?
  2. C'è un modo per sbarazzarsi di queste macro? (Is_const() non può essere una funzione perché la dipendenza funzione non funzionerà nell'espressione dimensione dell'array;. Inoltre non può essere un modello perché non accetterà un parametro variabile o)

Aggiornamento: Ecco un aggiornamento con qualcosa di più simile all'utilizzo previsto. Il compilatore non genererà alcun codice per il ramo if(N==0) se N non è 0. Lo stesso modo in cui possiamo passare a strutture di dati completamente diverse se vogliamo. Certo, non è perfetto, ma è per questo che ho postato questa domanda.


#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()' funziona per x> = 0 solo, ma il trucco (fare un risultato a tempo di compilazione non definito) lavora con 'is_const (X) | is_const (-X) 'anche, avendo quindi is_const che lavora solo per' all x: x! = INT_MIN'. –

+0

Nota che 'sizeof (int)' e 'sizeof (char)' non sono garantiti per essere diversi (e ci sono processori di vita reale dove sono gli stessi), quindi dovresti usare qualcosa come 'char [2]'. (D'altra parte, vedo costanti hardcoded quindi suppongo che la portabilità non sia un problema.) – ymett

+0

Grande codice, ottima idea (suppongo che la fonte originale sia http://encode.ru/threads/396-C-compile-time Constant-rilevamento?). Ho adattato il codice is_const ad essere un po 'più portatili (problemi sizeof char, INT_MAX utilizzato), per gestire tutti i possibili valori di input, e ha creato una versione più semplice non-gcc - vedi 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

risposta

0

Se è possibile passare in un parametro di modello, allora è garantito per essere un constexpr (termine del standard per le espressioni in fase di compilazione). Se non viene passato dal parametro template, non è un constexpr. Non c'è modo di aggirarlo.

Ciò che sarebbe molto più semplice è il rollare a mano una classe di array a lunghezza variabile allocata allo stack usando alloca. Ciò garantirà l'allocazione dello stack per gli array, indipendentemente dal fatto che siano statici o meno. Inoltre, è possibile ottenere gran parte della stessa funzionalità di iterazione di un vettore/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]; 
      } 
     }; 

Nota che non ho effettivamente testato questo codice, ma sarebbe molto più facile da afferrare, utilizzare e mantenere di mantenere quella mostruosità nel vostro OP.

+0

Bene, il mio codice implementa un modo per passare una funzione di (possibile) variabile come parametro template. Inoltre, non si tratta di allocare le tabelle nello stack, ma di passare a una versione di libreria ottimizzata quando sembra che (forse alcuni dei) parametri siano noti al momento della compilazione. – Shelwien

+0

@Shelwien: È vero che il tuo codice ha un potenziale che il mio non possiede. Tuttavia, il mio codice ha anche un sacco di potenziale che il tuo non ha. – Puppy

+0

Certo, grazie per aver condiviso il tuo codice, ma non è proprio relativo all'argomento :) – Shelwien

1

is_const dovrebbe essere più affidabile. Su gcc-4.4 per esempio, i seguenti:

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

stampe:

0,1 

GCC è abbastanza ambizioso espressioni ripiegamento costanti che non sono integrali espressioni costanti per le parole della norma. Un potenzialmente migliore definizione del is_const potrebbe essere:

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

A parte questo, la vostra tecnica è impressionante, perché posso finalmente scrivere una macro SUPER_ASSERT che viene controllata durante la compilazione se l'espressione asserzione se in fase di compilazione e durante il runtime altrimenti :

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

Guarderò quella cosa const_switch_xxx() più avanti.Non ho idea di come implementare un altro modo, il trucco decostruire/ricostruire è brillante.

+1

non è costui in realtà una violazione dello standard da gcc? Non riesco a trovare alcuna formulazione che permetta di interpretare ad esempio. 'K-k' (dove' k' è una variabile di tipo 'int' senza segno) come espressione-costante integrale e quindi come una costante null-pointer, anche se restituisce sempre zero. – jpalecek

+0

Nota: poiché sei preoccupato di rilevare solo il tempo di compilazione zero, l'implementazione di SUPER_ASSERT può essere molto più semplice, senza const_switch e qualsiasi problema di compatibilità di gcc: #define SUPER_ASSERT (X) {BOOST_STATIC_ASSERT (! Is_const_0 (X)); assert (X) ;} – Suma

1

Se si sta lavorando con GCC, utilizzare __builtin_constant_p per indicare se qualcosa è una costante di tempo di compilazione. La documentazione include esempi come

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

L'ho provato, ma è diverso, come il modello boost corrispondente - il mio esempio non funziona se ridefinisco is_const 0 e is_const_0i usando quel builtin - const_switch * i risultati sono sbagliati e l'esempio del template non si compila affatto. Inoltre ho bisogno che sia compatibile con MSC e IntelC comunque, oltre a gcc. – Shelwien