Diamond Inheritance and Constructors in C++

Can you remember all the inheritance rules in C++? I certainly can’t, which bit me recently. Here’s a simple inheritance tree (bottom-most node is most derived):

  A
  |
  B
 / \
C   D

Question: Who calls A’s constructor? Who calls B’s?

If you say B should call A’s constructor, and C and D each call B’s, you’re not wrong. However, what about this:

  A
  |
  B
 / \
C   D
 \ /
  E
  |
  F

Who calls B’s constructor now? What about A’s constructor?

The answer is F must call it, since the most-derived class in a diamond-inheritance tree must do the call. This is easy to forget, and it’s not intuitive, since in this specific case, E could also do the call unambiguously (the ambiguity stemming from the fact that we derive from B on multiple paths).

This is not a difficult rule, but I’m wondering if gcc could give an indication that something’s wrong. See this code:

class A
{
public:
    A() { cout << "--A--" << endl; } // For virtual inheritance
    A(int x) { cout << "A() " << x << endl; } // The actual constructor
};

class B : public virtual A
{
public:
    B(int x) /* : A(x) */ { cout << "B() " << x << endl; } // A() is not called here from D()
};

class C : public virtual A
{
public:
    C(int x) : A(x) { cout << "C() " << x << endl; } // ...or here. Even if it's not called further down!
};

class D : public B, public C
{
public:
    //D(int x) : B(x), C(x) { cout << "D() " << x << endl; } // Won't call A(x)
    D(int x) : A(x), B(x), C(x) { cout << "D() " << x << endl; } // Order of c'tor calls doesn't matter.
};

D has 2 constructors implemented. Only the second one (the one not commented) will call the actual constructor of A. Note that B and C each call A(x), but the call will not be made – and this seems like something that could trigger warnings by gcc. Of course, the compiler can’t know I don’t want to instantiate C (or D) while compiling those. Tricky, tricky!

Is it really worth it?

Like many advanced C++ features, diamond inheritance is a double-edged sword waiting for accidents to happen. Sometimes, it can really make sense to use diamond inheritance, and when that time comes, make sure to get the constructor calls right. In other cases, refactoring and simplifying code to not use diamond inheritance is probably the way to go.