Question 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 typedef Tail::inUnion<U> dummy ligne. Je suis à peu près certain que inUnion est un nom dépendant, et VC ++ a raison de l'étouffer. Je sais aussi que je devrais pouvoir ajouter template quelque part pour dire au compilateur que inUnion est un ID de template. Mais où exactement? Et devrait-il alors supposer que inUnion est un modèle de classe, c.-à-d. inUnion<U> nomme un type et non une fonction?


901
2018-03-04 11:56


origine


Réponses:


Afin d'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 beaucoup de langages, un compilateur n'a pas besoin de connaître la signification d'un nom pour analyser et connaître fondamentalement l'action d'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 veux dire. Si c'est un type, alors ce sera une 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 est appelé recherche de nom.

Comment le compilateur trouvera-t-il quel nom? t::x se réfère à, si t fait référence à un paramètre de type de modèle? x pourrait être un membre statique de données int qui pourrait être multiplié ou pourrait tout aussi bien être une classe imbriquée ou typedef qui pourrait donner lieu à une déclaration. Si un nom a cette propriété - qu'il ne peut pas être recherché jusqu'à ce que les arguments de modèle réels soient connus - alors il s'appelle un nom dépendant (Cela "dépend" des paramètres du template).

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

Attendons que l'utilisateur instancie le modèle, puis découvre plus tard la véritable signification de t::x * f;. 

Cela fonctionnera et est effectivement autorisé par la norme en tant qu'approche de mise en œuvre possible. Ces compilateurs copient fondamentalement le texte du modèle dans un tampon interne, et seulement lorsqu'une instanciation est nécessaire, ils analysent le modèle et détectent éventuellement les erreurs dans la définition. Mais au lieu de déranger les utilisateurs du modèle (pauvres collègues!) Avec des erreurs faites par l'auteur d'un modèle, d'autres implémentations choisissent de vérifier les modèles tôt et donnent des erreurs dans la définition dès que possible, avant même qu'une instanciation ait lieu.

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

Le mot-clé "typename"

La réponse est: nous décidez comment le compilateur devrait analyser cela. Si t::x est un nom dépendant, alors nous devons le préfixer par typenamedire 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é typename.

Il y a beaucoup de 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, trouver comment analyser une construction elle-même - par exemple avec T *f;, quand T est un paramètre de modèle de type. Mais pour t::x * f; pour être une déclaration, il doit être écrit comme typename t::x *f;. Si vous omettez le mot-clé et que le nom est considéré comme n'étant pas un type, mais que l'instanciation indique qu'il indique 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 permet typename seulement avant les noms qualifiés - il est donc considéré comme acquis que les noms non qualifiés sont toujours connus pour se référer aux types s'ils le font.

Un gotcha similaire existe pour les noms qui dénotent des modèles, comme suggéré par le texte d'introduction.

Le mot-clé "template"

Rappelez-vous la citation initiale ci-dessus et comment la norme exige une manipulation spéciale pour les modèles aussi bien? Prenons l'exemple innocent suivant:

boost::function< int() > f;

Cela peut sembler évident à un lecteur humain. Pas 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 valide expression! Il utilise l'opérateur inférieur à comparer boost::function contre zéro (int()), puis utilise l'opérateur supérieur à pour comparer le résultat bool contre f. Cependant, comme vous le savez peut-être, boost::function  dans la vraie vie est un template, donc le compilateur sait (14.2 / 3):

Après recherche de nom (3.4) trouve un nom est un nom de modèle, si ce nom est suivi d'un <, le <est   toujours pris comme le début d'un template-argument-list et jamais comme un nom suivi par le moins de   opérateur.

Nous revenons maintenant au même problème qu'avec typename. Que faire si nous ne pouvons pas encore savoir si le nom est un modèle lors de l'analyse du code? Nous aurons besoin d'insérer template immédiatement avant le nom du modèle, tel que 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 aussi après un -> ou . dans un accès de membre de classe. Vous devez également insérer le mot-clé:

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

Dépendances

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

Dans les déclarations de modèle, certaines constructions ont des significations différentes en fonction des arguments de modèle que vous utilisez pour instancier le modèle: Les expressions peuvent avoir différents types ou valeurs, les variables peuvent avoir des types différents ou les appels de fonctions peuvent appeler différentes fonctions. De telles constructions sont généralement dites dépendre sur les paramètres du modèle.

La norme définit précisément les règles selon que la construction est dépendante ou non. Il les sépare en groupes logiquement différents: l'un attrape les types, l'autre attrape les 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épendant de la valeur (par exemple: un paramètre de modèle non-type N)
  • Expressions dépendantes du type (par exemple: une conversion en un paramètre de modèle de type (T)0)

La plupart des règles sont intuitives et construites récursivement: Par exemple, un type construit comme T[N] est un type dépendant si N est une expression dépendant 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épendant de la valeur.

Noms dépendants

La norme n'est pas claire sur 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 nom dépendant est le cas particulier des noms de fonctions ci-dessous. Mais puisque clairement T::x doit également être recherché dans le contexte d'instanciation, il doit aussi être un nom dépendant (heureusement, à partir du milieu C ++ 14, le comité a commencé à chercher comment corriger cette définition déroutante).

Pour éviter ce problème, j'ai eu recours à une interprétation simple du texte standard. De toutes les constructions qui indiquent des types ou expressions dépendants, un sous-ensemble d'entre elles 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 identifiant de fonction d'opérateur (13.5), d'un identifiant de fonction de conversion (12.3.2) ou d'un identifiant de modèle (14.2) qui désigne une entité ou une étiquette (6.6.4, 6.1)

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

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

Noms de fonction dépendants

Pas principalement une préoccupation de cet article, mais toujours digne de mention: Les noms de fonctions sont une exception qui sont traités séparément. Un nom de fonction d'identificateur ne dépend pas de lui-même, mais des expressions d'argument dépendant du type utilisées dans un appel. Dans l'exemple f((T)0), f est un nom dépendant. Dans la norme, ceci est spécifié à (14.6.2/1).

Notes supplémentaires et exemples

Dans suffisamment de cas, nous avons besoin des deux typename et template. Votre code devrait ressembler à ce qui suit

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 d'un nom. Il peut apparaître au milieu d'un nom de classe utilisé comme portée, comme dans l'exemple suivant

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

Dans certains cas, les mots-clés sont interdits, comme détaillé 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 les déclarations d'utilisation, il n'est pas possible d'utiliser template après le dernier ::et le comité C ++ m'a dit ne pas travailler 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
     };
    

931
2018-03-05 00:27



C ++ 11

Problème

Alors que les règles dans C ++ 03 sur quand vous avez besoin typename et template sont largement raisonnables, il y a un désavantage 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 voir, nous avons besoin du mot-clé homonymie même si le compilateur pouvait parfaitement comprendre lui-même que A::result_type peut seulement être int (et est donc un type), et this->g ne peut être que le modèle de membre g déclaré plus tard (même si A est explicitement spécialisé quelque part, cela n'affecterait pas le code dans ce modèle, donc sa signification ne peut pas être affectée par une spécialisation ultérieure de A!).

Instanciation actuelle

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

Basé sur cette notion, le langage dit que CurrentInstantiation::Foo, Foo et CurrentInstantiationTyped->Foo (tel que A *a = this; a->Foo) sont tous membre de l'instanciation actuelle  si ils sont trouvés être des membres d'une classe qui est l'instanciation actuelle ou l'une de ses classes de base non dépendantes (en effectuant juste la recherche de nom immédiatement).

Les mots-clés typename et template ne sont plus requis si le qualificateur est membre de l'instanciation courante. Un point clé ici à retenir est que A<T> est encore un nom dépendant du type (après tout T est également dépendant du type). Mais A<T>::result_type est connu pour être un type - le compilateur va "magiquement" regarder dans ce genre de types dépendants pour comprendre cela.

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? La langue va même plus loin et a besoin qu'une implémentation lève de nouveau les yeux D::result_type lors de l'instanciation D::f (même si elle a déjà trouvé sa signification au moment de la définition). Quand maintenant le résultat de la recherche diffère ou entraîne une ambiguïté, le programme est mal formé et un diagnostic doit être donné. Imaginez ce qui se passe si nous avons défini C comme ça

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

Un compilateur est requis pour intercepter l'erreur lors de l'instanciation D<int>::f. Vous obtenez ainsi le meilleur des deux mondes: la recherche "Retardée" vous protégeant si vous pouviez avoir des ennuis avec les classes de base dépendantes, et aussi 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, le langage le marque comme membre d'une spécialisation inconnue. En particulier, c'est toujours le cas lorsque vous faites DependentTypeName::Foo ou DependentTypedName->Foo et soit le type dépendant est ne pas l'instanciation actuelle (auquel cas le compilateur peut abandonner et dire "nous regarderons plus tard ce que Foo est) ou est l'instanciation courante et le nom n'y ont pas été trouvés ou ses classes de base non dépendantes et il y a aussi des classes de base dépendantes.

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

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

En C ++ 03, le langage permettait d'intercepter cette erreur car il ne pouvait jamais y avoir de moyen valide d'instancier A<T>::h (quel que soit l'argument que vous donnez à T). En C ++ 11, le langage a maintenant une vérification supplémentaire pour donner plus de raison aux compilateurs d'implémenter cette règle. Depuis A n'a pas de classes de base dépendantes, et A ne déclare aucun membre questionable_type, le nom A<T>::questionable_type est ni un membre de l'instanciation actuelle ni un membre d'une spécialisation inconnue. Dans ce cas, il ne devrait y avoir aucun moyen que ce code puisse valablement compiler au moment de l'instanciation, donc la langue interdit un nom où le qualificateur est l'instanciation courante pour n'être ni membre d'une spécialisation inconnue ni membre de l'instanciation courante (cependant , cette violation n'est toujours pas requise pour être diagnostiquée).

Exemples et questions

Vous pouvez essayer cette connaissance sur cette réponse et voyez si les définitions ci-dessus ont un sens pour vous sur un exemple du monde réel (elles sont répétées légèrement moins détaillées dans cette réponse).

Les règles C ++ 11 rendent le code C ++ 03 valide 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 se lierait this->f à A::f au moment de l'instanciation et tout va bien. C ++ 11 le lie 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 C<A>::g, la Règle de dominance s'applique et recherche trouvera A::f au lieu.


118
2017-07-10 20:02



PRÉFACE

Ce message est destiné à être un facile à lire alternative à Le message de litb.

L'objectif sous-jacent est le même; une explication à "Quand?" et pourquoi?" typename et template doit être appliqué.


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 y a certains contextes dans C ++ où le compilateur doit explicitement être dit comment traiter un nom, et tous ces contextes ont une chose en commun; ils dépendent d'au moins un paramètre-template.

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

Ce post offrira une explication à la relation entre noms dépendants, et les deux mots-clés.


UN SNIPPET DIT PLUS DE 1000 MOTS

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

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


Cela pourrait ne pas être aussi facile qu'on le pense, plus précisément le résultat de l'évaluation (UNE) fortement dépend sur la définition du type transmis en tant que paramètre de modèle T.

Différent Ts peut radicalement changer 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 le modèle de fonction avec le type X, un péché (C), nous aurons une déclaration d'un pointeur vers int nommé X, mais;

  • si nous instancions le modèle avec le type Y, un péché (), (UNE) consisterait plutôt en une expression qui calcule le produit de 123 multiplié avec une variable déjà déclarée X.



LA JUSTIFICATION

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

Afin d'éviter qu'une mauvaise implémentation ne souffre de mauvaises surprises, le Standard impose de résoudre l'ambiguïté d'un nom dépendant par explicitement indiquant l'intention partout où nous aimerions traiter le nom comme un nom de typeou un ID de modèle.

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



COMMENT GÉRER NOMS DÉPENDANTS?

Si c'était un film hollywoodien, noms dépendants serait la maladie qui se propage par contact corporel, affecte instantanément son hôte pour le rendre confus. Confusion qui pourrait éventuellement conduire à un programme perso, erhm .. mal formé.

UNE nom dépendant est tout nom qui directement, ou indirectement, dépend d'un paramètre-template.

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 dépendant noms dans l'extrait ci-dessus:

  • E)
    • "type" dépend de l'instanciation de SomeTrait<T>, qui inclut T, et;
  • F)
    • "NestedTrait", qui est un ID de modèle, dépend de SomeTrait<T>, et;
    • "type" au bout du (F) dépend de NestedTrait, qui dépend de SomeTrait<T>, et;
  • g)
    • "Les données", qui ressemble à un modèle de fonction de membre, est indirectement nom dépendant puisque le type de foo dépend de l'instanciation de SomeTrait<T>.

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

LA SOLUTION

Faire g_tmpl avoir une définition valide, nous devons dire explicitement au compilateur que nous attendons un type (E), une ID de modèle et un type dans (F), et un ID de modèle 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 prénom dénote un type, tout  des noms impliqué doit être soit noms de types ou espaces de nomsDans cette optique, il est assez facile de voir que nous appliquons typename au début de notre entièrement nom qualifié.

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



Puis-je juste coller le MOTS CLÉS Devant n'importe quel nom?

"Puis-je rester typename et template devant n'importe quel 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 aussi longtemps que vous traitez 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: Application typename ou template dans un contexte où cela n'est pas requis n'est pas considéré comme une bonne pratique; juste parce que vous pouvez faire quelque chose, ne signifie pas que vous devriez.


De plus, il y a des contextes où typename et template sont explicitement non autorisé:

  • Lorsque vous spécifiez les bases dont hérite une classe

    Chaque nom écrit dans une classe dérivée base-spécification-liste est déjà traité comme un nom de type, 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 */ {
       ...
     };
    


  • Quand le ID de modèle est celui qui est mentionné dans une classe dérivée directive d'utilisation

     struct Base {
       template<class T>
       struct type { };
     };
    
     struct Derived : Base {
       using Base::template type; // ill-formed
       using Base::type;          // legal
     };
    

73
2018-06-07 20:28



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 censée être instanciée, donc l'onglet "échouer" ne sera jamais virtuellement échoué. Peut-être qu'il serait 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: Jetez un oeil à Boost :: Variante

PS2: Jetez un oeil à typelists, notamment dans le livre d'Andrei Alexandrescu: Modern C ++ Design


17
2018-03-04 13:37



Cette réponse est censée être une courte et douce pour répondre (partie de) la question intitulée. Si vous voulez une réponse avec plus de détails qui explique pourquoi vous devez les mettre là, s'il vous plaît aller ici.


La règle générale pour mettre le typename mot-clé est la plupart du temps lorsque vous utilisez un paramètre de modèle et que vous voulez accéder à un imbriqué typedef ou using-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 aussi aux méta-fonctions ou aux choses qui prennent aussi des paramètres génériques. Cependant, si le paramètre template 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 de template Les qualificateurs sont généralement similaires sauf qu'ils impliquent généralement des fonctions membres modélisées (statiques ou non) d'une structure / classe qui est elle-même modélisée, par exemple:

Compte tenu de cette structure et de 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
}

Tenter d'accéder t.get<int>() de l'intérieur de la fonction se traduira par 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 de template mot-clé à l'avance et l'appeler comme ça:

t.template get<int>()

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


14
2018-06-06 22:23