À l'occasion, j'ai vu des messages d'erreur vraiment indéchiffrables crachés par gcc lors de l'utilisation de modèles ... Plus précisément, j'ai eu des problèmes où des déclarations apparemment correctes provoquaient des erreurs de compilation très étranges qui ont disparu comme par magie en préfixant le mot-clé "typename" à le début de la déclaration ... (Par exemple, la semaine dernière, je déclarais deux itérateurs membres d'une autre classe basée sur des modèles et je devais le faire) ...
Quelle est l'histoire du nom de type?
Voici la citation du livre de Josuttis:
Le mot clé typename a été introduit pour spécifier que l'identifiant qui suit est un type. Prenons l'exemple suivant:
template <class T> Class MyClass { typename T::SubType * ptr; ... };
Ici, typename est utilisé pour clarifier que SubType est un type de classe T. Ainsi, ptr est un pointeur vers le type T :: SubType. Sans nom de type, SubType serait considéré comme un membre statique. Ainsi
T::SubType * ptr
serait une multiplication de la valeur SubType de type T avec ptr.
Message 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 casser 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 sa perturbation potentielle. Et jusqu'à la norme ISO-C++, c'était la seule façon de déclarer un paramètre de type.
Donc, fondamentalement, Stroustrup a réutilisé le mot-clé de classe sans introduire de nouveau mot-clé qui est modifié par la suite dans la norme pour les raisons suivantes
Comme l'exemple donné
template <class T>
class Demonstration {
public:
void method() {
T::A *aObj; // oops …
// …
};
grammaire linguistique mal interprétée T::A *aObj;
en tant qu'expression arithmétique, un nouveau mot clé est donc appelé typename
typename T::A* a6;
il demande au compilateur de traiter l'instruction suivante comme une déclaration.
Puisque le mot-clé était sur la paie, diable, pourquoi ne pas corriger la confusion causée par la décision d'origine de réutiliser le mot-clé de classe.
C'est pourquoi nous avons les deux
Vous pouvez jeter un oeil à ce post , cela vous aidera certainement, je viens d'en extraire autant que possible
Considérez le code
template<class T> somefunction( T * arg )
{
T::sometype x; // broken
.
.
Malheureusement, le compilateur n'est pas obligé d'être psychique, et ne sait pas si T :: someype finira par se référer à un nom de type ou à un membre statique de T. Donc, on utilise typename
pour le dire:
template<class T> 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 de modèle"), le compilateur ne peut pas toujours déduire sans ambiguïté la signification sémantique de la construction résultante, car elle ne le fait pas. savoir de quel type de nom il s'agit (c'est-à-dire s'il s'agit d'un nom d'un type, d'un nom d'un membre de données ou d'un autre nom). Dans de tels cas, vous devez lever l'ambiguïté en disant explicitement au compilateur que le nom appartient à un nom de type défini comme membre de ce type dépendant.
Par exemple
template <class T> struct S {
typename T::type i;
};
Dans cet exemple, le mot clé typename
est nécessaire à la compilation du code.
La même chose se produit lorsque vous souhaitez faire référence à 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 mot clé template
, bien qu'il soit placé différemment
template <class T> struct S {
T::template ptr<int> p;
};
Dans certains cas, il peut être nécessaire d'utiliser les deux
template <class T> struct S {
typename T::template ptr<int>::type i;
};
(si j'ai obtenu la syntaxe correctement).
Bien entendu, un autre rôle du mot clé typename
doit être utilisé dans les déclarations de paramètres de modèle.
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 une interface complètement différente pour plusieurs types. Par exemple, vous pouvez écrire:
template<typename T>
struct test {
typedef T* ptr;
};
template<> // complete specialization
struct test<int> { // 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<bool>
le type reference
est complètement différent de celui des autres T
s. Certes, cela ne change pas le type de reference
d'un type en quelque chose de différent, mais cela pourrait néanmoins arriver.
Maintenant, que se passe-t-il si vous écrivez vos propres modèles à l'aide de ce modèle test
. Quelque chose comme ça
template<typename T>
void print(T& x) {
test<T>::ptr p = &x;
std::cout << *p << std::endl;
}
il semble que ça va pour vous parce que vous attendre cette test<T>::ptr
est un type. Mais le compilateur ne sait pas et en fait il est même conseillé par la norme d'attendre le contraire, test<T>::ptr
n'est pas un type. Pour dire au compilateur ce que vous attendez, vous devez ajouter un typename
avant. Le modèle correct ressemble à ceci
template<typename T>
void print(T& x) {
typename test<T>::ptr p = &x;
std::cout << *p << std::endl;
}
Conclusion: vous devez ajouter typename
avant chaque fois que vous utilisez un type imbriqué de modèle dans vos modèles. (Bien sûr, uniquement si un paramètre de modèle de votre modèle est utilisé pour ce modèle interne.)
Deux usages:
template <typename T> class X // [1] { typename T::Y _member; // [2] }
#include <iostream>
class A {
public:
typedef int my_t;
};
template <class T>
class B {
public:
// T::my_t *ptr; // It will produce compilation error
typename T::my_t *ptr; // It will output 5
};
int main() {
B<A> b;
int my_int = 5;
b.ptr = &my_int;
std::cout << *b.ptr;
std::cin.ignore();
return 0;
}