Skip to content

89 Advanced The Hundred

The Base Class You Have Twice

two writes to one int, and both survive

struct Device {
    int id = 0;
    void assign(int n) { id = n; }
};

struct Reader : Device {};
struct Writer : Device {};

struct Duplex : Reader, Writer {};

int main() {
    Duplex d;
    d.Reader::assign(7);
    d.Writer::assign(9);   // same id, second time... right?
    std::cout << "Reader / Writer id:   " << d.Reader::id << " / " << d.Writer::id << "\n";
    std::cout << "Device / Duplex size: " << sizeof(Device) << " / " << sizeof(Duplex) << "\n";
}

Run it. Did the second assign overwrite the first?

Answer

No — the ids print as 7 / 9, the sizes as 4 / 8. There is no the id; there are two.

Why

With ordinary inheritance every base subobject is complete and independent, so Duplex — reaching Device through both Reader and Writer — contains two whole Device subobjects side by side: 8 bytes for a class whose only data member is one int. The two assign calls write to different memory, so the id you get is the one you name — and name one you must: unqualified d.id doesn't compile, GCC listing the same candidate twice, deadpan.

error: request for member 'id' is ambiguous
note: candidates are: 'int Device::id'
note:                 'int Device::id'

Device* p = &d; is rejected the same way, and -Wall -Wextra never mentions the duplication.

The fix

Ask for a shared base with virtual, at both midpoints of the diamond:

struct Reader : virtual Device {};
struct Writer : virtual Device {};

Duplex now holds one shared Device: the ids print 9 / 9, d.id compiles, Device* p = &d; works. The price is an indirection to reach that base (size 24 here) and a rule that catches everyone — the most derived class initializes the virtual base. Give Device only a Device(int) and the Device(1)/Device(2) in Reader and Writer are ignored: Duplex itself must say Duplex() : Device(99), or the build fails with use of deleted function.

Takeaway: plain inheritance duplicates the base, virtual inheritance shares it — and only the most derived class may initialize a virtual base.

Try it: g++ -std=c++17 -Wall -Wextra main.cpp -o demo && ./demo; -DSHOW_BUG adds d.id.

Open in Compiler Explorer ↗ Open SHOW_BUG variant ↗ Quiz this entry