#include <iostream>
#include <cstdint>
#include <cassert>

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

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

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

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

class D : public B, public C {
public:
    // There is now only one copy of A, so A::sum() is no longer ambiguous
    uint64_t sum() const override { return A::sum() + B::sum() + C::sum() + d; }
private:
    uint64_t d = 200;
};

int main() {
    using namespace std;

    // 1.  Virtual inheritance fixes the diamond problem

    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( reinterpret_cast<void*>(p[2]) );
    w( p[3] );
    w( p[4] );
    w( reinterpret_cast<void*>(p[5]) );
    w( p[6] );

    // 2.  sum() behaves polymorphically via any base pointer

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

    // 3.  (Optional) vtable layout

    {
        cout << "*** vtable layout" << endl;

        uint64_t* p = reinterpret_cast<uint64_t*>(&x);
        void** vtbl = reinterpret_cast<void**>(p[0]);

        w( reinterpret_cast<int64_t>(vtbl[-3]) );
        w( reinterpret_cast<int64_t>(vtbl[-2]) );
        w( vtbl[-1] );
        w( vtbl[0] );
        w( reinterpret_cast<int64_t>(vtbl[1]) );
        w( reinterpret_cast<int64_t>(vtbl[2]) );
        w( vtbl[3] );
        w( vtbl[4] );
        w( reinterpret_cast<int64_t>(vtbl[5]) );
        w( reinterpret_cast<int64_t>(vtbl[6]) );
        w( vtbl[7] );
        w( vtbl[8] );

        // First vptr points to vtbl[0]
        assert(reinterpret_cast<void**>(p[0]) == &vtbl[0]);
        // Second vptr points to vtbl[4]
        assert(reinterpret_cast<void**>(p[2]) == &vtbl[4]);
        // Third vptr points to vtbl[8]
        assert(reinterpret_cast<void**>(p[5]) == &vtbl[8]);
    }
}
