Inheriting Constructors

Inheriting constructors

If your compiler supports C++11 standard, there is a constructor inheritance using using (pun intended). For more see Wikipedia C++11 article. You write:

class A
{
public:
explicit A(int x) {}
};

class B: public A
{
using A::A;
};

This is all or nothing - you cannot inherit only some constructors, if you write this, you inherit all of them. To inherit only selected ones you need to write the individual constructors manually and call the base constructor as needed from them.

Historically constructors could not be inherited in the C++03 standard. You needed to inherit them manually one by one by calling base implementation on your own.


For templated base classes, refer to this example:

using std::vector;

template<class T>
class my_vector : public vector<T> {
public:
using vector<T>::vector; ///Takes all vector's constructors
/* */
};

Inherited Constructors in C++

The default constructors are being called because Derived inherits from Base1 and Base2. Both of those bases need to be constructed when you construct a Derived object. So when you do

Derived d1(3);

You call Base1(int i). Now you need to construct the Base2 part and since you do not specify how, the compiler default constructs it. The same thing happens in

Derived d2("hello");

Since you do not specify how to construct the Base1 part in the constructor the compiler default constructs it for you. Then Base2(const string& s) is called to construct the Base2 part.

Essentially what you have is

class Derived :public Base1, public Base2
{
public:
Derived(int n) : Base1(n), Base2() {}
Derived(const std::string& str) : Base1(), Base2(str) {}
};

How useful would Inheriting Constructors be in C++?

2) Are there any technical reasons you can think of that would preclude "perfect forwarding constructors" from being an adequate alternative?

I have shown one problem with that perfect forwarding approach here: Forwarding all constructors in C++0x .

Also, the perfect forwarding approach can't "forward" the expliciteness of base-class constructors: Either it is always a converting constructor or never, and the base-class will always be direct initialized (always making use of all constructors, even explicit ones).

Another problem are initializer-list constructors because you can't deduce Args to initializer_list<U>. Instead, you would need to forward to the base with B{args...} (note the braces) and initialize D objects with (a, b, c) or {1, 2, 3} or = {1, 2, 3}. In that case, Args would be the element types of the initializer list, and forward them to the base class. A initializer-list constructor can then receive them. This seems to cause unnecessary code bloat because the template argument pack will potentially contain lots of type sequences for each different combination of types and length and because you have to choose an initialization syntax this means:

struct MyList {
// initializes by initializer list
MyList(std::initializer_list<Data> list);

// initializes with size copies of def
MyList(std::size_t size, Data def = Data());
};

MyList m{3, 1}; // data: [3, 1]
MyList m(3, 1); // data: [1, 1, 1]

// either you use { args ... } and support initializer lists or
// you use (args...) and won't
struct MyDerivedList : MyList {
template<class ... Args>
MyDerivedList(Args&& ... args) : MyList{ args... } { }
};

MyDerivedList m{3, 1}; // data: [3, 1]
MyDerivedList m(3, 1); // data: [3, 1] (!!)

C++11 inheriting constructors and access modifiers

According to 12.9/4, "Inheriting constructors", when saying using X::X,

A constructor so declared has the same access as the corresponding constructor in X.

So the inherited constructor is also protected.

Why does using-declared inheriting constructor NOT initialize the virtual base class using a default constructor?

I am just answering your question in the edit.

... but how is [the virtual base constructor] inherited by the grandchild class BC in the first place?

The paragraph you quoted says:

Constructors that are introduced by a using-declaration are treated as though they were constructors of the derived class when looking up the constructors of the derived class (class.qual) [...].

i.e., it says that these constructors are found by qualified name lookup. This makes the using-declaration effectively a recursive operation: to find a thing in the base class, it uses qualified name lookup, and then makes the thing it found available for qualified name lookup.

When struct B uses using A::A, it makes the constructor A::A available when a constructor is looked up in struct B. This happens when struct BC uses using B::B; this is a qualified lookup of the name B in struct B; therefore, it finds the constructor A::A, and in this way makes A::A available in struct BC.

What is constructor inheritance?

Inheriting Constructors means just that. A derived class can implicitly inherit constructors from its base class(es).

The syntax is as follows:

struct B
{
B(int); // normal constructor 1
B(string); // normal constructor 2
};

struct D : B
{
using B::B; // inherit constructors from B
};

So now D has the following constructors implicitly defined:

D::D(int); // inherited
D::D(string); // inherited

Ds members are default constructed by these inherited constructors.

It is as though the constructors were defined as follows:

D::D(int x) : B(x) {}
D::D(string s) : B(s) {}

The feature isn't anything special. It is just a shorthand to save typing boilerplate code.

Here are the gory details:

12.9 Inheriting Constructors


1) A using-declaration that names a constructor implicitly declares a
set of inheriting constructors. The candidate set of inherited
constructors from the class X named in the using-declaration consists
of actual constructors and notional constructors that result from the
transformation of defaulted parameters as follows:

  • all non-template constructors of X, and
  • for each non-template constructor of X that has at least one parameter with a default argument, the set of constructors that
    results from omitting any ellipsis parameter specification and
    successively omitting parameters with a default argument from the end
    of the parameter-type-list, and
  • all constructor templates of X, and
  • for each constructor template of X that has at least one parameter with a default argument, the set of constructor templates that results
    from omitting any ellipsis parameter specification and successively
    omitting parameters with a default argument from the end of the
    parameter-type-list

Inheriting constructors w / wo their default arguments?

From [class.inhctor]:

The
candidate set of inherited constructors from the class X named in the using-declaration consists of actual
constructors and notional constructors that result from the transformation of defaulted parameters and
ellipsis parameter specifications as follows:

— [...]

— for each non-template constructor of X that has at least one parameter with a default argument, the set
of constructors that results from omitting any ellipsis parameter specification and successively omitting
parameters with a default argument
from the end of the parameter-type-list, and

— [...]

We have two non-template constructors of Base. The default constructor of Base introduces the candidate:

Derived() : Base() { }

And the other constructor of Base introduces one candidate for each successively omitted parameter. Namely:

Derived(int x, int y, int z) : Base(x, y, z) { }
Derived(int x, int y) : Base(x, y) { }
Derived(int x) : Base(x) { }

The default arguments are not inherited - we just get a different constructor for each number of arguments. So Derived(5) simply calls Base(5), not Base(5, 88, 99).

The end result is the same - just how we get there is a bit different.

Inheriting constructors in C++20 (Visual Studio 2019)

Is this something new that was put in C++20 or is it some Microsoft specific thing?

Not with relation to inherited constructors. What changed is that aggregate initialization may use parenthesis under certain conditions. Derived is considered an aggregate on account of having no private parts, so we initialize its bases and members directly.

It even works when we add a public member:

class Base
{
public:
Base(int ) {}
};

struct Derived : public Base
{
// no constructors declared in Derived
int y;
};

int main() {
Derived d(5, 4);
}

Live



Related Topics



Leave a reply



Submit