Use of earlier member in designated initializer

470 views Asked by At

Consider the following code:

struct Foo{
    std::string s1;
    std::string s2;
};

int main(){
    Foo f{.s1 = "s1", .s2 = f.s1 + "s2"};
    std::cout << "s1='" << f.s1 << "', s2='" << f.s2 << "'" << std::endl;
}

Especially note that the initialization of s2 accesses the first member of f: .s2 = f.s1 + "s2". The latest clang, gcc and MSVC are happy with the code and give the naively expected result (they print "s1='s1', s2='s1s2'"). See live on godbolt.

Question: Is this legal? In other words, does the standard guarantee that f.s1 gets initialized before the designated initializer .s2 is evaluated?

Related: There is a similar question asking about whether .s2 = .s1 + "s2" is legal, which clearly isn't, because it does not compile. Also, P0328 (as per this answer) might be relevant, but I can't see my question being answered there.

2

There are 2 answers

0
Fareanor On BEST ANSWER

In C++, the order is important. Member variables are initialized in order of declaration.

Initializer lists, designated or not, follow this rule.

In your case, s1 is declared before s2 in Foo. Consequently, it will be initialized first.

If you had done the opposite (i.e. define s1 based on s2) it would have been ill-formed Undefined Behaviour since you would have used a yet uninitialized member.


From 9.4.5 List-initialization (§3.1):

If the braced-init-list contains a designated-initializer-list and T is not a reference type, T shall be an aggregate class. The ordered identifiers in the designators of the designated-initializer-list shall form a subsequence of the ordered identifiers in the direct non-static data members of T. Aggregate initialization is performed ([dcl.init.aggr]).

Then from 9.4.2 Aggregates (§7):

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.

0
Lundin On

Simplified answer:

It is fine, as long as you follow the left-to-right order.

You may think of initializer lists as following the same rules as operator precedence and operator evaluation. In this case the , in the initializer list is analogous to the comma operator which has explicit left-to-right evaluation.

Similarly, initialization with = is for the most part analogous to the assignment operator. The same conversions happen. (Unless we create specialized uses with constructors or overloaded assignment operators.)

Language-lawyer answer:

This is well-defined, items in the initializer list are initialized/evaluated/sequenced in a left-to-right order.

A struct/class is an "aggregate" and C++20 9.4.1 says that:

When an aggregate is initialized by an initializer list as specified in 9.4.4, the elements of the initializer list are taken as initializers for the elements of the aggregate.

Poking deeper into C++20 9.4.4 §4:

Within the initializer-list of a braced-init-list, the initializer-clauses, including any that result from pack expansions (13.7.3), are evaluated in the order in which they appear. That is, every value computation and side effect associated with a given initializer-clause is sequenced before every value computation and side effect associated with any initializer-clause that follows it in the comma-separated list of the initializer-list.