Onde e por que tenho que colocar as palavras-chave "modelo" e "nome do tipo"?

1126

Em modelos, onde e por que eu tenho que colocar typenamee templatesobre 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> dummylinha. 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 templatelugar 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?

MSalters
fonte
1
Pergunta irritante: por que não impulsionar :: Variant?
Assaf Lavie
58
Sensibilidades políticas, portabilidade.
MSalters
5
Fiz sua pergunta real ("Onde colocar o modelo / nome do tipo?") Se destacar melhor colocando a pergunta e o código finais no início e encurtando o código horizontalmente para caber em uma tela de 1024x.
Johannes Schaub - litb
7
Removidos os "nomes dependentes" do título, porque parece que a maioria das pessoas que se pergunta sobre "nome do tipo" e "modelo" não sabe o que são "nomes dependentes". Deveria ser menos confuso para eles dessa maneira.
Johannes Schaub - litb 29/10
2
@ MSalters: impulso é bastante portátil. Eu diria que apenas a política é a razão geral pela qual o impulso geralmente não está envolvido. A única boa razão que conheço é o aumento do tempo de compilação. Caso contrário, isso significa perder milhares de dólares reinventando a roda.
v.oddou

Respostas:

1164

(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:

t * f;

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 tsignifica. Se for um tipo, será uma declaração de um ponteiro f. No entanto, se não for um tipo, será uma multiplicação. Portanto, o Padrão C ++ diz no parágrafo (3/7):

Alguns nomes indicam tipos ou modelos. Em geral, sempre que um nome é encontrado, é necessário determinar se esse nome denota uma dessas entidades antes de continuar analisando o programa que o contém. O processo que determina isso é chamado de pesquisa de nome.

Como o compilador descobrirá a que nome t::xse refere, se se trefere a um parâmetro de tipo de modelo? xpoderia 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:

Vamos esperar até o usuário instanciar o modelo e depois descobrir o real significado de t::x * f;.

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á-lo typenamepara que o compilador o analise de uma certa maneira. O Padrão diz em (14.6 / 2):

É assumido que um nome usado em uma declaração ou definição de modelo e dependente de um parâmetro de modelo não cite um tipo, a menos que a pesquisa de nome aplicável encontre um nome de tipo ou o nome seja qualificado pela palavra-chave typename.

Existem muitos nomes para os quais typenamenã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 exemplo T *f;, quando Té um parâmetro de modelo de tipo. Mas para t::x * f;ser uma declaração, deve ser escrita como typename 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:

// t::x is taken as non-type, but as an expression the following misses an
// operator between the two names or a semicolon separating them.
t::x f;

A sintaxe permite typenameapenas 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:

boost::function< int() > f;

Pode parecer óbvio para um leitor humano. Não é assim para o compilador. Imagine a seguinte definição arbitrária de boost::functione f:

namespace boost { int function = 0; }
int main() { 
  int f = 0;
  boost::function< int() > f; 
}

Essa é realmente uma expressão válida ! Ele utiliza o operador menor do que para comparar boost::functioncontra zero ( int()), e em seguida utiliza o operador maior do que para comparar o resultante boolcontra f. No entanto, como você bem sabe, boost::function na vida real é um modelo, então o compilador sabe (14.2 / 3):

Depois que a pesquisa de nome (3.4) descobre que um nome é um nome de modelo, se esse nome é seguido por um <, o <é sempre usado como o início de uma lista de argumentos de modelo e nunca como um nome seguido por menos- que operador.

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 inserir templateimediatamente antes do nome do modelo, conforme especificado por 14.2/4. Isso se parece com:

t::template f<int>(); // call a function template

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:

this->template f<int>(); // call a function template

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:

  • Tipos dependentes (por exemplo: um parâmetro de modelo de tipo T)
  • Expressões dependentes de valor (por exemplo: um parâmetro de modelo que não seja do tipo N)
  • Expressões dependentes de tipo (por exemplo: uma conversão para um parâmetro de modelo de tipo (T)0)

A maioria das regras é intuitiva e criada recursivamente: Por exemplo, um tipo construído como T[N]um tipo dependente se Nfor uma expressão dependente de valor ou Tfor 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::xtambé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 nome é o uso de um identificador (2.11), ID da função do operador (13.5), ID da função de conversão (12.3.2) ou ID do modelo (14.2) que denota uma entidade ou rótulo (6.6.4, 6.1)

Um identificador é apenas uma sequência simples de caracteres / dígitos, enquanto os próximos dois são a forma operator +e operator 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 + Nnão é um nome, mas Né. 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 typenamee template. Seu código deve se parecer com o seguinte

template <typename T, typename Tail>
struct UnionNode : public Tail {
    // ...
    template<typename U> struct inUnion {
        typedef typename Tail::template inUnion<U> dummy;
    };
    // ...
};

A palavra-chave templatenem 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 seguir

typename t::template iterator<int>::value_type v;

Em 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:

     template <typename T>
     struct derive_from_Has_type : /* typename */ SomeBase<T>::type 
     { };
  • Nas declarações using, não é possível usar templatedepois da última ::, e o comitê C ++ disse para não trabalhar em uma solução.

     template <typename T>
     struct derive_from_Has_type : SomeBase<T> {
        using SomeBase<T>::template type; // error
        using typename SomeBase<T>::type; // typename *is* allowed
     };
Johannes Schaub - litb
fonte
22
Essa resposta foi copiada da minha entrada anterior da FAQ, que eu removi, porque achei que deveria usar melhor as perguntas semelhantes existentes, em vez de criar novas "pseudo-perguntas", apenas com a finalidade de respondê-las. Agradecemos a @Prasoon , que editou as idéias da última parte (casos em que o nome do modelo / modelo é proibido) na resposta.
Johannes Schaub - litb
1
Você pode me ajudar quando devo usar essa sintaxe? este-> modelo f <int> (); Eu recebo esse erro 'modelo' (como um desambiguador) só é permitido em modelos, mas sem a palavra-chave modelo, ele funciona bem.
22411
1
Fiz uma pergunta semelhante hoje, que logo foi marcada como duplicada: stackoverflow.com/questions/27923722/… . Fui instruído a reviver essa pergunta em vez de criar uma nova. Devo dizer que não concordo que eles sejam duplicados, mas quem sou eu, certo? Portanto, existe alguma razão typenameimposta mesmo quando a sintaxe não permite interpretações alternativas além dos nomes de tipo neste momento?
precisa saber é o seguinte
1
@ Pablo você não está perdendo nada. Mas ainda é necessário escrever a desambiguação, mesmo que a linha completa não seja mais ambígua.
Johannes Schaub - litb 6/09/18
1
@ Pablo, o objetivo é manter o idioma e os compiladores mais simples. Existem propostas para permitir que mais situações descubram automaticamente as coisas, para que você precise da palavra-chave com menos frequência. Observe que no seu exemplo, o token é ambíguo e somente depois de ver ">" depois do dobro, você pode desambiguar como um colchete angular do modelo. Para mais detalhes, sou a pessoa errada a perguntar, porque não tenho experiência na implementação de um analisador de compiladores C ++.
Johannes Schaub - litb 7/09/18
136

C ++ 11

Problema

Embora as regras em C ++ 03 sobre quando você precisa typenamee templateseja bastante razoável, há uma desvantagem irritante de sua formulação

template<typename T>
struct A {
  typedef int result_type;

  void f() {
    // error, "this" is dependent, "template" keyword needed
    this->g<float>();

    // OK
    g<float>();

    // error, "A<T>" is dependent, "typename" keyword needed
    A<T>::result_type n1;

    // OK
    result_type n2; 
  }

  template<typename U>
  void g();
};

Como pode ser visto, precisamos da palavra-chave de desambiguação, mesmo que o compilador possa descobrir perfeitamente que A::result_typesó pode ser int(e é, portanto, um tipo) e this->gsó pode ser o modelo de membro gdeclarado posteriormente (mesmo que Aseja 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 de A!).

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::NestedClasse Asão instanciações atuais).

Com base nessa noção, a linguagem diz que CurrentInstantiation::Foo, Fooe CurrentInstantiationTyped->Foo(como A *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 e templateagora 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.TA<T>::result_type

struct B {
  typedef int result_type;
};

template<typename T>
struct C { }; // could be specialized!

template<typename T>
struct D : B, C<T> {
  void f() {
    // OK, member of current instantiation!
    // A::result_type is not dependent: int
    D::result_type r1;

    // error, not a member of the current instantiation
    D::questionable_type r2;

    // OK for now - relying on C<T> to provide it
    // But not a member of the current instantiation
    typename D::questionable_type r3;        
  }
};

Isso é impressionante, mas podemos fazer melhor? A linguagem vai ainda mais longe e exige que uma implementação procure novamente D::result_typeao instanciar D::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 definimos Cassim

template<>
struct C<int> {
  typedef bool result_type;
  typedef int questionable_type;
};

É 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 liberta typenamee template.

Especializações desconhecidas

No código de D, o nome typename D::questionable_typenã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á realizando DependentTypeName::Fooou DependentTypedName->Fooo tipo dependente não é a instanciação atual (nesse caso, o compilador pode desistir e dizer "veremos mais adiante o que Fooé)" 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 hdentro do Amodelo de classe definido acima

void h() {
  typename A<T>::questionable_type x;
}

No C ++ 03, a linguagem permitiu capturar esse erro, porque nunca havia uma maneira válida de instanciar A<T>::h(qualquer argumento que você fornecer T). No C ++ 11, o idioma agora tem uma verificação adicional para fornecer mais motivos para os compiladores implementarem essa regra. Como Anão possui classes base dependentes e Adeclara nenhum membro questionable_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)

struct B { void f(); };
struct A : virtual B { void f(); };

template<typename T>
struct C : virtual B, T {
  void g() { this->f(); }
};

int main() { 
  C<A> c; c.g(); 
}

Este código C ++ 03 válido ligaria this->fpara A::fem tempo de instanciação e está tudo bem. No entanto, o C ++ 11 o vincula imediatamente B::fe requer uma verificação dupla ao instanciar, verificando se a pesquisa ainda corresponde. No entanto, ao instanciar C<A>::g, a regra de dominância se aplica e a pesquisa será encontrada A::f.

Johannes Schaub - litb
fonte
fyi - esta resposta é referenciada aqui: stackoverflow.com/questions/56411114/… Grande parte do código nesta resposta não compila em vários compiladores.
Adam Rackis 01/06/19
O @AdamRackis assumindo que a especificação C ++ não mudou mudou desde 2013 (data em que escrevi esta resposta), então os compiladores com os quais você tentou o seu código simplesmente ainda não implementam essa característica do C ++ 11 +.
Johannes Schaub - litb 2/06/19
100

PREFÁCIO

Esta publicação pretende ser uma alternativa fácil de ler à publicação de litb .

O objetivo subjacente é o mesmo; uma explicação para "Quando?" e porque?" typenamee templatedeve ser aplicado.


Qual é o propósito typenamee template?

typenamee templatesã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 )?

template<class T> void f_tmpl () { T::foo * x; /* <-- (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 Ts podem mudar drasticamente a semântica envolvida.

struct X { typedef int       foo;       }; /* (C) --> */ f_tmpl<X> ();
struct Y { static  int const foo = 123; }; /* (D) --> */ f_tmpl<Y> ();


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 .

template<class T> void g_tmpl () {
   SomeTrait<T>::type                   foo; // (E), ill-formed
   SomeTrait<T>::NestedTrait<int>::type bar; // (F), ill-formed
   foo.data<int> ();                         // (G), ill-formed    
}

Temos quatro nomes dependentes no snippet acima:

  • E )
    • "tipo" depende da instanciação de SomeTrait<T>, que inclui T, e;
  • F )
    • "NestedTrait" , que é um ID de modelo , depende SomeTrait<T>e;
    • "type" no final de ( F ) depende de NestedTrait , que depende SomeTrait<T>e;
  • G )
    • "data" , que se parece com um modelo de função de membro , é indiretamente um nome dependente, pois o tipo de foo depende da instanciação de 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_tmplter 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 ).

template<class T> void g_tmpl () {
   typename SomeTrait<T>::type foo;                            // (G), legal
   typename SomeTrait<T>::template NestedTrait<int>::type bar; // (H), legal
   foo.template data<int> ();                                  // (I), legal
}

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 typenameno início de nosso nome completo .

templateno 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 aplicamos templatediretamente na frente de qualquer nome que gostaríamos de tratar como tal.



POSSO ADICIONAR AS PALAVRAS-CHAVE EM FRENTE A QUALQUER NOME?

" Can I ater apenas typenamee templatena frente de qualquer nome que não quer se preocupar com o contexto em que eles aparecem ...? " -Some C++ Developer

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 ).

namespace N {
  template<class T>
  struct X { };
}

         N::         X<int> a; // ...  legal
typename N::template X<int> b; // (K), legal
typename template    X<int> c; // (L), ill-formed

Nota : A aplicação typenameou templateem 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 typenamee explicitamente não templatesã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.

                       // .------- the base-specifier-list
     template<class T> // v
     struct Derived      : typename SomeTrait<T>::type /* <- ill-formed */ {
       ...
     };


  • Quando o ID do modelo é o que está sendo referido na diretiva de uso de uma classe derivada

     struct Base {
       template<class T>
       struct type { };
     };
    
     struct Derived : Base {
       using Base::template type; // ill-formed
       using Base::type;          // legal
     };
Filip Roséen - refp
fonte
20
typedef typename Tail::inUnion<U> dummy;

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.

template <typename T, typename TypeList> struct Contains;

template <typename T, typename Head, typename Tail>
struct Contains<T, UnionNode<Head, Tail> >
{
    enum { result = Contains<T, Tail>::result };
};

template <typename T, typename Tail>
struct Contains<T, UnionNode<T, Tail> >
{
    enum { result = true };
};

template <typename T>
struct Contains<T, void>
{
    enum { result = false };
};

PS: Dê uma olhada no Boost :: Variant

PS2: Dê uma olhada nas listas de tipos , especialmente no livro de Andrei Alexandrescu: Modern C ++ Design

Luc Touraille
fonte
inUnion <U> seria instanciado, se você tentasse, por exemplo, chamar Union <float, bool> :: operator = (U) com U == int. Chama um conjunto privado (U, inUnion <U> * = 0).
MSalters
E o trabalho com resultado = verdadeiro / falso é que eu precisaria do boost :: enable_if <>, que é incompatível com a nossa cadeia de ferramentas OSX atual. O modelo separado ainda é uma boa ideia.
MSalters
Luc significa o manequim typedef Tail :: inUnion <U>; linha. isso instanciará a cauda. mas não na União <U>. ele é instanciado quando precisa da definição completa dele. isso acontece, por exemplo, se você pegar o tamanho de ou acessar um membro (usando :: foo). @MSalters de qualquer maneira, você tem outro problema:
Johannes Schaub - litb
-sizeof (U) nunca é negativo :) porque size_t é um tipo inteiro não assinado. você receberá um número muito alto. você provavelmente quer fazer sizeof (U)> = 1? -1: 1 ou semelhante :)
Johannes Schaub - litb
eu apenas deixaria indefinido e apenas o declararia: template <typename U> struct inUnion; então certamente não pode ser instanciado. Eu acho que tê-lo com o sizeof, o compilador é permitido também para dar-lhe um erro, mesmo se você não instanciá-lo, porque se sabe sizeof (U) é sempre> = 1 e ...
Johannes Schaub - litb
20

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 typenamepalavra-chave é principalmente quando você está usando um parâmetro de modelo e deseja acessar um typedefalias aninhado ou usando, por exemplo:

template<typename T>
struct test {
    using type = T; // no typename required
    using underlying_type = typename T::type // typename required
};

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:

template<typename T>
struct test {
    // typename required
    using type = typename std::conditional<true, const T&, T&&>::type;
    // no typename required
    using integer = std::conditional<true, int, float>::type;
};

As regras gerais para adicionar o templatequalificador 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:

template<typename T>
struct test {
    template<typename U>
    void get() const {
        std::cout << "get\n";
    }
};

template<typename T>
void func(const test<T>& t) {
    t.get<int>(); // error
}

Tentar acessar t.get<int>()de dentro da função resultará em um erro:

main.cpp:13:11: error: expected primary-expression before 'int'
     t.get<int>();
           ^
main.cpp:13:11: error: expected ';' before 'int'

Assim, nesse contexto, você precisaria da templatepalavra - 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.

Rapptz
fonte
2

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.

Em um modelo que escrevemos, existem dois tipos de nomes que podem ser usados ​​- nomes dependentes e nomes não dependentes. Um nome dependente é um nome que depende de um parâmetro de modelo; um nome não dependente tem o mesmo significado, independentemente de quais são os parâmetros do modelo.

Por exemplo:

template< typename T > void foo( T& x, std::string str, int count )
{
    // these names are looked up during the second phase
    // when foo is instantiated and the type T is known
    x.size(); // dependant name (non-type)
    T::instance_count ; // dependant name (non-type)
    typename T::iterator i ; // dependant name (type)

    // during the first phase, 
    // T::instance_count is treated as a non-type (this is the default)
    // the typename keyword specifies that T::iterator is to be treated as a type.

    // these names are looked up during the first phase
    std::string::size_type s ; // non-dependant name (type)
    std::string::npos ; // non-dependant name (non-type)
    str.empty() ; // non-dependant name (non-type)
    count ; // non-dependant name (non-type)
}

O nome de um dependente pode ser diferente para cada instanciação diferente do modelo. Como conseqüência, os modelos C ++ estão sujeitos à "pesquisa de nome em duas fases". Quando um modelo é analisado inicialmente (antes de qualquer instanciação), o compilador pesquisa os nomes não dependentes. Quando uma instanciação específica do modelo ocorre, os parâmetros do modelo são conhecidos até então e o compilador procura nomes dependentes.

Durante a primeira fase, o analisador precisa saber se um nome dependente é o nome de um tipo ou o nome de um não-tipo. Por padrão, supõe-se que um nome dependente seja o nome de um não-tipo. A palavra-chave typename antes de um nome dependente especifica que é o nome de um tipo.


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.

Nikos
fonte