Designated Initializers in C++20

Designated initializers in C++20

According to the C++ 20 Standard (9.3.1 Aggregates. p. #3)

(3.1) — If the initializer list is a designated-initializer-list, the
aggregate shall be of class type, the identifier in each designator
shall name a direct non-static data member of the class
, and the
explicitly initialized elements of the aggregate are the elements that
are, or contain, those members.

So you may not use the designated initializer list to initialize data members of base classes.

Use instead the usual list initialization like

Employee e1{ "John", "Wick", 40, 50000 };

or

Employee e1{ { "John", "Wick", 40 }, 50000 };

or as @Jarod42 pointed in a comment you can write

Employee e1{ { .name{"John"}, .surname{"Wick"}, .age{40} }, 50000 };

In this case the direct base class is initialized by a designated initializer list while the class Employe in whole is initialised by a non-designated initializer list.

How to use designated initialization of derived aggregates in C++?

Is there a way to use designated initializers here and calm down GCC at the same time to eliminate the warning?

No. GCC's warning is correct: you are missing an initializer for the base class, A. Now, in this case you happen to want to initialize from = {} anyway (which is what happens when you don't provide an initializer) and that's the only thing you can initialize it from, so maybe in this specific case it's a bit silly to warn (but in the general case where the base class actually has members, it is totally reasonable). Also the warning could be more useful and actually tell you which member you're missing an initializer for...

Unfortunately, there is no way to actually name the base class in a designated-initializer-list, which means there's no way to actually initialize B using designated initializers.

There is a proposal to fix this (P2287), the intent of which would be to allow B{.A={}, .x=1}, but that's still a work in progress.

C++20: Force usage of designated initializers to emulate named function arguments

The best way is just code review. Tell people to use designated initializers. Designated initializers are awesome, people like using them. This is really more of a social problem than a technical one.

If you really want to give a nudge, you could always stick in some truly awful data members first, like so:

class Args {
struct Key { explicit Key() = default; };
struct StopIt { StopIt(Key) { } };

public:
StopIt asdfjkhasdkljfhasdf = Key();

int a;
int b;
};

Args is still an aggregate, we just have this extra leading data member spelled with whatever came out when I just banged on my keyboard. It has a default member initializer - however that initializer is private to Args and only Args knows how to construct it.

So the user cannot correctly provide an initializer for that particular member, they have to rely on the default member initializer to initialize it correctly. And since it goes first, they have to rely on designated initialization in order to be able to initialize any of the other members.

But also... this is kind of a silly thing to write? Just tell people to use designated initialization.


Technically, they could write f({Args().asdfjkhasdkljfhasdf, 1, 2}) in this case, but this seems like we're going for absurdity or spite at this point.

Nested designated initializers

Yes, this is supported, cppreference - aggregate initialization states:

If the initializer clause is a nested braced-init-list (which is not an expression), the corresponding array element/class member/public base (since C++17) is list-initialized from that clause: aggregate initialization is recursive.

List initialization is aggregate initialization for aggregates.

Note that truly nested C designated initializers, i.e doing outer = {.inner.name = ""};, are not supported in C++. The required workaround is exactly what you have wrote.

Aggregate / designated initialization of c++ struct: Refer directly to another field

Is it possible to refer directly to s.a as just .a, or similar?

No, .a in this context is a designator, and it cannot be referred to in the brace-or-equal-initializer used to initialize a given data member by means of its matching designator.

struct Bar { int a; };

struct Foo {
int a;
Bar b;
};

int main() {
Foo f = {
// ^ start of braced-init-list
.a
// ^^ designator (for data member of Foo)
= 1,
// ^^^ initializer-clause
.b{.a = 2}
// ^^^^^^^^ braced-init-list
// referring to a designator of a
// data member of Bar
};
}

Details

Designated initializers, a new C++20 feature introduced by P0329R4, are part of the grammar for braced-init-lists:

braced-init-list:
{ initializer-list ,opt }
{ designated-initializer-list ,opt }
{ }

where the grammar of designated-initializer-list is:

designated-initializer-list:
designated-initializer-clause
designated-initializer-list , designated-initializer-clause

and, where the grammar for the individual designated-initializer-clause:s is:

designated-initializer-clause:
designator brace-or-equal-initializer

and where finally, a designator is:

designator:
. identifier

Now, a brace-or-equal-initializer does not allow, in any way, to refer to another designator of the same object. Note here the difference here between designators and other identifiers, e.g. referring to data members of the struct as part of the initializer-clause or nested braced-init-list:s of the brace-or-equal-initializer.

Designated initializer different behavior before and after c++20

Why does it compile successfully before c++20 ...

The program is ill-formed prior to C++20.

Designited initialisers did not exist in the language prior to C++20. It compiles because of a language extension.

What changed in c++20 that made it not compile anymore?

The program is still ill-formed in C++20.

Designated initialisers are introduced to the language in C++20, and it appears that the rules are slightly different from what the language extension does. The related rules are (from latest draft):

[dcl.init.list] List-initialization of an object or reference of type T is defined as follows:

  • If the braced-init-list contains a designated-initializer-list, T shall be an aggregate class. ...

  • ...

[dcl.init.aggr] An aggregate is an array or a class ([class]) with

  • no user-declared or inherited constructors ([class.ctor]),

  • ...

The behavioural difference of the language extension prior to C++20 may be related to the change in definition of what is an aggregate, as explained by NathanOliver

C++: Cannot use designated initializers on extended structs/classes

i is data member of S, but not S2.

You can add another braces referring to the base subobject, e.g.

return S2 { {.i = 1234} };

Or you can just take advantage of brace elision:

return S2 { 1234 };

Why do designated initializers zero-initialize the data members?

b.y will be initialized from an empty initializer list, as the effect, zero-initialized to 0.

For a non-union aggregate, elements for which a designated initializer is not provided are initialized the same as described above for when the number of initializer clauses is less than the number of members (default member initializers where provided, empty list-initialization otherwise):

struct A {
string str;
int n = 42;
int m = -1;
};
A{.m=21} // Initializes str with {}, which calls the default constructor
// then initializes n with = 42
// then initializes m with = 21

From the standard, [dcl.init.aggr]/5:

For a non-union aggregate, each element that is not an explicitly
initialized element is initialized as follows:

  • (5.1) If the element has a default member initializer ([class.mem]), the element is initialized from that initializer.

  • (5.2) Otherwise, if the element is not a reference, the element is copy-initialized from an empty initializer list ([dcl.init.list]).

  • (5.3) Otherwise, the program is ill-formed.



Related Topics



Leave a reply



Submit