#include <iostream>
#include <cstdint>

#define  w(expr)  cout << #expr << ": " << expr << endl

class A {
public:
    virtual uint64_t sum() const { return a; }
private:
    uint64_t a = 5;
};

class B : public A {
public:
    virtual uint64_t sum() const { return b; }
private:
    uint64_t b = 100;
};

class C : public A {
public:
    virtual uint64_t sum() const { return c; }
private:
    uint64_t c = 150;
};

class D : public B, public C {
public:
    uint64_t sum() const override { return B::sum() + C::sum() + d; }

    // A::sum() is ambiguous and does not compile:
    // uint64_t sum() const override { return A::sum() + B::sum() + C::sum() + d; }

private:
    uint64_t d = 200;
};

int main() {
    using namespace std;

    // 1.  The diamond problem: D object contains two A portions

    D x;

    cout << "*** Object layout" << endl;
    static_assert(sizeof(D) == 7 * sizeof(uint64_t));
    uint64_t* p = reinterpret_cast<uint64_t*>(&x);
    w( reinterpret_cast<void*>(p[0]) );
    w( p[1] );
    w( p[2] );
    w( reinterpret_cast<void*>(p[3]) );
    w( p[4] );
    w( p[5] );
    w( p[6] );

    // 2.  Base classes B & C still behave the same

    cout << "*** Base pointers have different addresses" << endl;
    B* pb = &x;
    C* pc = &x;
    D* pd = &x;
    w( pb );
    w( pc );
    w( pd );
    w( pb->sum() );
    w( pc->sum() );
    w( pd->sum() );

    // 3.  But referring to the base class A is ambiguous
    //
    // A* pa = &x;
}
