我可以在聚合结构初始化中合法地重用字段吗?

Can I legally reuse fields in aggregate struct initialization?

下面的程序使用 gcc 编译和运行时没有警告:

#include <iostream>
struct A { int a, b; };
int main() {
  A x = {.a = 42, .b = x.a}; // <-- b is initialized from x.a
  std::cout << x.a << ' ' << x.b << std::endl;
}

42 42

x 本质上是在其初始化期间使用的。这对于 .a 由大表达式初始化的情况非常方便。

这是 C++ 中的合法表达式吗,我能保证总是得到正确答案吗?

[dcl.init.aggr]

The initializations of the elements of the aggregate are evaluated in the element order. That is, all value computations and side effects associated with a given element are sequenced before those of any element that follows it in order.

因此,x.ax.b的初始化之前已经被初始化了。到目前为止,还不错。

[basic.life]

The lifetime of an object or reference is a runtime property of the object or reference. A variable is said to have vacuous initialization if it is default-initialized and, if it is of class type or a (possibly multi-dimensional) array thereof, that class type has a trivial default constructor. The lifetime of an object of type T begins when:

  • storage with the proper alignment and size for type T is obtained, and
  • its initialization (if any) is complete (including vacuous initialization) ([dcl.init]),

x.a 的生命周期已经开始,尽管 x 的生命周期尚未开始。


Similarly, before the lifetime of an object has started ... any glvalue that refers to the original object may be used but only in limited ways. For an object under construction or destruction, see [class.cdtor]. Otherwise, such a glvalue refers to allocated storage ([basic.stc.dynamic.allocation]), and using the properties of the glvalue that do not depend on its value is well-defined. The program has undefined behavior if:

  • the glvalue is used to access the object, or
  • ...

“访问”定义为:

[defns.access]

⟨execution-time action⟩ read or modify the value of an object

Note 1: Only objects of scalar type can be accessed. Reads of scalar objects are described in [conv.lval] and modifications of scalar objects are describred in [expr.ass], [expr.post.incr], and [expr.pre.incr]. Attempts to read or modify an object of class type typically invoke a constructor or assignment operator; such invocations do not themselves constitute accesses, although they may involve accesses of scalar subobjects. — end note]

根据此 注释,访问 x.a 不是对 x 命名的 class 对象的访问。因此,初始化 x.a 应该就足够了,并且示例定义明确并且可以。

小问题:注释不规范


编辑:我删除了适用于对象“under/during 构造”的规则的引号,并假设这些引号不适用于正在初始化的聚合。


P.S。也许为了清楚起见,甚至只是为了不让代码的读者担心合法性,考虑使用一个中间变量:

int temp = 42;
A x = {.a = temp, .b = temp};

P.P.S 指定初始化器在 C++20 中首次引入标准 C++。它们不在 C+17 中。