`default`构造函数在常量表达式中带有未初始化的成员
以下最小示例因未初始化数组数据成员而被 Clang 和 GCC 拒绝:
class vector3
{
public:
constexpr vector3() = default;
private:
float m_data[3];
};
constexpr auto vec = vector3{};
这产生了合理的直接错误:
<source>:4:15: error: explicitly defaulted function 'constexpr vector3::vector3()' cannot be declared 'constexpr' because the implicit declaration is not 'constexpr':
4 | constexpr vector3() = default;
| ^~~~~~~
<source>:6:11: note: defaulted default constructor does not initialize 'float vector3::m_data [3]'
6 | float m_data[3];
| ^~~~~~
<source>:4:15: error: explicitly defaulted function 'constexpr vector3::vector3()' cannot be declared 'constexpr' because the implicit declaration is not 'constexpr':
4 | constexpr vector3() = default;
| ^~~~~~~
<source>:6:11: note: defaulted default constructor does not initialize 'float vector3::m_data [3]'
6 | float m_data[3];
| ^~~~~~
Live Example
上面代码的目标是确保vector3可以通过值初始化(例如vector3{})在常量表达式中使用,这将对子元素(m_data)进行零初始化。
错误是由于使用了constexpr关键字而发生的,修复方法只是删除关键字并允许default正确推断是否可以在常量表达式中使用:
奇怪的是,这实际上现在有效- 并且仍然能够生成一个常量表达式,并m_data进行零初始化,如 GCC 的程序集中可见(类似存在于 Clang 中,但使用 XOR 指令):
vec:
.zero 12
Live Example
我的问题是:怎么可能= default产生一个(有效的)constexpr构造函数,而constexpr ... = default由于它对 无效而失败constexpr?
这个问题似乎会影响 C++20(C++11 到 C++17)之前的 C++ 版本。这是在 C++20 中改变的吗?
Live Example
回答
是的,在 C++20 中确实改变了规则,因此constexpr不再需要构造函数来初始化所有非静态成员和基类子对象。
在 C++20 之前,我们有一个有趣的情况,您的构造函数不能被声明constexpr,但该vector3类型的对象仍然可以在常量表达式中使用,因为在其第一次声明时显式默认的默认构造函数实际上并没有在 value 期间调用-initialization 除非它是非平凡的 (C++17 [dcl.init]/8.2),因此constexpr不会触发在常量表达式中调用非函数的禁令。这不是编译器错误;这只是语言中的一个怪癖。
- @Human-Compiler 默认的默认构造函数不会初始化标量成员。它们未初始化并且从中读取的是 UB(这意味着在常量表达式中执行它是不正确的)。但是,如果对象具有静态存储持续时间,那么它已经在静态初始化阶段被初始化,所以它不是 UB。