Search code examples
c++inheritancemultiple-inheritancediamond-problem

Why is super class empty constructor required but not called in a dreaded diamond situation?


I am trying to achieve the following design, which is a dreaded diamond situation:

struct super_base
{
  super_base(int a) { b = a; }
  int b;
};

struct base : virtual super_base
{};

struct other_base : virtual super_base
{};

struct derived : base, other_base
{
  derived(int a) : super_base{a} {}
};

int main() {}

Which doesn't work. The error for the above code using Clang is quite good at explaining the mistake:

error: call to implicitly-deleted default constructor of 'base'
  derived(int a) : super_base{a} {}
  ^
note: default constructor of 'base' is implicitly deleted because base
      class 'super_base' has no default constructor

So I added an empty constructor for super_base, and it works:

#include<iostream>
#include<stdexcept>

struct super_base
{
  super_base() { throw std::logic_error{"Constructor should not be called."}; };
  super_base(int a) { b = a; }
  int b;
};

struct base : virtual super_base
{};

struct other_base : virtual super_base
{};

struct derived : base, other_base
{
  derived(int a) : super_base{a} {}
};

int main() { std::cout << derived(10).b << '\n'; }

But why does this not throw ?

P.S.: I purposefully used a dreaded diamond pattern to illustrate the use of virtual inheritance. The problem remains the same with single inheritance.

P.P.S.: The compiler used is Clang 3.9.1. The results are the same with GCC 6.3.1.


Solution

  • struct base:super_base {}:
    

    this tries to create some constructors. One of them it tries to create is base::base().

    If super_base has no super_base::super_base(), this constructor is deleted.

    If we have super_base::super_base()=default or {}, then base::base() by default exists, even if you don't =default it.

    The same thing happens in other_base.

    And your derived class tries to call the base object constructors, which are deleted, which gives you an error.

    Now, why isn't it called? When you have a virtual base class, the constructor is only called once. Intermediate types which call the virtual base classes constructor have their calls ignored.

    So we have derived() calling base(), base() calling super_base(), but that call is ignored because of virtual inheritance.

    derived()'d call of super_base(int) is used instead.

    Now, why are these the rules? Because C++ doesn't have the concept of "constructor that can only be called if you are a derived class of this class explicitly calling a specific base constructor". Virtual inheritance is not quite as full featured as you might like.