It matters in which order sub-objects are initialized - if you have a class with the members A and B, and B takes pointer or reference to A in its constructor and does something with A, A better be already initialized. Sub-objects are initialized in the order of their declaration and having different order of designed initializers would be confusing. In fact, exactly that problem we have in C++ with the list of initializers of base classes and data members in constructors.
struct A { A(A*);};
struct B {
A a1;
A a2;
};
void f()
{
B b{.a1 = A(nullptr), .a2 = A(&b.a1) };
}
[0] https://godbolt.org/z/cGaxzh17T struct A {
int x;
A(A *a) { if (a) a->x = 42;}
};
struct B {
A a1;
A a2;
};
void f()
{
B b{.a1{nullptr}, .a2{&b.a1} };
}
This code is valid.Now if I change the struct definition to
struct B {
A a2;
A a1;
};
it will become UB. Luckily it won't compile because of the difference between the order of declaration and the order of designated initializers.The alternative way is to always initialize the sub-objects in the order of designated initializers (what do we do if not all initializers are provided?), but this would mean that the order of constructor calls wouldn't match the (reversed) order of destructor calls. Or we would need to select the destructor dynamically based on the way the object was initialized.
Upd:
There is an interesting sentence in [class.cdtor] but I don't think it applies here because B has no constructors:
"For an object with a non-trivial constructor, referring to any non-static member or base class of the object before the constructor begins execution results in undefined behavior."[1]
struct A { A(A*); };
A* f(struct B *b);
struct B {
A a1;
A a2;
B(): a1(f(this)), a2(f(this)) {}
};
//in a different translation unit
A* f(B *b)
{
return &b->a1; //or a2, we don't know
}
[0] https://godbolt.org/z/xMb64ssYKFurthermore your code possibly contains undefined behavior depending on the behavior of the constructor of A.
>your code possibly contains undefined behavior
Only if f() returns a pointer to a2 (which is my point). Or did you imply that in the case when f() returns a pointer to a1 and it gets passed to the constructor of a1, provenance matters?
"During the construction of an object, if the value of the object or any of its subobjects is accessed through a glvalue that is not obtained, directly or indirectly, from the constructor's this pointer, the value of the object or subobject thus obtained is unspecified." [0]
Reading an unspecified value isn't UB, that's good, but I don't understand why the standard says 'unspecified' because it clearly can be indeterminate if a sub-object hasn't been initialized yet.
struct A {
B one;
C two;
};
struct B {
B() {
cout << "init B" << endl;
}
}
struct C {
C() {
cout << "init C" << endl;
}
}
Mixing up the order is confusing: A{.two=B(),.one=A()}
since `two` is initialized after `one` despite coming before (the comma operator <expr a>, <expr b> usually means `expr a` happens before `expr b`.This case is a little contrived, but run the evolution forward: you can have members that depend on each other, or have complex initialization logic of their own. There, debugging the specific order of events is important.
{ val x$1 = B() val x$2 = A() A(.one = x$2, .two = x$1) }
This maintains left-to-right evaluation order while allowing you to pass arguments in any order.
There is probably some dark and forbidden reason why C++ can't do that.
ETA: That's basically what the post does.
The reality is C++ is a ridiculously complex and legacy-ridden language, with a difficult goal to preserve backwards compatibility. I haven't read the history on the keyword args proposals but I'm guessing they were declined due to a deluge of silly edge case interactions with C++ semantics that became too hard to work around. Like how struct designated initialisers have to be in order of member declaration due to object lifetime rules or something like that.
I would recommend trying to not be outraged at the state of C++ these days. It's time to stop hoping that C++ gets the nice features we need in any sort of reasonable manner. The reality of the language is not compatible with much niceness.