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

Why constinit of half-initialized struct does not work

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() {}

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

现在,考虑到 MSVC 和 GCC 都无法编译此代码,我假设标准以某种方式禁止它。但无论如何我看 a4 它的初始值是准确的,常量并且在编译时已知所以为什么标准禁止我们将它声明为 constinit)?

除此之外,clang++ 还拒绝第一个 constinit 初始化:

error: variable does not have a constant initializer
constinit A1 a1;  // x == 0, y == 0.

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

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

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

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

If a variable declared with the constinit specifier has dynamic initialization ([basic.start.dynamic]), the program is ill-formed.

常量初始化的规则是,从[expr.const]:

A variable or temporary object o is constant-initialized if

  • either it has an initializer or its default-initialization results in some initialization being performed, and
  • the full-expression of its initialization is a constant expression when interpreted as a constant-expression, except that if o is an object, that full-expression may also invoke constexpr constructors for o and its subobjects even if those objects are of non-literal class types.

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

struct A2 { int x = 1; int y = 2; };
constinit A2 a2; // x == 1, y == 2.

在这里,我们正在初始化所有成员,这显然是常量初始化。这里不是一个真正的问题。

下一个:

struct A1 { int x;     int y; };
constinit A1 a1; // x == 0, y == 0.

在这里,我们的默认构造函数...什么也没做,没有初始化。所以这不是常量初始化。但是 zero-initialization 发生了,然后就没有进一步的初始化了。这里肯定没有必须进行的动态初始化,因此 constinit 无需诊断。

最后:

struct A3 { int x = 1; int y; };
constinit A3 a4; // Error: illegal initialization of 'constinit' entity with a non-constant expression

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

但我认为在这种情况下的意图是 a4 而不是 常量初始化,因为它被部分未初始化。这可能值得一个核心问题。

既然不是常量初始化,那我们就进入零初始化。但在那之后,a4 仍然没有完全初始化 - 因为我们必须将 x 设置为 1。没有提供 half-constant half-zero 初始化。规则是不变的,或者如果不是那样,则为零。由于这不是(或者至少不应该是)常量初始化,并且 zero-initialization 是不够的,因此 a4 必须进行动态初始化。因此,constinit 应该标记这种情况。 gcc 和 msvc 在这里是正确的(clang 错误地拒绝 a1)。