In this example, C++17 can construct a Derived class using the Base class's constructor using {}
, but not ()
.
This also works on C++20 for GCC and Clang, but not for MSVC.
Why can I constuct the Derived class this way? Is it safe?
I have looked through the additions and changes in C++17 and can not find what changed to allow this.
It is very possible that I am blind.
#include <iostream> class Base { public: Base(const int value) { std::cout << "Constructed with value: " << value << '\n'; } }; class Derived : public Base { }; int main(){ // does compile on C++17 with MSVC // does not compile pre or post C++17 with MSVC // does compile on and post C++17 on GCC and Clang // does not compile pre C++17 with GCC and Glang Derived foo{ 42 }; // does not compile on any C++ version with MSVC, GCC or Clang Derived bar(42); }
In inheritance, the derived class inherits all the members(fields, methods) of the base class, but derived class cannot inherit the constructor of the base class because constructors are not the members of the class.
A constructor plays a vital role in initializing an object. An important note, while using constructors during inheritance, is that, as long as a base class constructor does not take any arguments, the derived class need not have a constructor function.
Derived Class: A class that is created from an existing class. The derived class inherits all members and member functions of a base class. The derived class can have more functionality with respect to the Base class and can easily access the Base class. A Derived class is also called a child class or subclass.
Usually you use a derived class when an existing class provides members that the new class can use, or when you want to extend or embellish existing class properties and methods. This is called inheritance: the new class inherits, and has direct access to, all Public and Private members of the existing base class.
Here's a quick rundown of the situation:
Base
is implicitly convertible from an int
.Base
is not an aggregate, since it has a user-provided constructor.Derived
is not convertible from an int
(implicitly or otherwise), since base-class constructors are not inherited unless you explicitly inherit them (which you didn't).Derived
is not an aggregate due to having a base class... in C++14.Derived
is an aggregate in C++17, which allows aggregates to have base classes. Derived
does not have any constructors provided by the user; again, Base
's constructor doesn't matter because it was not inherited.Given these facts, what's happening is the following.
Attempting to use {}
on a type will first (sort of) check to see if that type is an aggregate; if so, it will perform aggregate initialization using the values in the braced-init-list. Since whether Derived
is an aggregate changed between C++14 and C++17, the validity of that initialization changed as well.
Per #4, Derived
is not an aggregate in C++14. So list initialization rules will attempt to call a constructor that takes an int
. Per #3, Derived
has no such constructor. So Derived foo{ 42 };
is il-formed in C++14.
Per #5, Derived
is an aggregate in C++17. So list initialization rules will perform aggregate initialization. This is done by copy-initializing each subobject of the aggregate by the corresponding initializer in the braced-init-list. Derived
has only one subobject, of type Base
, and the braced-init-list only has one initializer: 42
. So it will perform copy-initialization of Base
by the initializer 42
. That will attempt to perform implicit conversion from an int
to Base
, which is valid per #1.
So Derived foo{ 42 };
is valid in C++17.
Visual Studio may not have implemented C++17's ruleset correctly.
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