Em modelos, onde e por que eu tenho que colocar typename
e template
sobre os nomes dos dependentes?
Quais são exatamente os nomes dependentes, afinal?
Eu tenho o seguinte código:
template <typename T, typename Tail> // Tail will be a UnionNode too.
struct UnionNode : public Tail {
// ...
template<typename U> struct inUnion {
// Q: where to add typename/template here?
typedef Tail::inUnion<U> dummy;
};
template< > struct inUnion<T> {
};
};
template <typename T> // For the last node Tn.
struct UnionNode<T, void> {
// ...
template<typename U> struct inUnion {
char fail[ -2 + (sizeof(U)%2) ]; // Cannot be instantiated for any U
};
template< > struct inUnion<T> {
};
};
O problema que tenho está na typedef Tail::inUnion<U> dummy
linha. Estou bastante certo de que esse inUnion
é um nome dependente, e o VC ++ está certo em se engasgar com ele.
Também sei que devo poder adicionar um template
lugar para informar ao compilador que inUnion é um ID de modelo. Mas onde exatamente? E deveria assumir que inUnion é um modelo de classe, ou seja, inUnion<U>
nomeia um tipo e não uma função?
Respostas:
(Veja aqui também a minha resposta em C ++ 11 )
Para analisar um programa C ++, o compilador precisa saber se determinados nomes são do tipo ou não. O exemplo a seguir demonstra que:
Como isso deve ser analisado? Para muitos idiomas, um compilador não precisa saber o significado de um nome para analisar e basicamente saber qual ação uma linha de código faz. No C ++, no entanto, o acima pode produzir interpretações muito diferentes, dependendo do que
t
significa. Se for um tipo, será uma declaração de um ponteirof
. No entanto, se não for um tipo, será uma multiplicação. Portanto, o Padrão C ++ diz no parágrafo (3/7):Como o compilador descobrirá a que nome
t::x
se refere, se set
refere a um parâmetro de tipo de modelo?x
poderia ser um membro de dados int estático que poderia ser multiplicado ou poderia igualmente ser uma classe ou typedef aninhada que poderia render uma declaração. Se um nome tiver essa propriedade - que não pode ser procurada até que os argumentos reais do modelo sejam conhecidos -, será chamado de nome dependente ("depende" dos parâmetros do modelo).Você pode recomendar apenas esperar até o usuário instanciar o modelo:
Isso funcionará e, na verdade, é permitido pelo Padrão como uma possível abordagem de implementação. Esses compiladores basicamente copiam o texto do modelo em um buffer interno e somente quando é necessária uma instanciação, eles analisam o modelo e possivelmente detectam erros na definição. Mas, em vez de incomodar os usuários do modelo (colegas pobres!) Com erros cometidos pelo autor de um modelo, outras implementações optam por verificar modelos desde o início e fornecer erros na definição o mais rápido possível, antes que uma instanciação ocorra.
Portanto, deve haver uma maneira de dizer ao compilador que certos nomes são tipos e que certos nomes não são.
A palavra-chave "typename"
A resposta é: Decidimos como o compilador deve analisar isso. Se
t::x
é um nome dependente, precisamos prefixá-lotypename
para que o compilador o analise de uma certa maneira. O Padrão diz em (14.6 / 2):Existem muitos nomes para os quais
typename
não é necessário, porque o compilador pode, com a pesquisa de nome aplicável na definição de modelo, descobrir como analisar uma construção em si - por exemploT *f;
, quandoT
é um parâmetro de modelo de tipo. Mas parat::x * f;
ser uma declaração, deve ser escrita comotypename t::x *f;
. Se você omitir a palavra-chave e o nome for considerado não-tipo, mas quando a instanciação encontrar, denota um tipo, as mensagens de erro comuns serão emitidas pelo compilador. Às vezes, o erro é dado no momento da definição:A sintaxe permite
typename
apenas antes dos nomes qualificados - portanto, é garantido que nomes não qualificados sempre são conhecidos por se referirem a tipos, se o fizerem.Existe uma pegadinha semelhante para nomes que denotam modelos, conforme sugerido pelo texto introdutório.
A palavra-chave "modelo"
Lembre-se da cotação inicial acima e como a Norma também exige tratamento especial para modelos? Vamos dar o seguinte exemplo de aparência inocente:
Pode parecer óbvio para um leitor humano. Não é assim para o compilador. Imagine a seguinte definição arbitrária de
boost::function
ef
:Essa é realmente uma expressão válida ! Ele utiliza o operador menor do que para comparar
boost::function
contra zero (int()
), e em seguida utiliza o operador maior do que para comparar o resultantebool
contraf
. No entanto, como você bem sabe,boost::function
na vida real é um modelo, então o compilador sabe (14.2 / 3):Agora estamos de volta ao mesmo problema que com
typename
. E se ainda não soubermos se o nome é um modelo ao analisar o código? Precisamos inserirtemplate
imediatamente antes do nome do modelo, conforme especificado por14.2/4
. Isso se parece com:Os nomes de modelos não podem ocorrer apenas após um,
::
mas também após um->
ou.
em um acesso de membro da classe. Você precisa inserir a palavra-chave também:Dependências
Para as pessoas que têm livros padronizados grossos em suas prateleiras e que querem saber exatamente do que eu estava falando, falarei um pouco sobre como isso é especificado no Padrão.
Nas declarações de modelo, algumas construções têm significados diferentes, dependendo dos argumentos de modelo que você usa para instanciar o modelo: Expressões podem ter tipos ou valores diferentes, variáveis podem ter tipos diferentes ou chamadas de função podem acabar chamando funções diferentes. Diz-se que essas construções geralmente dependem dos parâmetros do modelo.
A Norma define precisamente as regras, independentemente de uma construção ser dependente ou não. Ele os separa em grupos logicamente diferentes: um captura tipos, outro captura expressões. As expressões podem depender de seu valor e / ou tipo. Então, temos, com exemplos típicos anexados:
T
)N
)(T)0
)A maioria das regras é intuitiva e criada recursivamente: Por exemplo, um tipo construído como
T[N]
um tipo dependente seN
for uma expressão dependente de valor ouT
for um tipo dependente. Os detalhes disso podem ser lidos na seção(14.6.2/1
) para tipos dependentes,(14.6.2.2)
expressões dependentes de tipo e expressões dependentes(14.6.2.3)
de valor.Nomes dependentes
O Padrão não é um pouco claro sobre o que exatamente é um nome dependente . Em uma leitura simples (você sabe, o princípio da menor surpresa), tudo o que define como um nome dependente é o caso especial dos nomes das funções abaixo. Mas como claramente
T::x
também precisa ser pesquisado no contexto da instanciação, ele também precisa ser um nome dependente (felizmente, a partir de meados do C ++ 14, o comitê começou a estudar como corrigir essa definição confusa).Para evitar esse problema, recorri a uma interpretação simples do texto padrão. De todas as construções que denotam tipos ou expressões dependentes, um subconjunto delas representa nomes. Esses nomes são, portanto, "nomes dependentes". Um nome pode assumir diferentes formas - o Padrão diz:
Um identificador é apenas uma sequência simples de caracteres / dígitos, enquanto os próximos dois são a forma
operator +
eoperator type
. A última forma étemplate-name <argument list>
. Todos esses são nomes e, pelo uso convencional no Padrão, um nome também pode incluir qualificadores que dizem em qual namespace ou classe um nome deve ser pesquisado.Uma expressão dependente de valor
1 + N
não é um nome, masN
é. O subconjunto de todas as construções dependentes que são nomes é chamado nome dependente . Os nomes das funções, no entanto, podem ter significado diferente nas instâncias diferentes de um modelo, mas infelizmente não são capturados por esta regra geral.Nomes de funções dependentes
Não é principalmente uma preocupação deste artigo, mas ainda vale a pena mencionar: Os nomes das funções são uma exceção que são manipulados separadamente. Um nome de função de identificador é dependente não por si só, mas pelas expressões de argumento dependentes de tipo usadas em uma chamada. No exemplo
f((T)0)
,f
é um nome dependente. No padrão, isso é especificado em(14.6.2/1)
.Notas e exemplos adicionais
Em casos suficientes, precisamos de
typename
etemplate
. Seu código deve se parecer com o seguinteA palavra-chave
template
nem sempre precisa aparecer na última parte de um nome. Pode aparecer no meio antes de um nome de classe usado como escopo, como no exemplo a seguirEm alguns casos, as palavras-chave são proibidas, conforme detalhado abaixo
No nome de uma classe base dependente, você não tem permissão para escrever
typename
. Supõe-se que o nome fornecido seja um nome de tipo de classe. Isso vale para os nomes na lista de classes base e na lista de inicializadores do construtor:Nas declarações using, não é possível usar
template
depois da última::
, e o comitê C ++ disse para não trabalhar em uma solução.fonte
typename
imposta mesmo quando a sintaxe não permite interpretações alternativas além dos nomes de tipo neste momento?C ++ 11
Problema
Embora as regras em C ++ 03 sobre quando você precisa
typename
etemplate
seja bastante razoável, há uma desvantagem irritante de sua formulaçãoComo pode ser visto, precisamos da palavra-chave de desambiguação, mesmo que o compilador possa descobrir perfeitamente que
A::result_type
só pode serint
(e é, portanto, um tipo) ethis->g
só pode ser o modelo de membrog
declarado posteriormente (mesmo queA
seja explicitamente especializado em algum lugar, isso seria não afeta o código dentro desse modelo; portanto, seu significado não pode ser afetado por uma especialização posterior deA
!).Instanciação atual
Para melhorar a situação, no C ++ 11 a linguagem rastreia quando um tipo se refere ao modelo anexo. Para saber que, o tipo deve ter sido formado usando uma certa forma de nome, que é o seu próprio nome (no exemplo acima,
A
,A<T>
,::A<T>
). Um tipo referenciado por esse nome é conhecido como a instanciação atual . Pode haver vários tipos que são toda a instanciação atual se o tipo a partir do qual o nome é formado for uma classe membro / aninhada (então,A::NestedClass
eA
são instanciações atuais).Com base nessa noção, a linguagem diz que
CurrentInstantiation::Foo
,Foo
eCurrentInstantiationTyped->Foo
(comoA *a = this; a->Foo
), todos são membros da instanciação atual se forem membros de uma classe que é a instanciação atual ou uma de suas classes base não dependentes (apenas fazendo procure o nome imediatamente).As palavras
typename
- chave etemplate
agora não são mais necessárias se o qualificador for um membro da instanciação atual. Um ponto-chave aqui para lembrar é que aindaA<T>
é um nome dependente de tipo (afinal de contas também é dependente de tipo). Mas é conhecido por ser um tipo - o compilador analisará "magicamente" esse tipo de tipos dependentes para descobrir isso.T
A<T>::result_type
Isso é impressionante, mas podemos fazer melhor? A linguagem vai ainda mais longe e exige que uma implementação procure novamente
D::result_type
ao instanciarD::f
(mesmo que já tenha encontrado seu significado no momento da definição). Quando agora o resultado da pesquisa difere ou resulta em ambiguidade, o programa está mal formado e um diagnóstico deve ser fornecido. Imagine o que acontece se definimosC
assimÉ necessário um compilador para detectar o erro ao instanciar
D<int>::f
. Assim, você obtém o melhor dos dois mundos: pesquisa "atrasada", protegendo-o se você pudesse ter problemas com classes base dependentes, e também pesquisa "imediata", que o libertatypename
etemplate
.Especializações desconhecidas
No código de
D
, o nometypename D::questionable_type
não é um membro da instanciação atual. Em vez disso, o idioma o marca como membro de uma especialização desconhecida . Em particular, esse sempre é o caso quando você está realizandoDependentTypeName::Foo
ouDependentTypedName->Foo
o tipo dependente não é a instanciação atual (nesse caso, o compilador pode desistir e dizer "veremos mais adiante o queFoo
é)" ou é a instanciação atual e a nome não foi encontrado nele ou em suas classes base não dependentes e também existem classes base dependentes.Imagine o que acontece se tivéssemos uma função de membro
h
dentro doA
modelo de classe definido acimaNo C ++ 03, a linguagem permitiu capturar esse erro, porque nunca havia uma maneira válida de instanciar
A<T>::h
(qualquer argumento que você fornecerT
). No C ++ 11, o idioma agora tem uma verificação adicional para fornecer mais motivos para os compiladores implementarem essa regra. ComoA
não possui classes base dependentes eA
declara nenhum membroquestionable_type
, o nome nãoA<T>::questionable_type
é membro da instanciação atual nemum membro de uma especialização desconhecida. Nesse caso, não deve haver como esse código compilar validamente no momento da instanciação; portanto, o idioma proíbe que um nome em que o qualificador seja a instanciação atual não seja membro de uma especialização desconhecida nem membro da instanciação atual (no entanto , essa violação ainda não precisa ser diagnosticada).Exemplos e curiosidades
Você pode tentar esse conhecimento nesta resposta e ver se as definições acima fazem sentido para você em um exemplo do mundo real (elas são repetidas um pouco menos detalhadas nessa resposta).
As regras do C ++ 11 tornam o seguinte código C ++ 03 válido incorreto (que não foi planejado pelo comitê do C ++, mas provavelmente não será corrigido)
Este código C ++ 03 válido ligaria
this->f
paraA::f
em tempo de instanciação e está tudo bem. No entanto, o C ++ 11 o vincula imediatamenteB::f
e requer uma verificação dupla ao instanciar, verificando se a pesquisa ainda corresponde. No entanto, ao instanciarC<A>::g
, a regra de dominância se aplica e a pesquisa será encontradaA::f
.fonte
Qual é o propósito
typename
etemplate
?typename
etemplate
são utilizáveis em circunstâncias diferentes da declaração de um modelo.Existem certos contextos em C ++ em que o compilador deve ser explicitamente informado sobre como tratar um nome e todos esses contextos têm uma coisa em comum; eles dependem de pelo menos um parâmetro de modelo .
Nós nos referimos a esses nomes, onde pode haver uma ambiguidade na interpretação, como; " nomes dependentes ".
Esta postagem oferecerá uma explicação para o relacionamento entre nomes dependentes e as duas palavras-chave.
UM SNIPPET DIZ MAIS DE 1000 PALAVRAS
Tente explicar o que está acontecendo no modelo de função a seguir , seja para você mesmo, um amigo ou talvez seu gato; o que está acontecendo na declaração marcada ( A )?
Pode não ser tão fácil quanto se pensa, mais especificamente o resultado da avaliação ( A ) depende muito da definição do tipo passado como parâmetro-modelo
T
.Diferentes
T
s podem mudar drasticamente a semântica envolvida.Os dois cenários diferentes :
Se instanciamos o modelo de função com o tipo X , como em ( C ), teremos uma declaração de um ponteiro para int chamado x , mas;
se instanciarmos o modelo com o tipo Y , como em ( D ), ( A ) consistiria em uma expressão que calcula o produto de 123 multiplicado por alguma variável já declarada x .
O JUSTIFICATIVA
O padrão C ++ se preocupa com a nossa segurança e bem-estar, pelo menos nesse caso.
Para evitar uma implementação de potencialmente sofrendo de surpresas desagradáveis, os mandatos padrão que nós resolver a ambigüidade de um nome-dependente por explicitamente indicando o lugar intenção gostaríamos de tratar o nome ou como um nome do tipo , ou um template- id .
Se nada for declarado, o nome do dependente será considerado uma variável ou uma função.
COMO LIDAR COM NOMES DEPENDENTES ?
Se fosse um filme de Hollywood, os nomes dependentes seriam a doença que se propaga pelo contato corporal, afetando instantaneamente seu anfitrião, deixando-o confuso. Confusão que poderia, possivelmente, levar a um programa mal formado de perso-, erhm ...
Um nome dependente é qualquer nome que dependa direta ou indiretamente de um parâmetro de modelo .
Temos quatro nomes dependentes no snippet acima:
SomeTrait<T>
, que incluiT
, e;SomeTrait<T>
e;SomeTrait<T>
e;SomeTrait<T>
.Nenhuma das instruções ( E ), ( F ) ou ( G ) é válida se o compilador interpretar os nomes dependentes como variáveis / funções (que, como afirmado anteriormente, é o que acontece se não dissermos explicitamente o contrário).
A SOLUÇÃO
Para
g_tmpl
ter uma definição válida, devemos dizer explicitamente ao compilador que esperamos um tipo em ( E ), uma identificação de modelo e um tipo em ( F ) e uma identificação de modelo em ( G ).Sempre que um nome denota um tipo, todos os nomes envolvidos devem ser nomes de tipos ou espaços de nomes , com isso em mente, é bastante fácil ver que aplicamos
typename
no início de nosso nome completo .template
no entanto, é diferente a esse respeito, pois não há como chegar a uma conclusão como; "oh, isso é um modelo, então essa outra coisa também deve ser um modelo" . Isso significa que aplicamostemplate
diretamente na frente de qualquer nome que gostaríamos de tratar como tal.POSSO ADICIONAR AS PALAVRAS-CHAVE EM FRENTE A QUALQUER NOME?
As regras do Padrão afirmam que você pode aplicar as palavras-chave enquanto estiver lidando com um nome qualificado ( K ), mas se o nome não for qualificado, o aplicativo estará mal formado ( L ).
Nota : A aplicação
typename
outemplate
em um contexto em que não é obrigatório não é considerada uma boa prática; só porque você pode fazer algo, não significa que você deveria.Além disso, há contextos em que
typename
e explicitamente nãotemplate
são permitidos:Ao especificar as bases das quais uma classe herda
Todo nome escrito na lista de especificadores de base de uma classe derivada já é tratado como um nome de tipo , especificando explicitamente
typename
é mal formado e redundante.Quando o ID do modelo é o que está sendo referido na diretiva de uso de uma classe derivada
fonte
No entanto, não sei se a implementação do inUnion está correta. Se bem entendi, essa classe não deve ser instanciada; portanto, a guia "falha" nunca avtually falha. Talvez seja melhor indicar se o tipo está na união ou não com um valor booleano simples.
PS: Dê uma olhada no Boost :: Variant
PS2: Dê uma olhada nas listas de tipos , especialmente no livro de Andrei Alexandrescu: Modern C ++ Design
fonte
Esta resposta deve ser bastante curta e agradável para responder (parte da) pergunta do título. Se você quiser uma resposta com mais detalhes que explique por que você deve colocá-las lá, acesse aqui .
A regra geral para colocar a
typename
palavra-chave é principalmente quando você está usando um parâmetro de modelo e deseja acessar umtypedef
alias aninhado ou usando, por exemplo:Observe que isso também se aplica a meta-funções ou itens que também usam parâmetros genéricos de modelo. No entanto, se o parâmetro de modelo fornecido for um tipo explícito, você não precisará especificar
typename
, por exemplo:As regras gerais para adicionar o
template
qualificador são principalmente semelhantes, exceto que normalmente envolvem funções de membro modeladas (estáticas ou não) de uma estrutura / classe que é modelada, por exemplo:Dada essa estrutura e função:
Tentar acessar
t.get<int>()
de dentro da função resultará em um erro:Assim, nesse contexto, você precisaria da
template
palavra - chave com antecedência e a chamaria assim:t.template get<int>()
Dessa forma, o compilador analisará isso corretamente e não
t.get < int
.fonte
Estou colocando a excelente resposta de JLBorges a uma pergunta semelhante, literalmente, em cplusplus.com, pois é a explicação mais sucinta que li sobre o assunto.
Sumário
Use a palavra-chave typename apenas nas declarações e definições de modelo, desde que você tenha um nome qualificado que se refira a um tipo e dependa de um parâmetro de modelo.
fonte