9

There're cases when a library source is available, and it has to support variable parameters in general, but in practice these parameters are commonly constants.

Then it may be possible to optimize things by special handling of constant parameters (eg. use static arrays instead of heap allocation), but for that its necessary to determine whether something is a constant first (or maybe define some macros, but its less convenient).

So here's a working implementation.

Update: also here: http://codepad.org/ngP7Kt1V

  1. Is it really a valid C++ ?
  2. Is there a way to get rid of these macros? (is_const() can't be a function because the function dependence won't work in array size expression; also it can't be a template because that won't accept a variable parameter either. )

Update: Here's an update with something more like intended usage. The compiler won't generate any code for the if(N==0) branch if N is not 0. Same way we can switch to completely different data structures if we want. Sure its not perfect, but that's why I posted this question.


 #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 );
}
bpeterson76
  • 12,918
  • 5
  • 49
  • 82
Shelwien
  • 2,160
  • 15
  • 17
  • `is_const()` works for x>=0 only, however the trick (make a result compile-time undefined) works with `is_const(X) | is_const(-X)` too, thus having is_const working only for `all x: x!=INT_MIN`. – Nordic Mainframe Jul 21 '10 at 14:10
  • Note that `sizeof(int)` and `sizeof(char)` are not guaranteed to be different (and there are real life processors where they are the same), so you should use something like `char[2]`. (On the other hand, I see hardcoded constants so I suppose portability is not a concern.) – ymett Aug 04 '11 at 10:12
  • Great code, great idea (I guess the original source is http://encode.ru/threads/396-C-compile-time-constant-detection?). I have adapted the is_const code to be a bit more portable (sizeof char issues, INT_MAX used), to handle all possible input values, and created a simpler non-gcc version - see 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 Oct 05 '11 at 21:13

3 Answers3

2

If you're working with GCC, use __builtin_constant_p to tell you whether something is a compile time constant. The documentation includes examples like

static const int table[] = {
  __builtin_constant_p (EXPRESSION) ? (EXPRESSION) : -1,
  /* ... */
};
Kirk Kelsey
  • 4,259
  • 1
  • 23
  • 26
  • I tried that, but its different, same as corresponding boost template - my example doesn't work if I redefine is_const 0 and is_const_0i using that builtin - const_switch* results are wrong and the template example doesn't compile at all. Also I need it to be compatible with MSC and IntelC anyway, beside gcc. – Shelwien Aug 04 '10 at 23:14
1

is_const should be more reliable. On gcc-4.4 for example, the following:

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

prints:

0,1

GCC is quite ambitious folding constant expressions which are not integral constant expressions by the words of the standard. A potentially better definition of is_const could be:

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

Aside from that, your technique is awesome, because I can finally write a SUPER_ASSERT macro which is checked during compilation if the assertion expression if compile-time and during runtime otherwise:

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

I'll look into that const_switch_xxx() thing later. I have no idea how to implement another way, the deconstruct/reconstruct trick is brilliant.

Nordic Mainframe
  • 28,058
  • 10
  • 66
  • 83
  • 1
    Isn't this actually a violation of the standard by gcc? I can't find any wording that would permit it to interpret eg. `k-k` (where `k` is a variable of type `unsigned int`) as an integral constant-expression and hence as a null-pointer constant, even though it always evaluates to zero. – jpalecek Aug 03 '10 at 22:48
  • Note: as you are concerned about detecting compile time zero only, SUPER_ASSERT implementation can be a lot simpler, without const_switch and any gcc compatibility issues: #define SUPER_ASSERT(X) {BOOST_STATIC_ASSERT(!is_const_0(X));assert(X);} – Suma Oct 13 '11 at 10:46
0

If you can pass in a template parameter then it is guaranteed to be a constexpr (the Standard's term for compile-time expressions). If it's not passed by template parameter, then it's not a constexpr. There is no way around this.

What would be much easier is to hand-roll a stack allocated variable length array class using alloca. This will guarantee stack allocation for arrays, regardless of whether or not they're static or not. In addition, you can get much of the same iteration functionality of a 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];
            }
        };

Note that I haven't actually tested this code, but it would be MUCH easier to grab, use, and maintain than to maintain that monstrosity in your OP.

Puppy
  • 144,682
  • 38
  • 256
  • 465
  • Well, my code implements a way to pass a function of (possible) variable as a template parameter. Also this is not really about allocating tables on stack, but about switching to an optimized library version when it appears that (maybe some of) parameters are known at compile time. – Shelwien Jul 21 '10 at 15:33
  • @Shelwien: It's true that your code has some potential that mine doesn't. However, my code also has a lot of potential that yours doesn't. – Puppy Jul 21 '10 at 16:00
  • Sure, thanks for sharing your code, but imho its not really related to the topic :) – Shelwien Jul 21 '10 at 16:25
  • @Shelwien: It solves the actual problem that you posted (static vs dynamic allocation of a potentially runtime determinant size). I just produced a different (much better) implementation with a different backing idea as to how to accomplish it. – Puppy Jul 21 '10 at 16:36
  • 1
    -1: The OP does **not** want to solve some variable length arrays on the stack problem. He showed us a method to detect during compile time, if a value is a constexpr **and a ingenious way to craft a constexpr to select a constexpr depeding on constexpr'ness of another value**. You people should understand what that means, before you upvote anything. The value of const_switch_uint can be used anywhere, where a constant integral value is required, for example template non-type parameters, enum definitions, switch statement case labels... – Nordic Mainframe Jul 21 '10 at 21:13
  • @Luther: No, you should learn to read. He says himself that you can't perform this trick with function parameters, thus solving exactly nothing. The statement in my OP is entirely true- constexpr values to function parameters do not produce a constexpr argument. The OP will have to separately provide both template and runtime argument versions of any library that wishes to support this. If he wants to use a user-provided int to instantiate templates, he is going to have to either start including user-defined headers, or taking them by template. His macros do not solve that problem. – Puppy Jul 21 '10 at 21:33
  • @Luther Blissett: Agreed, though the magic in the implementation is too evil for me :) trying to find simpler way, since it is proved that it's possible – Alsk Jul 21 '10 at 21:38
  • @DeadMG: or any library function would have the same int argument in two forms - the template argument form and usual function argument and every call would be wrapped in macro as it is already illustrated by Shelwien – Alsk Jul 21 '10 at 21:45