非结构类型constexpr参数的C++示例编译分歧:GCC与Clang谁合规?
Clang与GCC对非结构类型constexpr值编译的分歧分析
问题背景
以下是来自Ben Deane演讲的代码,GCC可正常编译,但Clang拒绝编译:
#define CX_VALUE(...) [] { struct { constexpr auto operator()() const noexcept { return __VA_ARGS__; } using cx_value_tag = void; } val; return val; }() template <typename T> concept cx_value = requires { typename T::cx_value_tag; }; auto func(cx_value auto x) { constexpr auto val = x(); std::cout << "Constexpr value: " << val.val() << '\n'; } class S { public: constexpr S(const int val) : val_(val){} int val() const { return val_; } private: int val_; }; int main() { constexpr S non_structural_value{420}; func(CX_VALUE(non_structural_value)); }
核心分析
Clang的报错集中在func内的constexpr auto val = x();,认为该初始化不是合法的常量表达式,但根据C++标准,这行代码完全合规,原因如下:
S是合法的字面类型
根据C++标准对**字面类型(literal type)**的定义,S满足全部条件:- 拥有constexpr析构函数(默认生成的析构函数符合constexpr要求);
- 拥有非拷贝/移动类别的constexpr构造函数;
- 所有非静态数据成员(
val_是int,属于标量字面类型)和基类均为字面类型。
x()的调用是合法的常量表达式CX_VALUE宏生成的匿名结构体的operator()是constexpr函数,它返回non_structural_value的拷贝:non_structural_value是main中合法初始化的constexpr变量;- 拷贝操作调用
S的隐式constexpr拷贝构造函数——作为类成员函数,它有权访问private的val_,因此整个返回操作属于合法的常量表达式。
constexpr变量初始化合规
constexpr auto val = x();中,val的类型是字面类型S,初始化表达式x()是合法的常量表达式,完全符合C++标准对constexpr变量的要求。
结论
GCC的编译行为符合C++标准,Clang的报错属于实现bug——它错误地认为在常量表达式中拷贝非结构类型的constexpr对象时,无法访问其private成员,但实际上拷贝构造函数作为类成员拥有访问权限,整个操作是合规的。
内容的提问来源于stack exchange,提问作者NoSenseEtAl
相关产品推荐
相关产品推荐

