Eu tenho essas aulas:
#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;
}
Ao compilar, a_m
não é construtível por padrão, mas a
é.
Ao mudar C
para:
struct C {
int i;
};
tudo está bem.
Testado com Clang 9.0.0.
C() {}
isso também funciona.static_assert
inA
falha, mas se você construir umT
interiorA
por padrão (por exemplo, colocar um membroT t;
lá), tudo funcionará bem. Uma inconsistência entre o que o traço de tipo está lhe dizendo e o que é realmente possível ...const int x;
é inválido sem um inicializador, devido exclusivamente aoconst
comportamento de inicialização de tipos internos e alguns history)Respostas:
Isso não é permitido pelo texto do padrão e por várias implementações importantes, conforme observado nos comentários, mas por razões completamente não relacionadas.
Primeiro, a razão "pelo livro": o ponto de instanciação de
A<C>
é, de acordo com o padrão, imediatamente antes da definição deB
, e o ponto de instanciação destd::is_default_constructible<C>
imediatamente antes disso:Como
C
é claramente incompleto nesse ponto, o comportamento da instanciaçãostd::is_default_constructible<C>
é indefinido. No entanto, consulte a edição principal 287 , que mudaria essa regra.Na realidade, isso tem a ver com o NSDMI.
= 0
poderia, em princípio, se referir a coisasB
ainda não declaradas, portanto a implementação não pode realmente tentar analisá-la até que termineB
.C
não há um construtor declarado.A<C>
, ele pensa queC
está incompleto.Toda essa área que lida com regiões com atraso de análise é lamentavelmente subespecificada, com as divergências na implementação. Pode demorar um pouco antes de ser limpo.
fonte
Comportamento indefinido é:
fonte
C
está completo, masB
não está. EB::C
depende indiretamenteB
.C
tiver um modelo de construtor padrão com algum SFINAE estranho que possa alterar as respostas seB
for concluído de maneira diferente, então a característica depende disso.