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 el libro": el punto de instanciación de A<C>
es, según el estándar, inmediatamente antes de la definición deB
, 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 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.
Como C
está claramente incompleto en ese punto, el comportamiento de la creación de instancias std::is_default_constructible<C>
no está definido. Sin embargo, vea el 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, eso
= 0
podría referirse en principio a cosas B
aún no declaradas, por lo que la implementación realmente no puede tratar de analizarlo hasta que haya terminado B
.
- Completar una clase requiere la declaración implícita de funciones miembro especiales, en particular el constructor predeterminado, ya
C
que 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
A<C>
se crea una instancia , 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.
C() {}
esto también funciona.static_assert
inA
falla, pero si por defecto construyes unT
interior deA
(por ejemplo, pones un miembroT t;
allí), todo funciona bien. Una inconsistencia entre lo que es realmente posible el tipo de rasgo que está diciendo y lo que ...const int x;
no es válido sin un inicializador, simplemente debido alconst
comportamiento de inicialización de los tipos incorporados y algunos historia)