Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Strange default empty constructor on a virtual inheritance behaviour on GCC

I have the following situation of a Derived class with virtual inheritance to a Base class in my code:

class Base {
    int x;
public:
    Base(int x): x{x} {}
    virtual void f() = 0;
};

class Derived : public virtual Base  {
  public:
    Derived() = default;
};

class Concrete: public Derived {
public:
    Concrete(): Base{42} {}
    void f() override {}
};

Link: https://godbolt.org/z/bn1EY6

GCC (trunk) gives the following error: error: use of deleted function 'Derived::Derived()' while Clang (trunk) compiles it without a problem.

GCC works if I change the constructor to Derived() {} instead of Derived() = default or define an empty constructor on the Base class.

Why is the = default removing the function in GCC in this case?

like image 396
Lucas Marcondes Pavelski Avatar asked Dec 14 '20 10:12

Lucas Marcondes Pavelski


2 Answers

Standard says (latest draft):

[class.default.ctor]

A defaulted default constructor for class X is defined as deleted if:

  • X is a union that ... [[does not apply]]
  • X is a non-union class that has a variant member M with ... [[does not apply]]
  • any non-static data member with no default member initializer ([class.mem]) is of reference type, [[does not apply]]
  • any non-variant non-static data member of const-qualified type ... [[does not apply]]
  • X is a union and ... [[does not apply]]
  • X is a non-union class and all members of any anonymous union member ... [[does not apply]]
  • [applies if the base is a potentially constructed subobject] 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, or
  • any potentially constructed subobject has a type with a destructor that is deleted or inaccessible from the defaulted default constructor. [[does not apply]]

Only one rule potentially applies for the defaulted default constructor being deleted, and it depends on whether the base is a potentially constructed subobject.

[special]

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.

Derived is abstract (because it doesn't implement all pure virtual functions), and Base is a virtual base, therefore the base is not a potentially constructed subobject, and therefore the only rule that would otherwise have applied for the defaulted constructor being deleted does not apply and thus it should not be deleted. The compiler is wrong.


A simple workaround (besides those that you already mentioned) is to no declare Derived::Derieved() at all. It seems to be correctly implicitly generated in that case.


Adding the noexcept yields the error internal compiler error

This is also a compiler bug.

like image 164
eerorika Avatar answered Oct 30 '22 10:10

eerorika


Why is the = default removing the function in GCC in this case?

Whether or not this is a bug in GCC (MSVC behaves similarly but clang-cl accepts the code, as is) is a matter for those more studied in the C++ Standards. However, it appears that the complier is taking the = default to imply that the Derived constructor depends on (or is equivalent to) the default constructor for Base - which is definitely deleted, as you have defined another (non-default) constructor.

However, explicitly adding your own default constructor, with Derived() {} removes that implied dependency.

This is confirmed (in GCC and MSVC) by specifying (i.e. undeleting) the default constructor for the Base class:

class Base {
    int x;
public:
    Base() : x{0} {}  // Adding this removes the error!
//  Base() = default; // Also works
    Base(int x): x{x} {}
    virtual void f() = 0;
};

class Derived : public virtual Base  {
  public:
    Derived() = default;
};

class Concrete: public Derived {
public:
    Concrete(): Base{42} {}
    void f() override {}
};

EDIT: This may also be relevant, or even a possible duplicate: Why is Default constructor called in virtual inheritance?

like image 36
Adrian Mole Avatar answered Oct 30 '22 10:10

Adrian Mole