What is the construction order in a "diamond problem" where a class is derived from three base classes?

Below is an example code of what I am asking. The output will be XXbBACY, but I don't understand why.

#include <iostream>
using namespace std;

class X {
public:
    X() { cout << 'X'; }
    X(char c) { cout << 'X' << c; }
};

class A : virtual X {
public:
    A() : X('a') { cout << 'A'; }
};

class B : X {
public:
    B() : X('b') { cout << 'B'; }
};

class C : virtual X {
public:
    C() : X('c') { cout << 'C'; }
};

class Y : A, virtual B, C {
public:
    Y() { cout << 'Y'; }
    ~Y() {}
};

int main() {
    Y y;
    return 0;
}

From my understanding, when we create the object y of class Y, because not all the base classes are virtually derived from the class X (B specifically), Y will have 3 instances of class X in it.

The first constructor called when constructing the object y will be the constructor of class B because it is virtually derived from class Y, but before constructing the class B, an instance of class X will have to be created and because B() : X('b') { cout << 'B'; } is the default constructor of class B, X(char c) { cout << 'X' << c; } will be called in class X. Which will print Xb. Now we return to class B where the default constructor will print B, and then back to class Y

Now an instance of class A and class C will similarly be constructed leaving us with an output of: XbBXaAXcCY

My understanding seems to be completely wrong, because the output is XXbBACY, how can this be?


Solution 1:

Construction order is always this:

  1. First all (direct or indirect) virtual bases, ordered depth-first declaration-order. Obviously at most one of any type.
  2. Next, the non-virtual bases in declaration order.
  3. All other members in declaration order.
  4. Finally, the ctor body. Now you may safely call virtual functions, directly or indirectly, and they will resolve based on the currently running ctor.

In your case that means:

  1. virtual X from Y::A::X.
  2. virtual B from Y::B:
    (First step: X from Y::B::X.)
  3. A from Y::A. X virtual base already done.
  4. C from Y::C. X virtual base already done.

Destruction order reverses that.