web-dev-qa-db-fra.com

Où et pourquoi dois-je mettre les mots-clés "template" et "typename"?

Dans les modèles, où et pourquoi dois-je mettre typename et template sur les noms dépendants? Quels sont exactement les noms dépendants de toute façon? J'ai le code suivant:

template <typename T, typename Tail> // Tail will be a UnionNode too.
struct UnionNode : public Tail {
    // ...
    template<typename U> struct inUnion {
        // Q: where to add typename/template here?
        typedef Tail::inUnion<U> dummy; 
    };
    template< > struct inUnion<T> {
    };
};
template <typename T> // For the last node Tn.
struct UnionNode<T, void> {
    // ...
    template<typename U> struct inUnion {
        char fail[ -2 + (sizeof(U)%2) ]; // Cannot be instantiated for any U
    };
    template< > struct inUnion<T> {
    };
};

Le problème que j'ai est dans la ligne typedef Tail::inUnion<U> dummy. Je suis à peu près certain que inUnion est un nom dépendant, et VC++ a tout à fait raison de l'étouffer. Je sais aussi que je devrais pouvoir ajouter template quelque part pour dire au compilateur qu'inUnion est un template-id. Mais où exactement? Et devrait-il alors supposer qu'inUnion est un modèle de classe, c.-à-d. inUnion<U> nomme un type et non une fonction?

1034
MSalters

Pour analyser un programme C++, le compilateur doit savoir si certains noms sont des types ou non. L'exemple suivant montre que:

t * f;

Comment cela devrait-il être analysé? Pour de nombreuses langues, un compilateur n'a pas besoin de connaître la signification d'un nom pour pouvoir analyser et, fondamentalement, connaître l'action effectuée par une ligne de code. En C++, ce qui précède peut cependant donner des interprétations très différentes selon ce que t signifie. S'il s'agit d'un type, il s'agira alors de la déclaration d'un pointeur f. Cependant si ce n'est pas un type, ce sera une multiplication. Ainsi, la norme C++ dit au paragraphe (3/7):

Certains noms désignent des types ou des modèles. En général, chaque fois qu'un nom est rencontré, il est nécessaire de déterminer si ce nom désigne l'une de ces entités avant de continuer à analyser le programme qui le contient. Le processus qui détermine cela s'appelle la recherche de nom.

Comment le compilateur va-t-il découvrir à quoi un nom t::x fait référence, si t se réfère à un paramètre de type template? x pourrait être un membre de données int statique pouvant être multiplié ou pourrait également être une classe imbriquée ou une référence typée pouvant donner lieu à une déclaration. Si un nom a cette propriété - qu'il ne peut pas être recherché tant que les arguments du modèle ne sont pas connus -, cela s'appelle un nom dépendant ​​("dépend" des paramètres du modèle).

Vous pouvez recommander d'attendre que l'utilisateur instancie le modèle:

Attendons que l'utilisateur instancie le modèle, puis découvrons plus tard le vrai sens de t::x * f;.

Cela fonctionnera et est autorisé par la norme en tant qu’approche de mise en œuvre possible. Fondamentalement, ces compilateurs copient le texte du modèle dans un tampon interne et, lorsqu'une instanciation est nécessaire, ils analysent le modèle et détectent éventuellement des erreurs dans la définition. Mais au lieu de gêner les utilisateurs du modèle (pauvres collègues!) Avec des erreurs commises par l'auteur d'un modèle, d'autres implémentations choisissent de vérifier les modèles à un stade précoce et de générer des erreurs dans la définition dès que possible, avant même qu'une instanciation ne se produise.

Il doit donc y avoir un moyen de dire au compilateur que certains noms sont des types et que d'autres ne le sont pas.

Le mot-clé "typename"

La réponse est: Nous décidons comment le compilateur doit analyser cela. Si t::x est un nom dépendant, nous devons le préfixer par typename pour indiquer au compilateur de l'analyser d'une certaine manière. La norme dit à (14.6/2):

Un nom utilisé dans une déclaration ou une définition de modèle et dépendant d'un paramètre de modèle est supposé ne pas nommer un type, sauf si la recherche de nom applicable trouve un nom de type ou si le nom est qualifié par le mot clé nom_type.

Il existe de nombreux noms pour lesquels typename n'est pas nécessaire, car le compilateur peut, avec la recherche de nom applicable dans la définition du modèle, déterminer comment analyser une construction elle-même - par exemple avec T *f;, lorsque T est un paramètre de modèle de type. Mais pour que t::x * f; soit une déclaration, elle doit être écrite sous la forme typename t::x *f;. Si vous omettez le mot-clé et que le nom est considéré comme un non-type, mais lorsque l'instanciation détecte un type, les messages d'erreur habituels sont émis par le compilateur. Parfois, l'erreur est par conséquent donnée au moment de la définition:

// 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;

La syntaxe autorise typename uniquement avant les noms qualifiés - il est donc considéré comme acquis que les noms non qualifiés sont toujours connus pour faire référence à des types s'ils le font.

Un casse-tête similaire existe pour les noms qui désignent des modèles, comme l'indique le texte d'introduction.

Le mot clé "template"

Vous souvenez-vous de la citation initiale ci-dessus et de la manière dont la norme requiert également une manipulation spéciale des modèles? Prenons l'exemple innocent suivant:

boost::function< int() > f;

Cela peut sembler évident pour un lecteur humain. Pas si pour le compilateur. Imaginez la définition arbitraire suivante de boost::function et f:

namespace boost { int function = 0; }
int main() { 
  int f = 0;
  boost::function< int() > f; 
}

C'est en fait un expression! Il utilise l'opérateur inférieur pour comparer boost::function par rapport à zéro (int()), puis utilise l'opérateur supérieur pour comparer la bool résultante à f. Cependant, comme vous le savez peut-être, boost::functiondans la vie réelle est un modèle, le compilateur sait donc (14.2/3):

Une fois que name lookup (3.4) a découvert qu'un nom est un nom de modèle, si ce nom est suivi d'un <, le <est toujours considéré comme le début d'une liste de modèles d'arguments et jamais comme un nom suivi du signe moins. que l'opérateur.

Nous revenons maintenant au même problème que pour typename. Que se passe-t-il si nous ne pouvons pas encore savoir si le nom est un modèle lors de l'analyse du code? Nous devrons insérer template juste avant le nom du modèle, comme spécifié par 14.2/4. Cela ressemble à:

t::template f<int>(); // call a function template

Les noms de modèles peuvent non seulement apparaître après un ::, mais également après un -> ou . dans un accès membre. Vous devez également y insérer le mot clé:

this->template f<int>(); // call a function template

Les dépendances

Pour les personnes qui ont des livres de Standardese épais sur leur étagère et qui veulent savoir de quoi je parlais exactement, je vais parler un peu de la manière dont cela est spécifié dans la norme.

Dans les déclarations de modèle, certaines constructions ont des significations différentes selon les arguments de modèle que vous utilisez pour instancier le modèle: Les expressions peuvent avoir des types ou des valeurs différents, les variables peuvent avoir des types différents ou les appels de fonction peuvent appeler des fonctions différentes. On dit généralement que ces constructions dépend sur les paramètres de modèle.

La norme définit précisément les règles selon qu'une construction est dépendante ou non. Il les sépare en groupes logiquement différents: un attrape des types, un autre attrape des expressions. Les expressions peuvent dépendre de leur valeur et/ou de leur type. Nous avons donc, avec des exemples typiques en annexe:

  • Types dépendants (par exemple: un paramètre de modèle de type T)
  • Expressions dépendantes de la valeur (par exemple: un paramètre de modèle non typé N)
  • Expressions dépendantes du type (par exemple: transtypage en paramètre de modèle de type (T)0)

La plupart des règles sont intuitives et sont construites de manière récursive: par exemple, un type construit comme T[N] est un type dépendant si N est une expression dépendante de la valeur ou T est un type dépendant . Les détails de ceci peuvent être lus dans la section (14.6.2/1) pour les types dépendants, (14.6.2.2) pour les expressions dépendantes du type et (14.6.2.3) pour les expressions dépendantes de la valeur.

Noms dépendants

La norme ignore un peu ce que exactement ​​est un nom dépendant. Sur une simple lecture (vous savez, le principe de la moindre surprise), tout ce qu’il définit comme un nom dépendant ​​est le cas spécial pour les noms de fonction ci-dessous. Mais comme il est clair que T::x doit également être recherché dans le contexte d'instanciation, il doit également être un nom dépendant (heureusement, à partir de mi-C++ 14, le comité a commencé à chercher des solutions pour résoudre cette définition déroutante. ).

Pour éviter ce problème, j'ai eu recours à une interprétation simple du texte standard. Parmi toutes les constructions qui désignent des types ou des expressions dépendants, un sous-ensemble de ceux-ci représente des noms. Ces noms sont donc des "noms dépendants". Un nom peut prendre différentes formes - la norme dit:

Un nom est l’utilisation d’un identifiant (2.11), d’un identificateur de fonction opérateur (13.5), d’un identificateur de fonction de conversion (12.3.2) ou d’un identificateur de modèle (14.2) désignant une entité ou une étiquette (6.6.4, 6.1)

Un identifiant est juste une simple séquence de caractères/chiffres, tandis que les deux suivants sont les formes operator + et operator type. Le dernier formulaire est template-name <argument list>. Tous ces noms sont des noms, et par l'usage conventionnel dans la norme, un nom peut également inclure des qualificatifs indiquant quel espace de nom ou quelle classe un nom doit être recherché.

Une expression dépendant de la valeur 1 + N n'est pas un nom, mais N l'est. Le sous-ensemble de toutes les constructions dépendantes qui sont des noms s'appelle nom dépendant. Les noms de fonction, cependant, peuvent avoir une signification différente selon les instanciations d'un modèle, mais ne sont malheureusement pas concernés par cette règle générale.

Noms de fonctions dépendantes

Cet article ne concerne pas principalement les utilisateurs, mais il convient néanmoins de le mentionner: les noms de fonctions sont une exception gérée séparément. Un nom de fonction identificateur dépend non pas de lui-même, mais des expressions d'argument dépendantes du type utilisées dans un appel. Dans l'exemple f((T)0), f est un nom dépendant. Dans la norme, cela est spécifié dans (14.6.2/1).

Notes complémentaires et exemples

Dans suffisamment de cas, nous avons besoin de typename et de template. Votre code devrait ressembler à ceci

template <typename T, typename Tail>
struct UnionNode : public Tail {
    // ...
    template<typename U> struct inUnion {
        typedef typename Tail::template inUnion<U> dummy;
    };
    // ...
};

Le mot clé template ne doit pas toujours apparaître dans la dernière partie du nom. Il peut apparaître au milieu d'un nom de classe utilisé comme une étendue, comme dans l'exemple suivant.

typename t::template iterator<int>::value_type v;

Dans certains cas, les mots-clés sont interdits, comme indiqué ci-dessous.

  • Sur le nom d'une classe de base dépendante, vous n'êtes pas autorisé à écrire typename. Il est supposé que le nom donné est un nom de type de classe. Cela est vrai pour les deux noms de la liste de la classe de base et de la liste d'initialisation du constructeur:

     template <typename T>
     struct derive_from_Has_type : /* typename */ SomeBase<T>::type 
     { };
    
  • Dans using-declarations, il n'est pas possible d'utiliser template après le dernier ::, et le comité C++ dit ne travaille pas sur une solution.

     template <typename T>
     struct derive_from_Has_type : SomeBase<T> {
        using SomeBase<T>::template type; // error
        using typename SomeBase<T>::type; // typename *is* allowed
     };
    
1070

C++ 11

Problème

Bien que les règles de C++ 03 relatives au moment où vous avez besoin de typename et template soient largement raisonnables, il existe un inconvénient gênant de sa formulation.

template<typename T>
struct A {
  typedef int result_type;

  void f() {
    // error, "this" is dependent, "template" keyword needed
    this->g<float>();

    // OK
    g<float>();

    // error, "A<T>" is dependent, "typename" keyword needed
    A<T>::result_type n1;

    // OK
    result_type n2; 
  }

  template<typename U>
  void g();
};

Comme on peut le constater, nous avons besoin du mot-clé de désambiguïsation même si le compilateur peut parfaitement comprendre lui-même que A::result_type ne peut être que int (et est donc un type), et this->g ne peut être que le modèle de membre g déclaré ultérieurement (même si A est explicitement spécialisé quelque part, cela n'affectera pas le code de ce modèle, de sorte que sa signification ne peut pas être affectée par une spécialisation ultérieure de A! ).

Instanciation en cours

Pour améliorer la situation, dans C++ 11, le langage assure le suivi lorsqu'un type fait référence au modèle englobant. Pour savoir cela, le type doit avoir été formé en utilisant une certaine forme de nom, qui est son propre nom (ci-dessus, A, A<T>, ::A<T>). Un type référencé par un tel nom est connu pour être l'instanciation actuelle . Il peut y avoir plusieurs types correspondant à toutes les instanciations actuelles si le type à partir duquel le nom est formé est une classe membre/imbriquée (alors, A::NestedClass et A sont tous deux des instanciations actuelles).

Basé sur cette notion, le langage dit que CurrentInstantiation::Foo, Foo et CurrentInstantiationTyped->Foo (comme A *a = this; a->Foo) sont tous membres de l'instanciation en cours si ils se trouvent être membres d'une classe qui est l'instanciation en cours ou l'une de ses classes de base non dépendantes (en faisant simplement la recherche de nom immédiatement).

Les mots-clés typename et template ne sont plus nécessaires si le qualificatif est membre de l'instanciation en cours. Un point clé ici à retenir est que A<T> est toujours un nom dépendant du type (après tout T dépend également du type). Mais A<T>::result_type est connu pour être un type - le compilateur examinera "par magie" ce type de types dépendants pour le comprendre.

struct B {
  typedef int result_type;
};

template<typename T>
struct C { }; // could be specialized!

template<typename T>
struct D : B, C<T> {
  void f() {
    // OK, member of current instantiation!
    // A::result_type is not dependent: int
    D::result_type r1;

    // error, not a member of the current instantiation
    D::questionable_type r2;

    // OK for now - relying on C<T> to provide it
    // But not a member of the current instantiation
    typename D::questionable_type r3;        
  }
};

C'est impressionnant, mais pouvons-nous faire mieux? Le langage va même plus loin et exige qu'une implémentation lise à nouveau D::result_type lors de l'instanciation de D::f (même si elle a déjà trouvé sa signification au moment de la définition). Lorsque le résultat de la recherche est différent ou crée une ambiguïté, le programme est mal formé et un diagnostic doit être fourni. Imaginez ce qui se passe si nous définissons C comme ceci

template<>
struct C<int> {
  typedef bool result_type;
  typedef int questionable_type;
};

Un compilateur est requis pour intercepter l'erreur lors de l'instanciation de D<int>::f. Vous obtenez donc le meilleur des deux mondes: la recherche "différée" vous protège en cas de problème avec les classes de base dépendantes, ainsi que la recherche "immédiate" qui vous libère de typename et template.

Spécialisations inconnues

Dans le code de D, le nom typename D::questionable_type n'est pas membre de l'instanciation en cours. Au lieu de cela, la langue le marque en tant que membre d'une spécialisation inconnue . En particulier, cela est toujours le cas lorsque vous exécutez DependentTypeName::Foo ou DependentTypedName->Foo et que le type dépendant est soit et non l'instanciation en cours (auquel cas le le compilateur peut abandonner et dire "nous verrons plus tard ce que Foo est) ou il est l'instanciation en cours et le nom n'y a pas été trouvé ou non dépendant classes de base et il existe également des classes de base dépendantes.

Imaginez ce qui se passe si nous avions une fonction membre h dans le modèle de classe défini ci-dessus A

void h() {
  typename A<T>::questionable_type x;
}

En C++ 03, le langage permettait de détecter cette erreur car il ne pouvait jamais exister de moyen valide pour instancier A<T>::h (quel que soit l'argument que vous donniez à T). En C++ 11, le langage a maintenant une vérification supplémentaire pour donner plus de raisons aux compilateurs d'implémenter cette règle. Puisque A n'a pas de classes de base dépendantes et que A ne déclare pas de membre questionable_type, le nom A<T>::questionable_type est ni un membre. de l'instanciation en cours ni membre d'une spécialisation inconnue. Dans ce cas, le code ne devrait en aucun cas être compilé de manière valide au moment de l'instanciation. Le langage interdit par conséquent à un nom où le qualificatif est l'instanciation actuelle de ne pas être membre d'une spécialisation inconnue ni d'un membre de l'instanciation actuelle (toutefois , cette violation n’est toujours pas requise pour être diagnostiquée).

Exemples et anecdotes

Vous pouvez essayer cette connaissance sur cette réponse et voir si les définitions ci-dessus ont un sens pour vous dans un exemple réel (elles sont répétées un peu moins détaillées dans cette réponse).

Les règles C++ 11 rendent le code C++ 03 valide suivant mal formé (ce qui n’était pas prévu par le comité C++, mais ne sera probablement pas corrigé)

struct B { void f(); };
struct A : virtual B { void f(); };

template<typename T>
struct C : virtual B, T {
  void g() { this->f(); }
};

int main() { 
  C<A> c; c.g(); 
}

Ce code C++ 03 valide lierait this->f à A::f au moment de l'instanciation et tout irait bien. C++ 11 le lie toutefois immédiatement à B::f et nécessite une double vérification lors de l'instanciation, en vérifiant si la recherche correspond toujours. Cependant, lors de l'instanciation de C<A>::g, règle de dominance s'applique et la recherche trouvera A::f à la place.

131

PRÉFACE

Ce message est censé être une alternative facile à lire au message de litb/ .

Le but sous-jacent est le même; une explication à "quand?" et pourquoi?" typename et template doivent être appliqués.


Quel est le but de typename et template?

typename et template sont utilisables dans des circonstances autres que lors de la déclaration d'un modèle.

Il existe certains contextes dans C++ où le compilateur doit être explicitement expliqué comment traiter un nom, et tous ces contextes ont une chose en commun; ils dépendent d'au moins un template-paramètre .

Nous nous référons à ces noms, où il peut y avoir une ambiguïté dans l'interprétation, comme; " noms dépendants ".

Cet article expliquera la relation entre dépendants et les deux mots-clés.


UN SNIPPET DIT PLUS DE 1000 MOTS

Essayez d’expliquer ce qui se passe dans le modèle de fonction suivant , soit à vous-même, à un ami ou peut-être à votre chat; que se passe-t-il dans la déclaration marquée (A)?

template<class T> void f_tmpl () { T::foo * x; /* <-- (A) */ }


 Ce n'est peut-être pas aussi facile qu'on le pense, plus précisément le résultat de l'évaluation (A) fortement dépend de la définition du type passé en tant que modèle-paramètre T.

Différents Ts peuvent changer radicalement la sémantique impliquée.

struct X { typedef int       foo;       }; /* (C) --> */ f_tmpl<X> ();
struct Y { static  int const foo = 123; }; /* (D) --> */ f_tmpl<Y> ();


Les deux scénarios différents :

  • Si nous instancions la fonction modèle avec le type X, comme dans (C), nous aurons une déclaration de pointeur sur int nommé x , mais;

  • si nous instancions le modèle avec le type Y, comme dans (D), (A) consisterait plutôt en une expression qui calcule le produit de 123 multiplié par une variable déjà déclarée x .



LA JUSTIFICATION

C++ Standard se soucie de notre sécurité et de notre bien-être, du moins dans ce cas.

Pour éviter qu'une mise en œuvre ne présente potentiellement de mauvaises surprises, le Standard impose d'éliminer l'ambiguïté d'un nom dépendant par explicitement en indiquant l'intention. n'importe où nous voudrions traiter le nom comme un nom de type ou un identifiant de modèle .

Si rien n'est indiqué, le nom dépendant sera considéré soit comme une variable, soit comme une fonction.



COMMENT GÉRER NOMS DÉPENDANTS ?

S'il s'agissait d'un film hollywoodien, des noms dépendants seraient la maladie qui se propagerait par contact avec le corps, affecterait instantanément son hôte pour le rendre confus. Confusion qui pourrait éventuellement conduire à un programme personnel mal formé.

Un nom-dépendant est tout nom qui dépend directement ou indirectement d'un modèle . -paramètre

template<class T> void g_tmpl () {
   SomeTrait<T>::type                   foo; // (E), ill-formed
   SomeTrait<T>::NestedTrait<int>::type bar; // (F), ill-formed
   foo.data<int> ();                         // (G), ill-formed    
}

Nous avons quatre noms dépendants dans l'extrait de code ci-dessus:

  • E)
    • "type" dépend de l'instanciation de SomeTrait<T>, qui inclut T, et;
  • F)
    • "NestedTrait" , qui est un template-id , dépend de SomeTrait<T>, et;
    • "type" à la fin de (F) dépend de NestedTrait , qui dépend de SomeTrait<T>, et;
  • G)
    • "data" , qui ressemble à un modèle de fonction de membre , est indirectement dépendant de -name car le type de foo dépend de l'instanciation de SomeTrait<T>.

Ni de déclaration (E), (F) ou (G) est valide si le compilateur interprète les noms dépendants en tant que variables/fonctions (ce qui, comme indiqué précédemment, est ce qui se passe si nous ne disons pas explicitement le contraire). 

LA SOLUTION

Pour que g_tmpl ait une définition valide, nous devons indiquer explicitement au compilateur que nous attendons un type (E), un template-id et un type dans (F) et un template-id dans ( G).

template<class T> void g_tmpl () {
   typename SomeTrait<T>::type foo;                            // (G), legal
   typename SomeTrait<T>::template NestedTrait<int>::type bar; // (H), legal
   foo.template data<int> ();                                  // (I), legal
}

Chaque fois qu'un nom désigne un type, tous les noms impliqués doivent soit les noms de types ou les espaces de noms , dans cet esprit, il est assez facile de voir que nous appliquons typename au début de notre nom qualifié complet .

template est cependant différent à cet égard, car il n’ya aucun moyen de parvenir à une conclusion telle que; "Oh, ceci est un modèle, alors cette autre chose doit également être un modèle" . Cela signifie que nous appliquons template directement devant tout nom que nous aimerions traiter comme tel.



Puis-je simplement coller les MOTS-CLÉS devant n'importe quel nom?

" Puis-je simplement coller typename et template devant un nom? Je ne veux pas m'inquiéter du contexte dans lequel ils apparaissent ... " - Some C++ Developer

Les règles de la norme stipulent que vous pouvez appliquer les mots-clés tant que vous avez affaire à un nom qualifié (K), mais si le nom n'est pas qualifié , l'application est mal formée (L).

namespace N {
  template<class T>
  struct X { };
}
         N::         X<int> a; // ...  legal
typename N::template X<int> b; // (K), legal
typename template    X<int> c; // (L), ill-formed

Remarque : Appliquer typename ou template dans un contexte où il n'est pas nécessaire, n'est pas considéré comme une bonne pratique. Ce n'est pas parce que vous pouvez faire quelque chose que vous devriez le faire.


En outre, il existe des contextes dans lesquels typename et template sont explicitement interdits:

  • Lors de la spécification des bases dont une classe hérite

    Chaque nom écrit dans la liste de spécificateur de base d'une classe dérivée est déjà traité comme un nom de type , en spécifiant explicitement typename est à la fois mal formé et redondant.

                       // .------- the base-specifier-list
     template<class T> // v
     struct Derived      : typename SomeTrait<T>::type /* <- ill-formed */ {
       ...
     };
    


  • Lorsque template-id est celui auquel on fait référence dans la directive using d'une classe dérivée

     struct Base {
       template<class T>
       struct type { };
     };
    
     struct Derived : Base {
       using Base::template type; // ill-formed
       using Base::type;          // legal
     };
    
87
typedef typename Tail::inUnion<U> dummy;

Cependant, je ne suis pas sûr que votre implémentation d’inUnion soit correcte. Si je comprends bien, cette classe n'est pas supposée être instanciée. Par conséquent, l'onglet "échec" n'échouera jamais complètement. Peut-être serait-il préférable d'indiquer si le type est dans l'union ou non avec une simple valeur booléenne.

template <typename T, typename TypeList> struct Contains;

template <typename T, typename Head, typename Tail>
struct Contains<T, UnionNode<Head, Tail> >
{
    enum { result = Contains<T, Tail>::result };
};

template <typename T, typename Tail>
struct Contains<T, UnionNode<T, Tail> >
{
    enum { result = true };
};

template <typename T>
struct Contains<T, void>
{
    enum { result = false };
};

PS: Regardez Boost :: Variant

PS2: Regardez listes de caractères , notamment dans le livre de Andrei Alexandrescu: Modern C++ Design

20
Luc Touraille

Cette réponse est censée être assez courte et agréable pour répondre à (une partie de) la question intitulée. Si vous voulez une réponse plus détaillée qui explique pourquoi vous devez la mettre ici, veuillez vous rendre ici .


La règle générale pour mettre le mot clé typename est généralement utilisée lorsque vous utilisez un paramètre de modèle et que vous souhaitez accéder à un typedef imbriqué ou à un alias, par exemple:

template<typename T>
struct test {
    using type = T; // no typename required
    using underlying_type = typename T::type // typename required
};

Notez que cela s'applique également aux méta-fonctions ou aux choses qui prennent également des paramètres de modèle génériques. Toutefois, si le paramètre de modèle fourni est un type explicite, vous n'avez pas à spécifier typename, par exemple:

template<typename T>
struct test {
    // typename required
    using type = typename std::conditional<true, const T&, T&&>::type;
    // no typename required
    using integer = std::conditional<true, int, float>::type;
};

Les règles générales pour l'ajout du qualificatif template sont généralement similaires, à la différence près qu'elles impliquent des fonctions de membre basées sur un modèle (statique ou autre) d'une structure/classe qui est elle-même basée sur un modèle, par exemple:

Étant donné cette structure et cette fonction:

template<typename T>
struct test {
    template<typename U>
    void get() const {
        std::cout << "get\n";
    }
};

template<typename T>
void func(const test<T>& t) {
    t.get<int>(); // error
}

Toute tentative d'accès à t.get<int>() de l'intérieur de la fonction entraînera une erreur:

main.cpp:13:11: error: expected primary-expression before 'int'
     t.get<int>();
           ^
main.cpp:13:11: error: expected ';' before 'int'

Ainsi, dans ce contexte, vous auriez besoin du mot clé template au préalable et appelez-le ainsi:

t.template get<int>()

De cette façon, le compilateur analysera cela correctement plutôt que t.get < int.

20
Rapptz

Je soumets l'excellente réponse de JLBorges à une question similaire textuellement de cplusplus.com, car c'est l'explication la plus succincte que j'ai lue à ce sujet.

Dans un modèle que nous écrivons, deux types de noms peuvent être utilisés: les noms dépendants et les noms non dépendants. Un nom dépendant est un nom qui dépend d'un paramètre de modèle. un nom non dépendant a la même signification, quels que soient les paramètres du modèle.

Par exemple:

template< typename T > void foo( T& x, std::string str, int count )
{
    // these names are looked up during the second phase
    // when foo is instantiated and the type T is known
    x.size(); // dependant name (non-type)
    T::instance_count ; // dependant name (non-type)
    typename T::iterator i ; // dependant name (type)

    // during the first phase, 
    // T::instance_count is treated as a non-type (this is the default)
    // the typename keyword specifies that T::iterator is to be treated as a type.

    // these names are looked up during the first phase
    std::string::size_type s ; // non-dependant name (type)
    std::string::npos ; // non-dependant name (non-type)
    str.empty() ; // non-dependant name (non-type)
    count ; // non-dependant name (non-type)
}

Ce à quoi un nom dépendant fait référence pourrait être quelque chose de différent pour chaque instanciation différente du modèle. En conséquence, les modèles C++ sont soumis à la "recherche de nom en deux phases". Lorsqu'un modèle est initialement analysé (avant toute instanciation), le compilateur recherche les noms non dépendants. Lorsqu'une instanciation particulière du modèle a lieu, les paramètres du modèle sont alors connus et le compilateur recherche les noms dépendants.

Au cours de la première phase, l'analyseur doit savoir si un nom dépendant est le nom d'un type ou le nom d'un non-type. Par défaut, un nom dépendant est supposé être le nom d'un non-type. Le mot-clé typename précédant un nom dépendant spécifie qu'il s'agit du nom d'un type.


Résumé

Utilisez le mot-clé typename uniquement dans les déclarations de modèle et les définitions, à condition que vous disposiez d'un nom qualifié faisant référence à un type et dépendant d'un paramètre de modèle.

2
Nikos