Tengo esas clases:

#include <type_traits>

template <typename T>
class A {
public:
    static_assert(std::is_default_constructible_v<T>);

};

struct B {
   struct C {
      int i = 0;
   };

    A<C> a_m;
};

int main() {
    A<B::C> a;
}

Al compilar, a_m no es construible por defecto, pero a sí lo es.

Al cambiar C a:

struct C {
      int i;
   };

Todo esta bien.

Probado con Clang 9.0.0.

c++
28
Nicolas 9 dic. 2019 a las 18:52

2 respuestas

La mejor respuesta

Esto no está permitido tanto por el texto del estándar como por varias implementaciones importantes como se señala en los comentarios, pero por razones completamente ajenas.

Primero, la razón "por libro": el punto de instanciación de A<C> es, según el estándar, inmediatamente antes de la definición de B, y el punto de instanciación de std::is_default_constructible<C> es inmediatamente anterior a eso:

Para una especialización de plantilla de clase, [...] si la especialización se instancia implícitamente porque se hace referencia desde dentro de otra especialización de plantilla, si el contexto desde el que se hace referencia a la especialización depende de un parámetro de plantilla, y si la especialización no se instancia previamente para la instanciación de la plantilla adjunta, el punto de instanciación es inmediatamente anterior al punto de instanciación de la plantilla adjunta. De lo contrario, el punto de instanciación para tal especialización precede inmediatamente a la declaración o definición del alcance del espacio de nombres que se refiere a la especialización.

Dado que C está claramente incompleto en ese punto, el comportamiento de instanciar std::is_default_constructible<C> no está definido. Sin embargo, consulte problema central 287, que cambiaría esta regla.


En realidad, esto tiene que ver con el NSDMI.

  • Los NSDMI son extraños porque se retrasan el análisis, o en el lenguaje estándar son un "contexto de clase completa".
  • Por lo tanto, ese = 0 en principio podría referirse a cosas en B aún no declaradas, por lo que la implementación realmente no puede intentar analizarlo hasta que haya terminado con B.
  • Completar una clase requiere la declaración implícita de funciones miembro especiales, en particular el constructor predeterminado, ya que C no tiene un constructor declarado.
  • Partes de esa declaración (constexpr-ness, noexcept-ness) dependen de las propiedades del NSDMI.
  • Por lo tanto, si el compilador no puede analizar el NSDMI, no puede completar la clase.
  • Como resultado, en el momento en que crea una instancia de A<C>, piensa que C está incompleto.

Toda esta área que se ocupa de las regiones analizadas de forma diferida es lamentablemente poco especificada, con la divergencia de implementación que la acompaña. Puede pasar un tiempo antes de que se limpie.

9
T.C. 17 dic. 2019 a las 16:22

Comportamiento indefinido es:

Si una instanciación de una plantilla anterior depende, directa o indirectamente, de un tipo incompleto, y esa instanciación podría producir un resultado diferente si ese tipo se completara hipotéticamente, el comportamiento es indefinido.

0
Oblivion 9 dic. 2019 a las 16:13