Search code examples
c++c++17diamond-problem

Diamond problem initialisation - default constructor ignored?


Possible duplicate of:

Consider the following example:

#include <iostream>
#include <cassert>

struct MyEnum {
    enum valid { CASE1, CASE2, DEFAULT };
    valid value;
    MyEnum(valid value = DEFAULT) : value(value) {}
};

class Base {
protected:
    MyEnum value;

public:
    Base() = default;
    Base(MyEnum value) : value(value) {
        std::cout << "Base::Base(MyEnum).\n";
    }  
};

class ReadableBase : public virtual Base {
public:
    ReadableBase() = default;
    ReadableBase(MyEnum value) : Base(value) {
        std::cout << "ReadableBase::ReadableBase(MyEnum).\n";
    }

public:
    MyEnum read() { return value; }
};

class WriteableBase : public virtual Base {
public: 
    WriteableBase() = default;
    WriteableBase(MyEnum value) : Base(value) {
        std::cout << "WriteableBase::WriteableBase(MyEnum).\n";    
    }

public:
    void write(MyEnum value) { this->value = value; }
};

class ReadWriteBase : public ReadableBase, 
                      public WriteableBase {
public:
    ReadWriteBase() = default;
    ReadWriteBase(MyEnum value) : ReadableBase(value), WriteableBase(value) {}
};

int main(int, char*[]) {
    // Incorrectly initialised with value = MyEnum::valid::DEFAULT
    ReadWriteBase rw(MyEnum::valid::CASE1);
    
    // Everything is okay now.
    rw.write(MyEnum::valid::CASE2);
}

As demonstrated, despite the ReadWriteBase derived class accepting the MyEnum with the equivalent value of 0 (= MyEnum::valid::CASE1), the program reports that the value of ReadableBase::value and WriteableBase::value is 2 (= MyEnum::valid::DEFAULT). This appears to be because Base::Base(MyEnum) is not called at all.

assert(rw.ReadableBase::value  == MyEnum::valid::DEFAULT); // true
assert(rw.WriteableBase::value == MyEnum::valid::DEFAULT); // true
assert(rw.Base::value          == MyEnum::valid::DEFAULT); // true

Why does this happen? How do I do this correctly?

Bonus question: Is there a better way to approach this problem?


Solution

  • Why does this happen? How do I do this correctly?

    virtual base should be initialized in the most derived class, so it should be

    class ReadWriteBase : public ReadableBase, 
                          public WriteableBase {
    public:
        ReadWriteBase() = default;
        ReadWriteBase(MyEnum value) :
            Base(value),
            ReadableBase(value), // possibly ReadableBase()
            WriteableBase(value) // possibly WriteableBase()
        {}
    };