3

I have a class Base which has a parameterized constructor and two classes Middle1 and Middle2 which virtually inherit from Base (in order to solve the diamond problem). In addition, class Foo inherits from Middle1 and Middle2.

Foo now calls the Base constructor explicitly and passes the parameter.

class Base
{
private:
    int value;
protected:
    Base(int& value) { this->value = value; }
};

class Middle1 : virtual public Base
{
protected:
    Middle1() { }
};

class Middle2 : virtual public Base
{
protected:
    Middle2() { }
};

class Foo : public Middle1, public Middle2
{
public:
    Foo(int& value) : Base(value) { }    
};

However, the code does not compile because Base lacks a default constructor and thus Middle1 and Middle2 don't have a default constructor to call, but need one which can be called by Foo.

Of course, I could now change the constructors of Middle1 and Middle2 to call the constructor of Base, too:

Middle1(int& value) : Base(value) { }
//...
Middle2(int& value) : Base(value) { }
// and then change my constructor of Foo:
Foo(int& value) : Base(value), Middle1(value), Middle2(value) { }  

However, it seems rather clunky - especially given that because of the virtual inheritance, the Base constructor will never be called by Middle1 or Middle2 (they are not meant to be initialized, i.e. are basically abstract classes). Thus, having to introduce the parameter to the constructors of both Middle1 and Middle2 seems rather useless.

Is there any other way to make the above code compile without also having to introduce parameterized constructors in Middle1 and Middle2?

Matthias
  • 12,053
  • 4
  • 49
  • 91
  • The problem is related to construction of `Middle1` and `Middle2` - since you have implemented their default constructor - and is not caused by `Foo`. The compiling fails because your definitions of the constructors for `Middle1` and `Middle2` both implicitly call the the default constructor of `Base` (e.g. `Middle1() : {}` is equivalent to `Middle1(): Base() {}` which calls the default constructor). Deriving `Foo` from `Middle1` and `Middle2` doesn't change that - in fact, if you remove the definition of `Foo` the code will still not compile. – Peter Sep 30 '21 at 05:39

1 Answers1

2

In virtual inheritance, the most-derived class has to directly call all of its ancestor constructors. Since Base doesn't have a default constructor, Middle1() and Middle2() can't compile if they can't pass an int& to Base().

However, in the code you have shown, there is no reason for Base() to take an int by reference. Pass it by value instead, and then Middle1() and Middle2() can pass 0 to Base():

class Base
{
private:
    int value;
protected:
    Base(int value = 0) { this->value = value; }
};

class Middle1 : virtual public Base
{
protected:
    Middle1() { }
};

class Middle2 : virtual public Base
{
protected:
    Middle2() { }
};

class Foo : public Middle1, public Middle2
{
public:
    Foo(int value) : Base(value) { }    
};

Though, I would suggest passing a pointer (or a std::optional) instead:

class Base
{
private:
    int value;
protected:
    Base(int* avalue = nullptr) { if (avalue) this->value = *avalue; }
};

class Middle1 : virtual public Base
{
protected:
    Middle1() { }
};

class Middle2 : virtual public Base
{
protected:
    Middle2() { }
};

class Foo : public Middle1, public Middle2
{
public:
    Foo(int& value) : Base(&value) { }    
};
Remy Lebeau
  • 555,201
  • 31
  • 458
  • 770
  • Thanks! The int& is just for illustration - in reality it's a struct reference. But your comment just made me realize that I could just let Middle1 and Middle2 pass an empty value of that given struct. Maybe still not perfect, but already much better. Or based on the example: `static int DUMMY_VALUE = 0; /*...*/ Middle1() : Base(DUMMY_VALUE)`. Or just change the reference to a pointer. – Matthias Sep 30 '21 at 04:56