A l’occasion, j’ai vu des messages d’erreur vraiment indéchiffrables crachés par gcc lors de l’utilisation de templates … En particulier, j’ai eu des problèmes où des déclarations apparemment correctes causaient des erreurs de compilation très étranges qui disparaissaient comme par magie avec le mot-clé “typename” le début de la déclaration … (Par exemple, la semaine dernière, je déclarais deux iterators comme membres d’une autre classe de modèles et je devais le faire) …
Quelle est l’histoire sur typename?
Voici la citation du livre de Josuttis:
Le mot-clé nom_type a été introduit pour spécifier que l’identificateur qui suit est un type. Prenons l’exemple suivant:
template
Class MyClass { typename T::SubType * ptr; ... }; Ici, typename est utilisé pour préciser que SubType est un type de classe T. Ainsi, ptr est un pointeur sur le type T :: SubType. Sans typename, SubType serait considéré comme un membre statique. Ainsi
T::SubType * ptr
serait une multiplication de la valeur SubType de type T avec ptr.
Le post BLog de Stan Lippman suggère: –
Stroustrup a réutilisé le mot-clé de classe existant pour spécifier un paramètre de type plutôt que d’introduire un nouveau mot-clé qui pourrait bien sûr briser les programmes existants. Ce n’est pas qu’un nouveau mot-clé n’a pas été pris en compte, mais simplement qu’il n’a pas été jugé nécessaire compte tenu de ses perturbations potentielles. Et jusqu’à la norme ISO-C ++, c’était la seule façon de déclarer un paramètre de type.
Donc, fondamentalement, Stroustrup mot-clé de classe réutilisé sans introduire un nouveau mot-clé qui est modifié ultérieurement dans la norme pour les raisons suivantes
Comme l’exemple donné
template class Demonstration { public: void method() { T::A *aObj; // oops … // … };
la grammaire du langage interprète mal T::A *aObj;
en tant qu’expression arithmétique, un nouveau mot-clé appelé typename
typename T::A* a6;
il demande au compilateur de traiter la déclaration suivante comme une déclaration.
Comme le mot-clé figurait sur la paie, pourquoi ne pas corriger la confusion provoquée par la décision initiale de réutiliser le mot-clé de classe.
Thats pourquoi nous avons les deux
Vous pouvez jeter un oeil à cet article , cela va certainement vous aider, je viens d’en extraire autant que possible
Considérez le code
template somefunction( T * arg ) { T::sometype x; // broken . .
Malheureusement, le compilateur n’est pas obligé d’être psychique et ne sait pas si T :: sometype finira par se référer à un nom de type ou à un membre statique de T. Donc, on utilise typename
pour lui dire:
template somefunction( T * arg ) { typename T::sometype x; // works! . .
Dans certaines situations où vous faites référence à un membre du type dit dépendant (signifiant “dépendant du paramètre template”), le compilateur ne peut pas toujours déduire sans ambiguïté la signification sémantique de la construction résultante, car il ne sait pas quel type de nom (c’est-à-dire s’il s’agit d’un nom de type, d’un nom d’un membre de données ou d’un nom d’autre). Dans de tels cas, vous devez désambiguïser la situation en indiquant explicitement au compilateur que le nom appartient à un nom de type défini en tant que membre de ce type dépendant.
Par exemple
template struct S { typename T::type i; };
Dans cet exemple, le mot-clé typename
nécessaire pour que le code comstack.
La même chose se produit lorsque vous souhaitez vous référer à un membre de modèle de type dépendant, c’est-à-dire à un nom qui désigne un modèle. Vous devez également aider le compilateur en utilisant le template
mot clé, même s’il est placé différemment
template struct S { T::template ptr p; };
Dans certains cas, il peut être nécessaire d’utiliser les deux
template struct S { typename T::template ptr::type i; };
(si j’ai la syntaxe correctement).
Bien entendu, un autre rôle du mot-clé typename
doit être utilisé dans les déclarations de parameters de modèle.
Deux utilisations:
template
class X // [1] { typename T::Y _member; // [2] }
Le secret réside dans le fait qu’un modèle peut être spécialisé pour certains types. Cela signifie qu’il peut également définir l’interface complètement différente pour plusieurs types. Par exemple, vous pouvez écrire:
template struct test { typedef T* ptr; }; template<> // complete specialization struct test { // for the case T is int T* ptr; };
On pourrait se demander pourquoi est-ce utile et en effet: cela semble vraiment inutile. Mais gardez à l’esprit que par exemple std::vector
le type de reference
est complètement différent de celui des autres T
s. Certes, cela ne change pas le type de reference
d’un type à quelque chose de différent mais néanmoins cela pourrait arriver.
Maintenant, que se passe-t-il si vous écrivez vos propres modèles en utilisant ce modèle de test
. Quelque chose comme ça
template void print(T& x) { test::ptr p = &x; std::cout << *p << std::endl; }
ça semble aller pour vous parce que vous vous attendez à ce que le test
soit un type. Mais le compilateur ne sait pas et en fait il est même conseillé par la norme d’attendre le contraire, le test
n’est pas un type. Pour indiquer au compilateur ce que vous attendez, vous devez append un nom de typename
avant. Le bon modèle ressemble à ceci
template void print(T& x) { typename test::ptr p = &x; std::cout << *p << std::endl; }
Bottom line: Vous devez append typename
avant chaque fois que vous utilisez un type nested d'un modèle dans vos modèles. (Bien sûr, si un paramètre de modèle de votre modèle est utilisé pour ce modèle interne.)
#include class A { public: typedef int my_t; }; template class B { public: // T::my_t *ptr; // It will produce compilation error typename T::my_t *ptr; // It will output 5 }; int main() { B b; int my_int = 5; b.ptr = &my_int; std::cout << *b.ptr; std::cin.ignore(); return 0; }