为什么半初始化结构的 constinit 不起作用

Pow*_*mer 14 c++ language-lawyer c++20

struct A1 { int x;     int y; };
struct A2 { int x = 1; int y = 2; };
struct A3 { int x = 1; int y; };

constinit A1 a1; // x == 0, y == 0.
constinit A2 a2; // x == 1, y == 2.
          A3 a3; // x == 1, y == 0.
constinit A3 a4; // Error: illegal initialization of 'constinit' entity with a non-constant expression

int main() {}
Run Code Online (Sandbox Code Playgroud)

有什么问题a4吗?我的意思a4.x是应该由一个常量1(与它工作的地方相同a2)和a4.y一个常量初始化0,因为它a4是一个全局静态变量(与它工作的地方相同a1)。

现在,考虑到 MSVC 和 GCC 都无法编译此代码,我认为该标准以某种方式禁止它。但我以任何方式看待a4它的初始值都是精确的、恒定的并且在编译时已知,那么为什么标准禁止我们将其声明为constinit)?

除此之外,clang++还拒绝第一次constinitialization:

error: variable does not have a constant initializer
constinit A1 a1;  // x == 0, y == 0.
Run Code Online (Sandbox Code Playgroud)

Bar*_*rry 3

我认为在这种情况下我们可能会遗漏一些措辞。

首先,初始化分为三个阶段([basic.start.static]):

  1. 常量初始化
  2. 如果不是这样,则零初始化(对于静态存储持续时间变量,如本问题中的变量)
  3. 如有必要,动态初始化

(1) 和 (2) 一起是静态初始化,(3) 是……嗯,动态的。所做的constinit是确保没有动态初始化,来自[dcl.constinit]

如果用说明符声明的变量constinit具有动态初始化([basic.start.dynamic]),则程序格式错误。

常量初始化的规则是,来自[expr.const]

变量或临时对象在以下情况下o常量初始化:

  • 它要么有一个初始化器,要么它的默认初始化导致执行一些初始化,并且
  • 当解释为常量表达式时,其初始化的完整表达式是常量表达式除了 ifo是一个对象,该完整表达式还可以为其o及其子对象调用 constexpr 构造函数,即使这些对象是非文字类类型。

话虽如此,让我们来看看这三种类型。从最简单的开始:

struct A2 { int x = 1; int y = 2; };
constinit A2 a2; // x == 1, y == 2.
Run Code Online (Sandbox Code Playgroud)

在这里,我们正在初始化所有成员,这显然是常量初始化。这里其实没什么问题。

下一个:

struct A1 { int x;     int y; };
constinit A1 a1; // x == 0, y == 0.
Run Code Online (Sandbox Code Playgroud)

在这里,我们的默认构造函数不执行任何操作,没有初始化。所以这不是常量初始化。但是发生了零初始化,然后就没有进一步的初始化了。这里当然不需要进行动态初始化,因此无需constinit进行诊断。

最后:

struct A3 { int x = 1; int y; };
constinit A3 a4; // Error: illegal initialization of 'constinit' entity with a non-constant expression
Run Code Online (Sandbox Code Playgroud)

在采用P1331之前,这种初始化a4显然不是常量初始化,因为常量初始化明确需要完全初始化的变量。但我们不再有这个要求了,规则后来被移到了a4.y实际被阅读的时间。

但我认为这种情况下的意图很大程度上a4不是常量初始化,因为它部分未初始化。这可能是一个核心问题。

鉴于它不是常量初始化,那么我们进入零初始化。但在那之后,a4仍然没有完全初始化 - 因为我们必须设置x1. 没有提供半常数半零初始化。该规则是恒定的,或者,如果不是恒定的,则为零。由于这不是(或者至少不应该是)常量初始化,并且零初始化是不够的,因此a4必须进行动态初始化。因此,constinit应该标记这种情况。gcc 和 msvc 在这里是正确的(clang 错误地拒绝a1)。