Dziwny domyślny pusty konstruktor na wirtualnym zachowaniu dziedziczenia w GCC

Dec 14 2020

Mam następującą sytuację klasy pochodnej z dziedziczeniem wirtualnym do klasy bazowej w moim kodzie:

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

Połączyć: https://godbolt.org/z/bn1EY6

GCC (trunk) daje następujący błąd: error: use of deleted function 'Derived::Derived()'podczas gdy Clang (trunk) kompiluje go bez problemu.

GCC działa, jeśli zmienię konstruktora na Derived() {}zamiast Derived() = defaultlub zdefiniuję pusty konstruktor w klasie Base.

Dlaczego = defaultw tym przypadku usunięcie funkcji w GCC?

Odpowiedzi

2 eerorika Dec 14 2020 at 18:29

Norma mówi (najnowsza wersja robocza):

[class.default.ctor]

Domyślny domyślny konstruktor dla klasy X jest definiowany jako usunięty, jeśli:

  • X to związek, który ... [[nie dotyczy]]
  • X to klasa niezrzeszona, która ma wariant składowy M z ... [[nie dotyczy]]
  • dowolny niestatyczny element członkowski danych bez domyślnego inicjatora elementu członkowskiego ([class.mem]) jest typu referencyjnego, [[nie dotyczy]]
  • dowolny niezmienny niestatyczny element członkowski danych typu const kwalifikowanego ... [[nie dotyczy]]
  • X jest związkiem i ... [[nie dotyczy]]
  • X jest klasą nie związkową i wszyscy członkowie dowolnego anonimowego członka związku ... [[nie dotyczy]]
  • [dotyczy, jeśli baza jest potencjalnie skonstruowanym podobiektem ] każdy potencjalnie skonstruowany podobiekt, z wyjątkiem niestatycznego elementu danych z inicjatorem nawiasu klamrowego lub równego, ma typ klasy M (lub jego tablicę) i M nie ma domyślnego konstruktora lub rozpoznanie przeciążenia ([over.match]) zastosowane do znalezienia odpowiedniego konstruktora M skutkuje niejednoznacznością lub funkcją, która jest usunięta lub niedostępna z domyślnego konstruktora domyślnego lub
  • każdy potencjalnie skonstruowany podobiekt ma typ z destruktorem, który jest usunięty lub niedostępny z domyślnego konstruktora domyślnego. [[nie dotyczy]]

Potencjalnie tylko jedna reguła ma zastosowanie do usuwanego domyślnego konstruktora domyślnego i zależy od tego, czy baza jest potencjalnie skonstruowanym podobiektem .

[specjalny]

W przypadku klasy jej niestatyczne składowe danych, jej niewirtualne bezpośrednie klasy bazowe oraz, jeśli klasa nie jest abstrakcyjna ([class.abstract]), jej wirtualne klasy bazowe nazywane są jej potencjalnie skonstruowanymi podobiektami.

Derivedjest abstrakcyjna (ponieważ nie implementuje wszystkich czystych funkcji wirtualnych) i Basejest wirtualną bazą, dlatego baza nie jest potencjalnie skonstruowanym podobiektem, a zatem jedyna reguła, która w przeciwnym razie obowiązywałaby dla usuwanego domyślnego konstruktora, nie ma zastosowania i dlatego nie należy go usuwać. Kompilator jest zły.


Prostym obejściem (oprócz tych, o których już wspomniałeś) jest brak deklaracji Derived::Derieved(). Wydaje się, że w tym przypadku jest on generowany domyślnie.


Dodanie noexcept powoduje błąd wewnętrzny błąd kompilatora

Jest to również błąd kompilatora.

1 AdrianMole Dec 14 2020 at 18:02

Dlaczego w tym przypadku = default usuwa funkcję w GCC?

To, czy jest to błąd w GCC (MSVC zachowuje się podobnie, ale clang-cl akceptuje kod w takiej postaci, w jakiej jest), jest kwestią dla tych, którzy są bardziej studiowani w standardach C ++. Wygląda jednak na to, że kompilator przyjmuje = default, że Derivedkonstruktor zależy (lub jest równoważny ) z domyślnym konstruktorem dla Base- który jest zdecydowanie usuwany, ponieważ zdefiniowano inny (inny niż domyślny) konstruktor.

Jednak jawne dodanie własnego domyślnego konstruktora, Derived() {}usuwa tę domniemaną zależność.

Potwierdza się to (w GCC i MSVC) przez określenie (czyli cofnięcie usunięcia) domyślnego konstruktora dla Baseklasy:

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

EDYCJA : Może to być również istotne, a nawet możliwe duplikaty: Dlaczego domyślny konstruktor jest wywoływany w dziedziczeniu wirtualnym?