【问题标题】:C++ compile-time constant detectionC++ 编译时常量检测
【发布时间】:2011-03-19 00:12:41
【问题描述】:

在某些情况下,库源可用,它通常必须支持可变参数,但实际上这些参数通常是常量。

然后可以通过对常量参数的特殊处理来优化事物(例如,使用静态数组而不是堆分配),但是为此有必要首先确定某个事物是否是常量(或者可能定义一些宏,但是不太方便)。

所以这是一个有效的实现。

更新:也在这里:http://codepad.org/ngP7Kt1V

  1. 它真的是一个有效的 C++ 吗?
  2. 有没有办法摆脱这些宏? (is_const() 不能是函数,因为函数依赖在数组大小表达式中不起作用;它也不能是模板,因为它也不接受可变参数。)

更新:这是一个更像预期用途的更新。 如果N 不为0,编译器不会为if(N==0) 分支生成任何代码。 同样,如果我们愿意,我们可以切换到完全不同的数据结构。 当然它并不完美,但这就是我发布这个问题的原因。


 #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 );
}

【问题讨论】:

  • is_const() 仅适用于 x>=0,但技巧(使结果编译时未定义)也适用于 is_const(X) | is_const(-X),因此 is_const 仅适用于 all x: x!=INT_MIN。跨度>
  • 请注意,sizeof(int)sizeof(char) 不能保证是不同的(现实生活中的处理器是相同的),所以你应该使用类似 char[2] 的东西。 (另一方面,我看到硬编码的常量,所以我认为可移植性不是问题。)
  • 好代码,好主意(我猜原始来源是encode.ru/threads/396-C-compile-time-constant-detection?)。我已将 is_const 代码调整为更便携(sizeof char 问题,使用 INT_MAX),以处理所有可能的输入值,并创建了一个更简单的非 gcc 版本 - 请参阅 stackoverflow.com/questions/7658060/…

标签: c++ templates metaprogramming compile-time-constant


【解决方案1】:

如果您正在使用 GCC,请使用 __builtin_constant_p 告诉您某事是否是编译时间常数。该文档包括诸如

之类的示例
static const int table[] = {
  __builtin_constant_p (EXPRESSION) ? (EXPRESSION) : -1,
  /* ... */
};

【讨论】:

  • 我试过了,但它不同,与相应的 boost 模板相同 - 如果我使用该内置函数重新定义 is_const 0 和 is_const_0i 我的示例不起作用 - const_switch* 结果是错误的并且模板示例没有t 编译。除了 gcc,我还需要它与 MSC 和 IntelC 兼容。
【解决方案2】:

is_const 应该更可靠。例如在 gcc-4.4 上,如下:

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

打印:

0,1

GCC 是相当雄心勃勃的折叠常量表达式,按照标准的话,它不是整数常量表达式。 is_const 的一个可能更好的定义可能是:

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

除此之外,您的技术很棒,因为我终于可以编写一个 SUPER_ASSERT 宏,如果是编译时的断言表达式,则在编译期间检查该宏,否则在运行时检查:

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

稍后我会研究 const_switch_xxx() 的事情。我不知道如何实现另一种方式,解构/重建技巧很棒。

【讨论】:

  • 这实际上不是gcc违反标准的吗?我找不到任何可以解释的措辞,例如。 k-k(其中kunsigned int 类型的变量)作为整数常量表达式,因此作为空指针常量,即使它的计算结果始终为零。
  • 注意:由于您只关心检测编译时间为零,因此 SUPER_ASSERT 实现可以简单得多,没有 const_switch 和任何 gcc 兼容性问题:#define SUPER_ASSERT(X) {BOOST_STATIC_ASSERT(!is_const_0(X ));断言(X);}
【解决方案3】:

如果你可以传入一个模板参数,那么它肯定是一个 constexpr(标准的编译时表达式术语)。如果它不是通过模板参数传递的,那么它不是 constexpr。没有办法解决这个问题。

使用 alloca 手动滚动堆栈分配的可变长度数组类会容易得多。这将保证数组的堆栈分配,无论它们是否是静态的。此外,您可以获得与 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];
            }
        };

请注意,我还没有实际测试过这段代码,但它比在你的 OP 中维护那个怪物要容易得多。

【讨论】:

  • 好吧,我的代码实现了一种将(可能)变量的函数作为模板参数传递的方法。此外,这实际上并不是在堆栈上分配表,而是在编译时似乎(可能是某些)参数已知时切换到优化的库版本。
  • @Shelwien:你的代码确实有一些我没有的潜力。但是,我的代码也有很多你没有的潜力。
  • 当然,感谢您分享您的代码,但恕我直言,它与主题无关:)
  • @Shelwien:它解决了您发布的实际问题(潜在运行时决定大小的静态与动态分配)。我刚刚制作了一个不同的(更好的)实现,但对于如何实现它有不同的支持想法。
  • -1:OP 确实想要解决堆栈问题上的一些可变长度数组。他向我们展示了一种在编译时检测值是否为 constexpr 的方法,以及一种巧妙的方法来制作 constexpr 以选择依赖于另一个值的 constexpr 的 constexpr。在你投票之前,你们应该明白这意味着什么。 const_switch_uint 的值可以在任何需要常量整数值的地方使用,例如模板非类型参数、枚举定义、switch 语句案例标签...
猜你喜欢
  • 2018-11-13
  • 2014-09-03
  • 1970-01-01
  • 2014-02-10
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多