Tentando entender modelos e pesquisa de nome

9

Estou tentando entender os seguintes trechos de código

Snippet # 1

template <typename T>
struct A
{
    static constexpr int VB = T::VD;
};

struct B : A<B>
{
};

Nem o gcc9 nem o clang9 lançam um erro aqui.

P. por que esse código é compilado? Não estamos instanciando A<B>ao herdar de B? Não há VD em B, então o compilador não deve lançar um erro aqui?

Snippet # 2

template <typename T>
struct A
{
    static constexpr auto AB = T::AD; // <- No member named AD in B
};

struct B : A<B>
{
    static constexpr auto AD = 0xD;
};

Nesse caso, o gcc9 compila bem, mas o clang9 gera um erro dizendo "Nenhum membro chamado AD em B".

P. Por que ele compila com o gcc9 / por que não compila com o clang9?

Snippet # 3

template <typename T>
struct A
{
    using TB = typename T::TD;
};

struct B : A<B>
{
    using TD = int;
};

Aqui, clang9 e gcc9 lançam um erro. gcc9 diz "uso inválido do tipo incompleto 'struct B'".

Q. Se a estrutura B está incompleta aqui, por que não está incompleta no trecho 2?

Bandeiras do compilador usado: -std=c++17 -O3 -Wall -Werror. Desde já, obrigado!!!

Efeito Colateral Mutável
fonte
@xception Não é struct Binstanciado Acom B?
Efeito Colateral Mutável
clang9 gera um erro dizendo "Nenhum membro chamado AD em B" . como Bestá incompleto ... Mas não
tenho
@MutableSideEffect oh sim, meu mal, leia isso como um modelo também :(
xception
@ Jarod42, então por que o gcc compila bem?
Efeito Colateral Mutável
11
Eu sinalizei essa pergunta como "precisando de mais foco" e a pergunta realmente contém mais de uma pergunta (daí a minha conclusão); então, por que minha bandeira está errada?
Dominique

Respostas:

4

Eu acredito que estes se resumem a [temp.inst] / 2 (grifo meu):

A instanciação implícita de uma especialização de modelo de classe causa a instanciação implícita das declarações, mas não das definições , argumentos padrão ou noexcept-specifiers das funções de membro da classe, classes de membros, enumerações de membros com escopo, membros de dados estáticos , modelos de membros e amigos; [...]

e [temp.inst] / 9

Uma implementação não deve instanciar implicitamente […] um membro de dados estáticos de um modelo de classe […], a menos que tal instanciação seja necessária.

A redação do padrão referente à instanciação implícita de modelos deixa muitos detalhes abertos à interpretação. Em geral, parece-me que você simplesmente não pode confiar em partes de um modelo que não são instanciadas, a menos que a especificação o diga explicitamente. Portanto:

Snippet # 1

P. por que esse código é compilado? Não estamos instanciando A ao herdar de B? Não há VD em B, então o compilador não deve lançar um erro aqui?

Você está instanciando A<B> . Mas a instanciação A<B>apenas instancia as declarações, não as definições de seus membros de dados estáticos.VBnunca é usado de uma maneira que exija uma definição para existir. O compilador deve aceitar esse código.

Snippet # 2

P. Por que ele compila com o gcc9 / por que não compila com o clang9?

Conforme apontado por Jarod42, a declaração de AB contém um tipo de espaço reservado. Parece-me que a redação do padrão não é muito clara sobre o que deveria acontecer aqui. A instanciação da declaração de um membro de dados estático que contém um tipo de espaço reservado aciona a dedução do tipo de espaço reservado e, portanto, constitui um uso que requer a definição do membro de dados estáticos? Não consigo encontrar uma redação no padrão que diga claramente sim ou não a isso. Assim, eu diria que as duas interpretações são igualmente válidas aqui e, portanto, o GCC e o clang estão certos ...

Snippet # 3

Q. Se a estrutura B está incompleta aqui, por que não está incompleta no trecho 2?

Um tipo de classe está completo apenas no ponto em que você alcança o fechamento }do especificador de classe [class.mem] / 6 . Portanto, Bfica incompleto durante a instanciação implícita de A<B>em todos os seus snippets. Isso é irrelevante para o snippet nº 1. No Snippet # 2, o clang causou um erro No member named AD in Bcomo resultado. Semelhante ao caso do Snippet # 2, não consigo encontrar quando exatamente as declarações de alias de membros seriam instanciadas. No entanto, diferentemente da definição de membros de dados estáticos, não há texto para impedir explicitamente a instanciação de declarações de alias de membro durante a instanciação implícita de um modelo de classe. Assim, eu diria que o comportamento do GCC e do clang é uma interpretação válida do padrão neste caso ...

Michael Kenzel
fonte
Obrigado. Nesse caso, inicializamos o membro de dados estáticos no corpo. A inicialização faz parte da declaração ou parte da definição do membro de dados estático? Fiquei com a impressão de que, se a inicialização está dentro do corpo, isso faz parte da declaração do membro de dados estático. Se fizer parte da declaração, sua primeira cotação exigirá instanciação imediata como parte da instanciação implícita ao redor do modelo de classe.
Johannes Schaub - litb
Eu olhei para as especificações e parece que há uma diferença entre C ++ 14 e C ++ 17 aqui. No C ++ 14, o constexprmembro de dados estáticos era apenas uma declaração. O C ++ 17 ganhou inlinevariáveis ​​e constexprimplica inline, e isso faz da declaração do membro de dados estáticos do corpo uma definição.
Johannes Schaub - litb
eel.is/c++draft/dcl.spec.auto#4.sentence-2 diz "O tipo de uma variável declarada usando um tipo de espaço reservado é deduzido do inicializador. Esse uso é permitido em uma declaração de inicialização ([dcl. init]) de uma variável. ". Portanto, eu argumentaria que a definição do membro de dados estático é necessária, porque contém o inicializador.
Johannes Schaub - litb
@ JohannesSchaub-litb Obrigado por olhar para isso! Também estive pensando nessas perguntas, mas não consegui encontrar nenhuma palavra que fosse conclusiva. No que diz respeito à inicialização versus definição, considere uma definição de uma função membro dentro da definição do modelo de classe. Essa definição também é uma declaração e não há outras declarações. No entanto, a instanciação implícita do modelo de classe instancia apenas uma declaração, mas não a definição da função de membro. Por que a mesma coisa não seria verdadeira para membros de dados estáticos?
Michael Kenzel
se não houver nada que exija a definição, a definição não será instanciada. Mas, no autocaso, a regra diz que a declaração deve ser uma declaração de inicialização. Isso só pode ser o caso se soubermos que a declaração é uma definição (tanto quanto eu sei ... estou fora da área de advogados há um tempo). No passado, havia um caso semelhante e eel.is/c++draft/temp.inst#2.sentence-3 foi adicionado, onde uma declaração que é uma definição é instanciada como "sendo conhecida por ser uma definição" sem realmente instanciando a definição.
Johannes Schaub - litb