template e typename em C++: onde colocar e por que não dá para viver sem eles
\n\nSe você já escreveu ou leu código C++ com templates, certamente já se deparou com uma situação em que o compilador, do nada, apresenta um erro. Você tem certeza de que o código está correto, mas o compilador insiste em exigir template ou typename. Familiar? Bem-vindo ao clube.
Esta é uma das perguntas mais populares no Stack Overflow (milhares de visualizações e dezenas de respostas). E não é surpresa: até desenvolvedores experientes às vezes se confundem sobre onde usar essas palavras-chave. Neste artigo, vamos entender de uma vez por todas: onde, quando e por que escrever template e typename em C++. E o mais importante: entenderemos como não cair em armadilhas.
Por que esse problema surgiu?
\n\nA raiz do problema está nos nomes dependentes (dependent names). Quando você escreve código com template, o compilador não consegue entender imediatamente com o que está lidando: se é um tipo ou outra coisa. Isso porque os parâmetros do template ainda não são conhecidos. O compilador adia a análise até o momento da instanciação, mas precisa saber o papel sintático do nome já na fase de análise inicial.
\n\nImagine que você escreve:
\n\ntemplate<typename T>\nvoid foo() {\n T::something * p;\n}\n\n\nO que é isso? Uma declaração do ponteiro p para T::something? Ou uma multiplicação de T::something por p? Sem informações adicionais, o compilador não sabe. E é exatamente para resolver essa ambiguidade que as palavras-chave typename e template foram introduzidas.
A palavra-chave typename: quando o tipo não é óbvio
\n\nRegra principal
\n\nA palavra-chave typename é usada para dizer ao compilador: "Ei, o que vem a seguir é um tipo, e não outra coisa". Ela é necessária nos casos em que o nome depende de um parâmetro do template e o compilador não consegue determinar por conta própria que se trata de um tipo.
Vejamos um exemplo clássico:
\n\ntemplate<typename T>\nclass MyClass {\npublic:\n // Sem typename — erro de compilação\n typename T::value_type getValue() const {\n return data;\n }\nprivate:\n T data;\n};\n\n\nAqui, T::value_type é um nome dependente. Sem typename, o compilador pensaria que value_type é um membro de dados estático ou uma classe aninhada, mas não um tipo. E nós sabemos que é um tipo (por exemplo, int, se T for std::vector<int>).
Onde mais o typename é necessário?
\n\nA regra é simples: typename é obrigatório antes de nomes de tipos dependentes nos seguintes contextos:
- \n
- Em declarações de variáveis e membros de classe \n
- Em parâmetros de funções \n
- Em tipos de retorno \n
- Em declarações using (apelidos de tipos) \n
- Em classes base (se a classe base for um tipo dependente) \n
Exemplo com using:
\n\ntemplate<typename T>\nclass Container {\n using Iterator = typename T::iterator; // typename é obrigatório!\n // ...\n};\n\n\nQuando o typename NÃO é necessário?
\n\nExistem exceções importantes em que typename não é necessário:
- \n
- Na lista de parâmetros do template:
template<typename T>— aquitypenamefaz parte da sintaxe \n - Em classes base:
class Derived : public Base<T>::Nested— aquitypenamenão é necessário \n - Na lista de inicialização do construtor \n
- Em contextos onde o nome é explicitamente um tipo (por exemplo, após
new) \n
A palavra-chave template: quando o template está dentro do template
\n\nA essência do problema
\n\nCom template a situação é espelhada. Ela é necessária quando você acessa um membro que é um template den