(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 t
significa. 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::x
se refere, se se t
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:
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 typename
para 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 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 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 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:
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::function
e 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::function
contra zero ( int()
), e em seguida utiliza o operador maior do que para comparar o resultante bool
contra 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 template
imediatamente 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 N
for uma expressão dependente de valor ou T
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 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 + N
nã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 typename
e 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 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 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 template
depois 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
};