In the following program, aggregate struct B has the field a, which is itself an aggregate. Can C++20 designated initializer be used to set its value without surrounding curly braces?
struct A { int i; };
struct B { A a; };
int main() {
[[maybe_unused]] B x{1}; //ok everywhere
[[maybe_unused]] B y{.a = {1}}; //ok everywhere
[[maybe_unused]] B z{.a = 1}; //ok in MSVC,Clang; error in GCC
}
MSVC and Clang compilers accept this code. But GCC issues a weird error:
error: 'A' has no non-static data member named 'a'
Demo: https://gcc.godbolt.org/z/65j1sTcPG
Is it a bug in GCC, or such initialization is not permitted by the standard?
TLDR; GCC is right, everyone else is wrong because they're pretending that designated initializer-lists act like equivalent non-designated initializer-lists all the time.
To understand what is happening here (and why compilers disagree), let's look at your first example:
Since we're using braces, the rules of list initialization kick in. The list is not a designated initializer list, so 3.1 fails. 3.2 fails because
intis not of typeBor a type derived fromB. 3.3 fails fails becauseBisn't an array of characters. Finally 3.4 is followed, which takes us to aggregate initialization.[dcl.init.aggr]/3.2 tells us that the explicitly initialized elements of
Bconsist ofB::a.Paragraph 4 tells us how the explicitly initialized elements are initialized. 4.1 doesn't apply, as
Bis not aunion. But also... 4.2 doesn't apply becauseB::acannot be copy-initialized from1.That seems like it shouldn't work. Fortunately, paragraphs 15 and 16 exist:
That is, if the initializer cannot initialize
Avia copy-initialization, the rules of brace elision kick in. AndAcan be initialize from an initializer-list of{1}. Therefore, it is.And this is where designated initializers have a problem. A
designated-initializer-listis not aninitializer-list. And therefore, the brace elision paragraphs do not apply.And therefore,
B z{.a = 1};must fail.The reason the other compilers don't catch this is likely the following. They probably implement designated initializers by stripping out the designations and inserting any default member initializers/value initialization between non-consecutive elements, then applying normal aggregate initializer rules. But that's not quite the same thing, since designated initializer lists don't participate in brace elision.