为何GCC/Clang判定返回的constexpr对象非常量表达式?
C++编译器对含指针成员的constexpr对象的行为差异
测试情况
- VC++全程编译通过
- 未给constexpr对象添加
static关键字时,GCC和Clang均报错:
GCC报错信息:
Clang报错信息:<source>: 在函数'int main()'中: <source>:141:30: 错误:'ArrayStack<int, 2>{int [2]{2, 1}, (((int*)(& y.ArrayStack<int, 2>::m_stack)) + 8)}'不是常量表达式 141 | constexpr auto y { fn2() }; | ^ <source>:142:29: 错误:static_assert的条件不是常量表达式 142 | static_assert( y.back() == 1, "Should equal 1."); | ~~~~~~~~~^~~~ <source>:143:44: 错误:'ArrayStack<int, 2>{int [2]{2, 1}, (((int*)(& z.ArrayStack<int, 2>::m_stack)) + 8)}'不是常量表达式 143 | constexpr ArrayStack<int, 2> z { fn2() }; | ^ <source>:144:29: 错误:static_assert的条件不是常量表达式 144 | static_assert( z.back() == 1, "Should equal 1."); | ~~~~~~~~~^~~~<source>:141:20: 错误:constexpr变量'y'必须由常量表达式初始化 constexpr auto y { fn2() }; ^~~~~~~~~~~ <source>:141:20: 注:指向'y'子对象的指针不是常量表达式 <source>:141:20: 注:非static constexpr变量'y'的地址在每次调用外围函数时可能不同;添加'static'以赋予其常量地址 constexpr auto y { fn2() }; ^ static <source>:142:20: 错误:static_assert表达式不是整数常量表达式 static_assert( y.back() == 1, "Should equal 1."); ^~~~~~~~~~~~~ <source>:142:22: 注:'y'的初始化器不是常量表达式 static_assert( y.back() == 1, "Should equal 1."); ^ <source>:141:20: 注:在此处声明 constexpr auto y { fn2() }; ^ - 添加
static关键字后:- GCC接受constexpr对象赋值,但不允许在
static_assert中调用constexpr成员函数 - Clang编译通过
- VC++仍无编译问题
- GCC接受constexpr对象赋值,但不允许在
相关代码
template <typename Type, size_t Size> class ArrayStack { public: using iterator = Type*; using const_iterator = Type const*; using value_type = Type; using pointer = Type*; using reference = Type&; using difference_type = std::make_signed_t<size_t>; using size_type = size_t; using reverse_iterator = std::reverse_iterator<iterator>; using const_reverse_iterator = std::reverse_iterator<const_iterator>; constexpr ArrayStack() : m_stack{} , m_end{ begin() } {} // Init only used part of m_stack is only available in C++20 constexpr ArrayStack(ArrayStack const& to_copy) : m_stack{} , m_end{ begin() } { *this = to_copy; } constexpr ArrayStack& operator=(ArrayStack const& rhs) { // std::copy is constexpr in C++20 auto it{ begin() }; for (auto rhs_it{ rhs.begin() }, rhs_end_it{ rhs.end() }; rhs_it != rhs_end_it; ++it, ++rhs_it) { *it = *rhs_it; } m_end = begin() + rhs.size(); return *this; } //... constexpr iterator begin() { return m_stack; } constexpr const_iterator begin() const { return m_stack; } //... constexpr iterator end() { return m_end; } constexpr const_iterator end() const { return m_end; } //... constexpr void push_back(value_type const& value) { if (!full()) { m_end++[0] = value; } else { throw std::out_of_range("Ran out of stack space"); } } //... constexpr size_t size() const { return end() - begin(); } constexpr value_type const& back() const { if (!empty()) { return m_end[-1]; } else { throw std::out_of_range("Nothing on stack"); } } //... constexpr bool empty() const { return m_end == begin(); } constexpr bool full() const { return end() == m_stack + Size; } private: Type m_stack[Size]; iterator m_end; }; constexpr bool fn() { ArrayStack<int, 2> stack; stack.push_back(2); stack.push_back(1); return stack.back() == 1; } constexpr auto fn2() { ArrayStack<int, 2> stack; stack.push_back(2); stack.push_back(1); return stack; } int main() { static_assert( sizeof(int) == 4, "Should be 4 bytes in size"); static_assert( fn(), "Should return true."); static_assert( fn2().back() == 1, "Should equal 1."); constexpr auto y { fn2() }; static_assert( y.back() == 1, "Should equal 1."); constexpr ArrayStack<int, 2> z { fn2() }; static_assert( z.back() == 1, "Should equal 1."); return 0; }
问题分析
问题根源在于ArrayStack类中的指针成员m_end:当返回ArrayStack对象时,m_end指向的是对象内部的m_stack数组,而非常量表达式允许的地址范围。将m_end替换为末尾索引可解决编译问题,但会改变类的数据布局。此外,在-O3优化等级下,static constexpr与constexpr变量生成的机器码存在差异。
疑问
- 指针成员为何会引发这类编译问题?这属于GCC/Clang的bug吗?
static constexpr和constexpr变量同为编译时常量,为何生成的代码不同?
内容的提问来源于stack exchange,提问作者Adrian
相关产品推荐
相关产品推荐

