You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

为何GCC/Clang判定返回的constexpr对象非常量表达式?

C++编译器对含指针成员的constexpr对象的行为差异

测试情况

  • VC++全程编译通过
  • 未给constexpr对象添加static关键字时,GCC和Clang均报错:
    GCC报错信息:
    <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.");
          |                    ~~~~~~~~~^~~~
    
    Clang报错信息:
    <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++仍无编译问题

相关代码

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变量生成的机器码存在差异。

疑问

  1. 指针成员为何会引发这类编译问题?这属于GCC/Clang的bug吗?
  2. static constexpr和constexpr变量同为编译时常量,为何生成的代码不同?

内容的提问来源于stack exchange,提问作者Adrian

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.09 20:45:32