Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why can this derived class be constructed with `{}` and not with `()` on C++17?

Tags:

c++

c++17

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); } 
like image 926
Lily Avatar asked Jan 26 '21 15:01

Lily


People also ask

Can a derived class have a constructor?

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.

Are constructors necessary in derived classes?

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.

What is derived class in C?

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.

Why do we use derived classes?

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.


1 Answers

Here's a quick rundown of the situation:

  1. Base is implicitly convertible from an int.
  2. Base is not an aggregate, since it has a user-provided constructor.
  3. 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).
  4. Derived is not an aggregate due to having a base class... in C++14.
  5. 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.

like image 196
Nicol Bolas Avatar answered Sep 20 '22 05:09

Nicol Bolas