C++编译时常量检测

9
有时候,当一个库源可用时,它必须在一般情况下支持可变参数,但实际上这些参数通常是常量。因此,通过特殊处理常量参数(例如使用静态数组而不是堆分配)来优化事物可能是可能的,但为此需要首先确定某些内容是否为常量(或者可以定义一些宏,但这不太方便)。以下是一个可行的实现。
更新:这里也有一个链接:http://codepad.org/ngP7Kt1V 问题:
  1. 它真的是有效的C++吗?
  2. 有没有办法摆脱这些宏?(is_const()不能是函数,因为函数依赖关系在数组大小表达式中无法工作;同样,它不能是模板,因为那将不接受变量参数。)
更新:这里是一个更像预期使用方式的更新。如果N不为0,则编译器不会为if(N==0)分支生成任何代码。同样,如果我们想要,我们可以切换到完全不同的数据结构。当然,它并不完美,但这就是我发布这个问题的原因。


 #include <stdio.h>


is_const() 仅适用于 x>=0,但是技巧(使结果在编译时未定义)也适用于 is_const(X) | is_const(-X),因此只有当 all x: x!=INT_MIN 时,is_const 才能正常工作。 - Nordic Mainframe
请注意,sizeof(int)sizeof(char)不能保证不同(实际上有一些处理器它们的大小相同),因此您应该使用类似于char[2]的东西。(另一方面,我看到硬编码常量,所以我想可移植性可能不是一个问题。) - ymett
伟大的代码,伟大的想法(我猜原始来源是http://encode.ru/threads/396-C-compile-time-constant-detection?)。我已经改编了is_const代码,使其更具可移植性(解决了sizeof char问题,使用了INT_MAX),以处理所有可能的输入值,并创建了一个更简单的非gcc版本-请参见https://dev59.com/_FvUa4cB1Zd3GeqPyOyE#7658363。 - Suma
3个回答

2
如果您正在使用GCC,请使用__builtin_constant_p来判断某个值是否为编译时常量。文档中包含了以下示例:
static const int table[] = {
  __builtin_constant_p (EXPRESSION) ? (EXPRESSION) : -1,
  /* ... */
};

我尝试过了,但是它不同,与相应的boost模板相同 - 如果我使用该内置函数重新定义is_const 0和is_const_0i,我的示例将无法工作 - const_switch*结果是错误的,而且模板示例根本无法编译。此外,我需要它与MSC和IntelC兼容,除了gcc。 - Shelwien

1

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()的东西。我不知道如何另外实现,拆解/重构的技巧确实很棒。


1
这实际上不是gcc违反标准吗?我找不到任何措辞可以允许它将eg.(其中k是类型为unsigned int的变量)解释为整数常量表达式,因此作为空指针常量,即使它总是求值为零。 - jpalecek
注意:如果您只关心在编译时检测到零,那么SUPER_ASSERT的实现可以更简单,不需要const_switch和任何gcc兼容性问题:#define SUPER_ASSERT(X){BOOST_STATIC_ASSERT(!is_const_0(X));assert(X);} - Suma

0

如果您可以传递模板参数,则保证它是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];
            }
        };

请注意,我实际上还没有测试过这段代码,但是相比于维护您的原始帖子中的那个怪物,获取、使用和维护它要容易得多。

我的代码实现了一种将(可能的)变量函数作为模板参数传递的方法。这并不是关于在堆栈上分配表格的问题,而是当出现(可能的)参数在编译时已知时,切换到优化库版本的问题。 - Shelwien
@Shelwien:你的代码确实有一些我的没有的潜力。然而,我的代码也有很多你的没有的潜力。 - Puppy
@Shelwien:它解决了您发布的实际问题(静态与动态分配可能是运行时确定大小的问题)。我只是使用不同的支持思路制作了一个不同的(更好的)实现。 - Puppy
1
-1:OP并不想解决堆栈上的某些变长数组问题。他向我们展示了一种在编译时检测值是否为constexpr的方法,以及一种巧妙的constexpr制作方法,可以根据另一个值的constexpr性质选择constexpr。在您点赞之前,您应该理解这意味着什么。const_switch_uint的值可以在任何需要常量整数值的地方使用,例如模板非类型参数、枚举定义、switch语句case标签... - Nordic Mainframe
@Luther Blissett:同意,但是实现中的魔法对我来说太邪恶了 :) 尝试寻找更简单的方法,因为已经证明这是可能的。 - Alsk
显示剩余3条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接