Brace Initialization for Inherited Pod

brace initialization for inherited pod

base_pod_t is an aggregate and the initialization you're performing is aggregate initialization.

From §8.5.1 [dcl.init.aggr]

1 An aggregate is an array or a class (Clause 9) with no user-provided constructors (12.1), no private or protected non-static data members (Clause 11), no base classes (Clause 10), and no virtual functions (10.3).

2 When an aggregate is initialized by an initializer list, as specified in 8.5.4, the elements of the initializer list are taken as initializers for the members of the aggregate, in increasing subscript or member order. Each member is copy-initialized from the corresponding initializer-clause. ...

However, der_pod_t is not an aggregate because it has a base class. It's a POD, and the same rules for list initialization do not apply. Now, when the compiler sees a non-empty braced-init-list it'll first search for a constructor that takes an initializer_list. If none are found it then attempts to match other constructors of the class. Since der_pod_t has no constructors that take a single int as argument, the error occurs.

initializer list with struct and inheritance

Your code would work with C++17. Since C++17 both L and Y are considered as aggregate type:

no virtual, private, or protected (since C++17) base classes

Then brace elision is allowed; i.e. the braces around nested initializer lists for subaggregate could be elided and then you can just write L l {1, 2}; and Y y {1, 2}; (instead of L l {{1, 2}}; and Y y {{1}, 2};).

If the aggregate initialization uses copy- (until C++14)list-initialization syntax (T a = {args..} or T a {args..} (since C++14)), the braces around the nested initializer lists may be elided (omitted), in which case as many initializer clauses as necessary are used to initialize every member or element of the corresponding subaggregate, and the subsequent initializer clauses are used to initialize the following members of the object.

LIVE

Before C++17 it doesn't work because list initialization is performed instead, the appropriate constructors are tried to be used and fails.

Why can I not brace initialize a struct derived from another struct?

Answer for C++ standard versions before C++17:

Your problem has to do with aggregate initialization: struct X is an aggregate while struct Y is not. Here is the standard quote about aggregates (8.5.1):

An aggregate is an array or a class (Clause 9) with no user-provided constructors (12.1), no brace-or-equal-initializers for non-static data members (9.2), no private or protected non-static data members (Clause 11), no base classes (Clause 10), and no virtual functions (10.3).

This clause specifies that if a class has a base class, then it's not an aggregate. Here, struct Y has struct X as a base class and thus cannot be an aggregate type.

Concerning the particular problem you have, take the following clause from the standard:

When an aggregate is initialized by an initializer list, as specified in 8.5.4, the elements of the initializer list are taken as initializers for the members of the aggregate, in increasing subscript or member order. Each member is copy-initialized from the corresponding initializer-clause. If the initializer-clause is an expression and a narrowing conversion (8.5.4) is required to convert the expression, the program is ill-formed.

When you do X x = {0}, aggregate initialization is used to initialize a to 0. However, when you do Y y = {0}, since struct Y is not an aggregate type, the compiler will look for an appropriate constructor. Since none of the implicitely generated constructors (default, copy and move) can do anything with a single integer, the compiler rejects your code.


Concerning this constructors lookup, the error messages from clang++ are a little bit more explicit about what the compiler is actually trying to do (online example):

Y Y = {0};
^ ~~~

main.cpp:5:8: note: candidate constructor (the implicit copy constructor) not viable: no known conversion from 'int' to 'const Y &' for 1st argument

struct Y : public X {};
^

main.cpp:5:8: note: candidate constructor (the implicit move constructor) not viable: no known conversion from 'int' to 'Y &&' for 1st argument

struct Y : public X {};
^

main.cpp:5:8: note: candidate constructor (the implicit default constructor) not viable: requires 0 arguments, but 1 was provided

Note that there is a proposal to extend aggregate initialization to support your use case, and it made it into C++17. If I read it correctly, it makes your example valid with the semantics you expect. So... you only have to wait for a C++17-compliant compiler.

Struct inheritance causing non-aggregate type error on static initialization

Once you have inheritance, then your class can no longer be an aggregate type. From this Draft C++ Standard (bold italics mine):

8.5.1 Aggregates

1 An aggregate is an array or a class (Clause 9) with no user-provided
constructors (12.1), no private or protected non-static data members
(Clause 11), no base classes (Clause 10), and no virtual functions
(10.3).

Can't initialize a struct using an initializer list if it inherits?

It works for C because it is an aggregate and therefore it is using aggregate initialization but D is not an aggregate because it has a base class. The obvious work-around as you mention is to write a constructor.

This is covered in the draft C++ standard section 8.5.1 Aggregates [dcl.init.aggr] with emphasis mine:

An aggregate is an array or a class (Clause 9) with no user-provided constructors (12.1), no private or
protected non-static data members (Clause 11), no base classes (Clause 10), and no virtual functions (10.3).

There is a proposal: Extension to aggregate initialization to remove that restriction. As chris points out this was accepted by Evolution Working Group but as far as I understand now needs to accepted by Core as well.

Braces (without constructor) initialization of a derived class

Only aggregates may be initialized this way (without defining your own constructor that is) and one of the requirements to be an aggregate is to not have any base classes. In short, you can't do that.

Brace initialization difference betweein {} and {0} in c++

The syntax that you refer to is called aggregate initialization.

If the number of initializer clauses is less than the number of members or initializer list is completely empty, the remaining members are value-initialized

Since value initialization of a POD is same as zero-initialization, there is no difference between the two syntaxes that you show.

This has been the case even prior to C++11 - the empty initializer list was not something it introduced. Since C++11, the syntax has become allowed for non-POD's as well. Aggregate initialization is now a special case of this new list initialization.

Struct initialization of derived struct with a templated base type

You can only do aggregate initialization on aggregates. An aggregate is, from [dcl.init.aggr]:

An aggregate is an array or a class (Clause 9) with no user-provided constructors (12.1), no private or
protected non-static data members (Clause 11), no base classes (Clause 10), and no virtual functions (10.3).

MoveEvent is not an aggregate. Thus, you will have to add a constructor:

template <class Derived>
struct Event {
};

struct MoveEvent: Event<MoveEvent> {
MoveEvent(int x, int y) : x(x), y(y) { }
int x, y;
};

int main() {
MoveEvent event{5, 4}; // NOW this is fine
}

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.



Related Topics



Leave a reply



Submit