常量表达式中具有未初始化成员的“默认”构造函数

`default` constructor with uninitialized member in constant expression

以下最小示例因未初始化数组数据成员而被 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];
      |           ^~~~~~

Live Example

上述代码的目标是确保 vector3 可以通过值初始化(例如 vector3{})用于常量表达式,这将零初始化子元素(m_data).

错误是由于使用了constexpr关键字引起的,修复方法是简单地删除关键字并允许default正确推断是否可以在常量表达式中使用它:

class vector3
{
public:
    vector3() = default;
private:
    float m_data[3];
};

constexpr auto vec = vector3{}; // now works?

奇怪的是,这实际上 现在有效 -- 并且仍然能够生成常量表达式,其中 m_data 被零初始化,如程序集中可见GCC(类似的存在于 Clang 中,但使用 XOR 指令):

vec:
        .zero   12

Live Example

我的问题是: = default 怎么可能生成一个(有效的)constexpr 构造函数,而 constexpr ... = default 由于以下原因而失败它不适用于 constexpr?


这个问题似乎会影响 C++20 之前的 C++ 版本(C++11 到 C++17)。这在 C++20 中有改变吗?

Live Example

是的,在 C++20 中确实更改了规则,因此不再需要 constexpr 构造函数来初始化所有非静态成员和基础 class 子对象。

在 C++20 之前,我们有一个有趣的情况,即您的构造函数无法声明 constexpr,但 vector3 类型的对象仍然可以在常量表达式中使用,因为默认在其第一个声明中显式默认的构造函数在值初始化期间实际上并未 调用 除非它是非平凡的 (C++17 [dcl.init]/8.2) 并且因此禁止在常量表达式中调用非 constexpr 函数不会被触发。这不是编译器错误;这只是语言中的一个怪癖。