Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Does designated initializer of sub-aggregate require curly braces?

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?

like image 428
Fedor Avatar asked Aug 06 '21 20:08

Fedor


People also ask

What is designated initializer in C?

A designated initializer, or designator, points out a particular element to be initialized. A designator list is a comma-separated list of one or more designators. A designator list followed by an equal sign constitutes a designation.

What is aggregate type c++?

An aggregate class gives users direct access to its members and has special initialization syntax. A class is an aggregate if. • All of its data members are public. • It does not define any constructors. • It has no in-class initializers (§ 2.6.1, p.

What is brace initialization?

If a class has non-default constructors, the order in which class members appear in the brace initializer is the order in which the corresponding parameters appear in the constructor, not the order in which the members are declared (as with class_a in the previous example).

What is a designated initializer Swift?

Designated initializers are the primary initializers for a class. A designated initializer fully initializes all properties introduced by that class and calls an appropriate superclass initializer to continue the initialization process up the superclass chain.

What happens when a union is initialized by aggregate initialization?

When a union is initialized by aggregate initialization, only its first non-static data member is initialized.

What are the characteristics of a designated initializer?

Each direct non-static data member named by the designated initializer is initialized from the corresponding brace-or-equals initializer that follows the designator. Narrowing conversions are prohibited. Designated initializer can be used to initialize a union into the state other than the first. Only one initializer may be provided for a union.

What is aggregate initialization in C++11?

Until C++11, aggregate initialization could only be used in variable definition, and could not be used in a constructor initializer list, a new-expression, or temporary object creation due to syntax restrictions.

What types of constructors are not allowed in aggregate initialization?

no user-provided, inherited, or explicit constructors (explicitly defaulted or deleted constructors are allowed) The effects of aggregate initialization are:


Video Answer


2 Answers

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:

B x{1};

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 int is not of type B or a type derived from B. 3.3 fails fails because B isn'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 B consist of B::a.

Paragraph 4 tells us how the explicitly initialized elements are initialized. 4.1 doesn't apply, as B is not a union. But also... 4.2 doesn't apply because B::a cannot be copy-initialized from 1.

That seems like it shouldn't work. Fortunately, paragraphs 15 and 16 exist:

Braces can be elided in an initializer-list as follows. If the initializer-list begins with a left brace, then the succeeding comma-separated list of initializer-clauses initializes the elements of a subaggregate; it is erroneous for there to be more initializer-clauses than elements. If, however, the initializer-list for a subaggregate does not begin with a left brace, then only enough initializer-clauses from the list are taken to initialize the elements of the subaggregate; any remaining initializer-clauses are left to initialize the next element of the aggregate of which the current subaggregate is an element.

All implicit type conversions ([conv]) are considered when initializing the element with an assignment-expression. If the assignment-expression can initialize an element, the element is initialized. Otherwise, if the element is itself a subaggregate, brace elision is assumed and the assignment-expression is considered for the initialization of the first element of the subaggregate.

That is, if the initializer cannot initialize A via copy-initialization, the rules of brace elision kick in. And A can be initialize from an initializer-list of {1}. Therefore, it is.

And this is where designated initializers have a problem. A designated-initializer-list is not an initializer-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.

like image 183
Nicol Bolas Avatar answered Oct 30 '22 19:10

Nicol Bolas


GCC is correct to reject this code: it attempts to copy-initialize z.a from 1 ([dcl.init.aggr]/4.2), which as the comments say doesn’t work. The brace elision that GCC seems to be envisioning to produce the diagnostic is, however, invalid: that just doesn’t exist for designated-initializer-lists.

like image 40
Davis Herring Avatar answered Oct 30 '22 20:10

Davis Herring