c++multiple-inheritancevirtual-inheritanceusing-declaration

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


I stumbled upon a question of the using-declared inheriting constructor yesterday. And after carefully reading the answer as well as the linked standard draft N3337, I found there might be some inconsistency (or at least my misunderstanding) when the direct base class also uses using to inherit constructors from the virtual base.

Here is an example:

struct A
{
    A(int, int){}
};

struct B : virtual A
{
    using A::A;
};

struct C : virtual A
{
    using A::A;
};

struct BC : B, C
{
    using B::B;
    using C::C;
}; 

// Now if we define an inline constructor that does the same
// as the constructor B inherited...
struct BB : virtual A
{
    BB(int a, int b):A(a,b){}
};

struct BBC : BB, C
{
    using BB::BB;
    using C::C;
};

int main()
{
    BC(1, 1);  // this compiles
    BBC(1, 1); // this doesn't because it needs to defaultly
               // initialize the virtual base A who doesn't
               // have a default constructor
}

I understand why BBC can't compile for the exact reason provided by the answer above, which I will repeat here [class.inhctor]/8

An implicitly-defined inheriting constructor performs the set of initializations of the class that would be performed by a user-written inline constructor for that class with a mem-initializer-list whose only mem-initializer has a mem-initializer-id that names the base class denoted in the nested-name-specifier of the using-declaration and an expression-list as specified below, and where the compound-statement in its function body is empty ([class.base.init]).

and [class.base.init]/10

In a non-delegating constructor, initialization proceeds in the following order: First, and only for the constructor of the most derived class ([intro.object]), virtual base classes are initialized in the order they appear on a depth-first left-to-right traversal of the directed acyclic graph of base classes, where “left-to-right” is the order of appearance of the base classes in the derived class base-specifier-list.

So basically the virtual base class needs to be default-constructed because it's not in the mem-initializer-list of the inheriting constructor of BBC. But A doesn't have a default constructor so it fails (adding A()=default; can obviously make it compile but that's not the point here).

But I'm not clear yet why BC doesn't have this problem? It's essentially the same example given by cppreference in the Inheriting constructors section. So it must work. But doesn't it look contradictory to the standard? When B inherits constructors from A, it also gets nothing but the non-default one, which performs the same initialization as the one defined in BB, except being implicit. Then when this constructor from B is further inherited by BC, shouldn't the same rule apply where A will be default-constructed hence not compile?

Edit: @j6t pointed out I was looking at an outdated standard draft. The new one indeed conforms better with the cppreference page I found earlier.

One thing that remains unclear for me is that it does explain what should happen if the virtual base constructor is selected, but how is it inherited by the grandchild class BC in the first place? From the same draft, it seems the virtual base constructor introduced by using will only be considered in the derived class (B in this case). How does using in BC inherit a constructor that's two levels above?

What I would expect is that when BC using-declare the constructors, what originally inherited by B from A should be treated as B constructors first, then get inherited by BC. But that's not the case.


Solution

  • 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.