Construtor vazio padrão estranho em um comportamento de herança virtual no GCC

Dec 14 2020

Tenho a seguinte situação de uma classe derivada com herança virtual para uma classe base em meu código:

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 {}
};

Ligação: https://godbolt.org/z/bn1EY6

GCC (tronco) dá o seguinte erro: error: use of deleted function 'Derived::Derived()'enquanto Clang (tronco) compila sem problemas.

O GCC funciona se eu alterar o construtor para em Derived() {}vez de Derived() = defaultou definir um construtor vazio na classe Base.

Por que = defaultremover a função no GCC neste caso?

Respostas

2 eerorika Dec 14 2020 at 18:29

Padrão diz (último rascunho):

[class.default.ctor]

Um construtor padrão padronizado para a classe X é definido como excluído se:

  • X é um sindicato que ... [não se aplica]]
  • X é uma classe não sindicalizada que possui um membro variante M com ... [não se aplica]]
  • qualquer membro de dados não estáticos sem inicializador de membro padrão ([class.mem]) é do tipo de referência, [[não se aplica]]
  • qualquer membro de dados não estático não variante de tipo qualificado const ... [não se aplica]]
  • 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.

1 AdrianMole Dec 14 2020 at 18:02

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?