typenames e templates
Manipulação de tipos em C++
Há uma certa dificuldade para os programadores iniciantes na área de templates que se aventuram a manipular tipos. A primeira delas é quanto ao uso da palavra reservada typename. Essa palavra deve ser utilizada para informar ao compilador que o nome que vem em seguida é um tipo, e não um membro de uma estrutura ou uma variável. No código abaixo podemos ver isso. Esse é um exemplo onde criamos uma função que recebe um vetor e calcula a soma dos elementos desse vetor.
#include <cstdlib>
#include <iostream>
#include <vector>
using namespace std;
template <class V>
V soma( const vector<V>& v ) {
V aux = 0;
for( typename vector<V>::const_iterator i = v.begin(); i != v.end(); ++i )
aux += *i;
return aux;
}
int main(int argc, char *argv[])
{
vector<double> v;
v.push_back( 1.1 );
v.push_back( 2.1 );
v.push_back( 3.1 );
cout << soma( v ) << endl;
system("PAUSE");
return EXIT_SUCCESS;
}
Sem a palavra typename na linha 11 temos as seguintes mensagens de erro:
main.cpp: In function `V soma(const std::vector<V, std::allocator<_CharT> >&)': main.cpp:11: error: expected `;' before "i" main.cpp:11: error: `i' undeclared (first use this function) main.cpp:11: error: (Each undeclared identifier is reported only once for each function it appears in.) main.cpp: In function `V soma(const std::vector<V, std::allocator<_CharT> >&) [with V = double]': main.cpp:25: instantiated from here main.cpp:11: error: dependent-name ` std::vector<V,std::allocator<_CharT> >::const_iterator' is parsed as a non-type, but instantiation yields a type main.cpp:11: note: say `typename std::vector<V,std::allocator<_CharT> >::const_iterator' if a type is meant
Ocorre que o compilador assume que const_iterator é um nome de um membro (variável ou método) da classe vector<V>, e não um nome de tipo (nesse caso específico, um tipo definido através de um typedef). De uma forma geral, toda expressão envolvendo um parâmetro da template (no exemplo, o V) onde ocorre acesso à um campo de uma estrutura (uso do “::”) será encarado pelo compilador não como um tipo mas sim como um membro dessa estrutura. Se quisermos que o compilador veja ali um tipo devemos preceder a declaração com a palavra reservada typename. Pelo menos o compilador nos dá uma boa dica do problema na mensagem de erro nº 8: ”note: say `typename std::vector<V,std::allocator<_CharT> >::const_iterator’ if a type is meant“.
Note que no fonte, na linha 9 temos a declaração da variável aux sem a necessidade do typename, pois nesse caso o compilador assume que V é um tipo.
Lidando com tipos internos
Porém há situações onde alguns tipos são informados como typedefs internos à uma estrutura (classe). É o caso de todos os containers da stl, que possuem o tipo value_type definido como o tipo do objeto que o container armazena. Essa informação é útil para a construção de rotinas mais genéricas, onde não se sabe o tipo do container. A função soma do exemplo acima lida apenas com vectors. Mas ela pode ser redefinida para lidar com qualquer classe container (ou compatível) da seguinte forma: ao invés de receber o parâmetro const vector<V>& v ela passa a receber const V& v. O problema é que nesse caso não sabemos o tipo do elemento armazenado no container. É para isso que serve o tipo value_type. E aí, dessa vez precisaremos usar typename antes de V::value_type.
#include <cstdlib>
#include <iostream>
#include <vector>
using namespace std;
template <class V>
typename V::value_type soma( const V& v ) {
typename V::value_type aux = 0;
for( typename V::const_iterator i = v.begin(); i != v.end(); ++i )
aux += *i;
return aux;
}
int main(int argc, char *argv[])
{
vector<double> v;
v.push_back( 1.1 );
v.push_back( 2.1 );
v.push_back( 3.1 );
cout << soma( v ) << endl;
system("PAUSE");
return EXIT_SUCCESS;
}
Na linha 8 temos o tipo de retorno da função: V::value_type, devidamente precedido por typename para informar ao compilador que se trata de um tipo (de modo análogo na linha 9 temos a declaração da variável aux).
“Hasta la vista, baby!”