Can I legally reuse fields in aggregate struct initialization?

Viewed 118

The program below compiles and runs without warnings with 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 is essentially used during its initialization. This is very handy for cases where .a is initialized by a large expression.

Is this a legal expression in C++, and am I guaranteed to always get the right answer?

1 Answers

[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.

Thus, x.a has been initialised before initialisation of x.b. So far, so good.

[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]),

The lifetime of x.a has begun although the lifetime of x has not.


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
  • ...

"Access" is defined:

[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]

According to this note, accessing x.a is not an access of the class object named by x. Thus, x.a being initialised should be sufficient and the example is well-defined and OK.

Minor problem: Notes are not normative.


Edit: I removed quotes to rules that apply to objects "under/during construction" with the assumption that those do not apply to aggregates being initialised.


P.S. Perhaps for clarity, or even just to not make readers of the code to be concerned about the legality, consider using an intermediate variable:

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

P.P.S Designated initialisers were first introduced to standard C++ in C++20. They are not in C+17.

Related