Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Indirect virtual base without a default ctor stops children from having a default ctor, unless every class in between also has one

Tags:

c++

I'm sorry for the obscure title, not sure how to word it better.

Consider following inheritance hierarchy:

struct A
{
    A(int) {}
};

struct B : virtual A
{
    B() : A(42) {}
};

struct C : B
{
    C() : A(43) {}
};

It does work. Now let's say I want to create a template that can be transparently injected in the middle of a hierarchy, like this:

template <typename ...P>
struct Proxy : P...
{
    // This constructor doesn't change anything. It was added
    // to indicate that `Proxy` should only be used as a base.
  protected:
    Proxy() = default;
};

struct D : Proxy<B>
{
    D() : A(44) {}
};

This gives me: error: call to implicitly-deleted default constructor of 'Proxy<B>'.

Run on gcc.godbolt.org

I understand what's going on: Proxy can't have a default constructor, because it doesn't provide an initializer for A, hence the derived class can't default-construct Proxy.

But it doesn't make sense if you think about it, because even if I provided an initializer for A in Proxy, D would ignore it and would have to provide its own.

How can I work around this limitation?

Everything in code can be changed, but I'd prefer less invasive changes.


In my actual code, there's only one base class that causes those problems, so I made two different default constructors for Proxy (distinguished with requires): one doing nothing, and the other (which is used when any of P... virtually inherits from A) passing a dummy parameter to A::A(int).

But I don't like this solution because it's not generic, so I'm looking for better alternatives.

like image 980
HolyBlackCat Avatar asked Nov 06 '22 01:11

HolyBlackCat


1 Answers

[special]/7:

For a class, its non-static data members, its non-virtual direct base classes, and, if the class is not abstract ([class.abstract]), its virtual base classes are called its potentially constructed subobjects.

[class.default.ctor]/2.7 says that a defaulted default constructor is defined as deleted if

any potentially constructed subobject, except for a non-static data member with a brace-or-equal-initializer, has class type M (or array thereof) and either M has no default constructor or overload resolution ([over.match]) as applied to find M's corresponding constructor results in an ambiguity or in a function that is deleted or inaccessible from the defaulted default constructor

So we can exclude virtual bases from the set of potentially constructed subobjects by making Proxy abstract, for instance by making the destructor pure virtual (but still supply a definition):

template <typename ...P>
struct Proxy : P...
{
  protected:
    virtual ~Proxy() = 0;
    Proxy() = default;
};

template <typename ...P>
Proxy<P...>::~Proxy() = default;
like image 181
T.C. Avatar answered Nov 14 '22 21:11

T.C.