Extraño constructor vacío predeterminado en un comportamiento de herencia virtual en GCC

Dec 14 2020

Tengo la siguiente situación de una clase derivada con herencia virtual a una clase base en mi 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 {}
};

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

GCC (troncal) da el siguiente error: error: use of deleted function 'Derived::Derived()'while Clang (troncal) lo compila sin problemas.

GCC funciona si cambio el constructor a en Derived() {}lugar de Derived() = defaultdefinir un constructor vacío en la clase Base.

¿Por qué se = defaultelimina la función en GCC en este caso?

Respuestas

2 eerorika Dec 14 2020 at 18:29

El estándar dice (último borrador):

[class.default.ctor]

Un constructor predeterminado predeterminado para la clase X se define como eliminado si:

  • X es una unión que ... [[no se aplica]]
  • X es una clase no sindical que tiene un miembro variante M con ... [[no aplica]]
  • cualquier miembro de datos no estático sin inicializador de miembro predeterminado ([class.mem]) es de tipo de referencia, [[no aplica]]
  • cualquier miembro de datos no estático no variante de tipo calificado const ... [[no aplica]]
  • X es una unión y ... [[no se aplica]]
  • X es una clase no sindical y todos los miembros de cualquier miembro sindical anónimo ... [[no aplica]]
  • [se aplica si la base es un subobjeto potencialmente construido ] cualquier subobjeto potencialmente construido, excepto un miembro de datos no estático con un inicializador de llave o igual, tiene el tipo de clase M (o una matriz del mismo) y M no tiene un constructor predeterminado o la resolución de sobrecarga ([over.match]) aplicada para encontrar el constructor correspondiente de M da como resultado una ambigüedad o una función que se elimina o es inaccesible desde el constructor predeterminado predeterminado, o
  • cualquier subobjeto potencialmente construido tiene un tipo con un destructor que se elimina o es inaccesible desde el constructor predeterminado predeterminado. [[no se aplica]]

Solo una regla se aplica potencialmente para el constructor predeterminado predeterminado que se elimina, y depende de si la base es un subobjeto potencialmente construido .

[especial]

Para una clase, sus miembros de datos no estáticos, sus clases base directas no virtuales y, si la clase no es abstracta ([class.abstract]), sus clases base virtuales se denominan subobjetos potencialmente construidos.

Derivedes abstracto (porque no implementa todas las funciones virtuales puras), y Basees una base virtual, por lo tanto, la base no es un subobjeto potencialmente construido y, por lo tanto, no se aplica la única regla que de otro modo se habría aplicado para que el constructor predeterminado se elimine y por lo tanto no debe eliminarse. El compilador está equivocado.


Una solución alternativa simple (además de las que ya mencionó) es no declarar Derived::Derieved()en absoluto. En ese caso, parece generarse implícitamente correctamente.


Agregar el noexcept produce el error error interno del compilador

Esto también es un error del compilador.

1 AdrianMole Dec 14 2020 at 18:02

¿Por qué = default elimina la función en GCC en este caso?

Si esto es o no un error en GCC (MSVC se comporta de manera similar pero clang-cl acepta el código, tal cual) es un asunto para aquellos más estudiados en los Estándares C ++. Sin embargo, parece que el compilador está interpretando = defaultque el Derivedconstructor depende de (o es equivalente a ) el constructor predeterminado para Base, que definitivamente se elimina, ya que ha definido otro constructor (no predeterminado).

Sin embargo, agregar explícitamente su propio constructor predeterminado, Derived() {}elimina esa dependencia implícita.

Esto se confirma (en GCC y MSVC) especificando (es decir, recuperando) el constructor predeterminado para la Baseclase:

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

EDITAR : Esto también puede ser relevante, o incluso un posible duplicado: ¿Por qué se llama al constructor predeterminado en la herencia virtual?