I ran into a weird problem when trying to move to C++17. The problem is that something (and I'm not sure what) changed in C++17 that made list-initialization work differently in the case of a default constructor. I tried to search https://en.cppreference.com/w/cpp/language/list_initialization for more info, but I didn't find anything that looks relevant.
Does someone know the reason the code below compiles in C++14 but not in C++17 when calling B{}
instead of B()
? (I tried it in both gcc 8.2 and 7.3 and icc 19)
struct A{ protected: A() {} }; struct B : public A {}; B f(){ return B(); //compilation OK //return B{}; //compilation error }
Initializer List is used in initializing the data members of a class. The list of members to be initialized is indicated with constructor as a comma-separated list followed by a colon. Following is an example that uses the initializer list to initialize x and y of Point class.
In C++11 and above, we can use the initializer lists '{...}' to initialize a list. This won't work in C++98 as standard permits list to be initialized by the constructor, not by '{...}' .
The compiler chooses a move constructor when the object is initialized by another object of the same type, if the other object is about to be destroyed and no longer needs its resources.
The default constructor does not initialize members of your class.
In C++14, the definition of aggregate was:
An aggregate is an array or a class (Clause [class]) with no user-provided constructors ([class.ctor]), no private or protected non-static data members (Clause [class.access]), no base classes (Clause [class.derived]), and no virtual functions ([class.virtual]).
Hence, B
is not an aggregate. As a result B{}
is surely not aggregate initialization, and B{}
and B()
end up meaning the same thing. They both just invoke B
's default constructor.
However, in C++17, the definition of aggregate was changed to:
An aggregate is an array or a class with
- no user-provided, explicit, or inherited constructors ([class.ctor]),
- no private or protected non-static data members (Clause [class.access]),
- no virtual functions, and
- no virtual, private, or protected base classes ([class.mi]).
[ Note: Aggregate initialization does not allow accessing protected and private base class' members or constructors. — end note ]
The restriction is no longer on any base classes, but just on virtual/private/protected ones. But B
has a public base class. It is now an aggregate! And C++17 aggregate initialization does allow for initializing base class subobjects.
In particular, B{}
is aggregate initialization where we just don't provide an initializer for any subobject. But the first (and only) subobject is an A
, which we're trying to initialize from {}
(during aggregate initialization, any subobject without an explicit initializer is copy-initialized from {}
), which we can't do because A
's constructor is protected and we are not a friend (see also, the quoted note).
Note that, just for fun, in C++20 the definition of aggregate will change again.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With