【问题标题】:`default` constructor with uninitialized member in constant expression`default` 构造函数在常量表达式中具有未初始化的成员
【发布时间】:2021-07-13 17:38:03
【问题描述】:

以下最小示例因未初始化数组数据成员而被 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 无效而失败? p>


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

Live Example

【问题讨论】:

  • The goal with the above code was to ensure that vector3 can be used in constant expressions via aggregate initialization 一个简单的解决方案:struct vector3 { float m_data[3]; }; 可以在常量表达式中使用并且可以聚合初始化。或者:using vector3 = std::array&lt;float, 3&gt;;
  • @eerorika 谢谢,但问题已经解释了 constexpr 关键字是这里问题的原因,并且删除它仍然允许在常量表达式中对成员进行正确的值初始化;我只是询问为什么 constexpr 关键字是问题所在。还值得一提的是,以上只是一个最小的示例,对于构造更复杂的 OO 类,这些建议不是可行的替代方案,而不仅仅是对数组进行建模。 (编辑:我刚刚意识到当我的意思是“价值”时,我使用了“聚合初始化”这个词。抱歉有任何混淆!)。

标签: c++ constexpr


【解决方案1】:

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

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

【讨论】:

  • 感谢您的及时、简洁和信息丰富的回答。只是为了澄清您对 C++20 的 constexpr 不再需要初始化的观点:这些对象是否仍将值初始化为 0?或者这是否意味着constexpr vector3() = default 与没有constexpr 的先前C++ 版本中的vector3() = default 具有不同的行为(因为您说在值初始化期间不会调用构造函数)?
  • @Human-Compiler 默认的默认构造函数不会初始化标量成员。它们未初始化,从它们读取的是 UB(这意味着在常量表达式中这样做是不正确的)。但是,如果对象具有静态存储持续时间,则它已经在静态初始化阶段进行了初始化,因此它不是 UB。
  • 啊,我明白了;所以只有在常量表达式中未初始化时才会真正影响它的使用,这在 C++20 之前是不可能的。我只是想确保您不是说从此构造函数构造的常量将是 UB,以便稍后在 C++20 运行时读取(这会很糟糕)。感谢您的澄清!
  • 对,我指的是使用带有 default 构造函数的大括号构造应该发生的值初始化(例如constexpr auto vec = vector3{};)。正如您所说,在 C++20 之前,它绕过了初始化的构造函数调用。我不确定你的意思是不是,从 C++20 开始,这不再绕过构造函数(尽管我相信你已经澄清这不会改变)。我知道微不足道的构造函数不会做任何实际工作(这不是我在这里指的)
  • @Human-Compiler 对不起,你是对的。当您对具有非用户提供的默认构造函数的vector3 进行值初始化时,该对象被零初始化并且不调用构造函数。在所有版本的 C++ 中都是如此。这避免了任何 UB 的可能性。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2011-05-02
  • 1970-01-01
  • 2021-12-26
  • 1970-01-01
  • 2015-10-07
相关资源
最近更新 更多