Où et pourquoi dois-je mettre les mots-clés «modèle» et «nom de type»?

1127

Dans les modèles, où et pourquoi dois-je mettre typenameet templatesur 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 typedef Tail::inUnion<U> dummyligne. Je suis assez certain que inUnionc'est un nom dépendant, et VC ++ a tout à fait raison de s'y étouffer.
Je sais également que je devrais pouvoir ajouter templatequelque part pour dire au compilateur qu'inUnion est un identifiant de modèle. Mais où exactement? Et faut-il alors supposer que inUnion est un modèle de classe, c'est-à-dire qu'il inUnion<U>nomme un type et non une fonction?

MSalters
la source
1
Question ennuyeuse: pourquoi ne pas booster :: Variant?
Assaf Lavie
58
Sensibilités politiques, portabilité.
MSalters
5
J'ai fait ressortir votre question réelle ("Où mettre le modèle / nom de police?") En plaçant la question finale et le code au début et en raccourcissant le code horizontalement pour l'adapter à un écran 1024x.
Johannes Schaub - litb
7
Suppression des "noms dépendants" du titre car il semble que la plupart des gens qui se posent des questions sur "nom de type" et "modèle" ne savent pas ce que sont les "noms dépendants". Cela devrait être moins déroutant pour eux de cette façon.
Johannes Schaub - litb
2
@MSalters: boost est assez portable. Je dirais que seule la politique est la raison générale pour laquelle le coup de pouce n'est souvent pas confondu. La seule bonne raison que je connaisse est l'augmentation des temps de construction. Sinon, il s'agit de perdre des milliers de dollars en réinventant la roue.
v.oddou

Réponses:

1165

(Voir ici aussi pour ma réponse C ++ 11 )

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 de nombreuses langues, un compilateur n'a pas besoin de connaître la signification d'un nom pour analyser et essentiellement savoir quelle action fait une ligne de code. En C ++, ce qui précède peut cependant donner des interprétations très différentes selon ce que cela tsignifie. Si c'est un type, ce sera 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 découvrira-t-il à quoi un nom t::xfait référence, s'il tfait référence à un paramètre de type de modèle? xpourrait être un membre de données int statique qui pourrait être multiplié ou pourrait tout aussi bien être une classe imbriquée ou un typedef pouvant donner lieu à une déclaration. Si un nom a cette propriété - qu'il ne peut pas être recherché jusqu'à ce que les arguments du modèle réel soient connus - alors il est appelé un nom dépendant (il "dépend" des paramètres du modèle).

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

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

Cela fonctionnera et est en fait autorisé par la norme comme approche d'implémentation possible. Ces compilateurs copient essentiellement 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 des 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 dès le début et de donner des erreurs dans la définition dès que possible, avant même qu'une instanciation n'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écidons comment le compilateur doit analyser cela. Si t::xest un nom dépendant, alors nous devons le préfixer typenamepour dire 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 qui dépend 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 que le nom est qualifié par le mot-clé nom-type.

Il existe de nombreux noms qui typenamene sont pas nécessaires, car le compilateur peut, avec la recherche de nom applicable dans la définition de modèle, comprendre comment analyser une construction elle-même - par exemple avec T *f;, quand Test un paramètre de modèle de type. Mais pour t::x * f;être une déclaration, elle doit s'écrire 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 le trouve dénote 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 typenameuniquement 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 aux types s'ils le font.

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

Le mot clé "template"

Rappelez-vous la citation initiale ci-dessus et comment la norme requiert également une gestion spéciale pour les modèles? Prenons l'exemple innocent suivant:

boost::function< int() > f;

Cela peut sembler évident pour un lecteur humain. Ce n'est pas le cas pour le compilateur. Imaginez la définition arbitraire suivante de boost::functionet f:

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

C'est en fait une expression valable ! Il utilise le moins que l' opérateur de comparer boost::functioncontre zéro ( int()), et utilise ensuite plus-que l' opérateur de comparer le résultat boolcontre f. Cependant, comme vous le savez peut-être, boost::function dans la vraie vie, c'est un modèle, donc le compilateur sait (14.2 / 3):

Après que la recherche de nom (3.4) trouve qu'un nom est un nom de modèle, si ce nom est suivi d'un <, le <est toujours pris comme le début d'une liste d'arguments de modèle et jamais comme un nom suivi du moins- que l'opérateur.

Nous revenons maintenant au même problème qu'avec typename. Et si nous ne pouvons pas encore savoir si le nom est un modèle lors de l'analyse du code? Nous devrons insérer templateimmédiatement avant le nom du modèle, comme spécifié par 14.2/4. Cela ressemble à ceci:

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

Les noms de modèle 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 y insérer le mot-clé:

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

Dépendances

Pour les personnes qui ont des livres normands épais 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 selon les 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 différents types ou les appels de fonction peuvent finir par appeler différentes fonctions. On dit généralement que de telles constructions dépendent des 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épendantes 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 sont construites de manière récursive: par exemple, un type construit tel quel T[N]est un type dépendant s'il Ns'agit d'une expression dépendante d'une valeur ou d' Tun 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 n'est pas claire sur ce qu'est exactement 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 particulier des noms de fonction ci-dessous. Mais comme il faut clairement T::xrechercher également dans le contexte d'instanciation, il doit également être un nom dépendant (heureusement, à partir du milieu de 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 dénotent 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 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énote une entité ou une étiquette (6.6.4, 6.1)

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

Une expression dépendante d'une valeur 1 + Nn'est pas un nom, mais l' Nest. Le sous-ensemble de toutes les constructions dépendantes qui sont des noms est appelé nom dépendant . Cependant, les noms de fonction 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

Ce n'est pas principalement une préoccupation de cet article, mais il convient de le mentionner: les noms de fonction sont une exception qui sont traités séparément. Un nom de fonction d'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), fest un nom dépendant. Dans la norme, cela est spécifié à (14.6.2/1).

Notes et exemples supplémentaires

Dans suffisamment de cas, nous avons besoin de typenameet 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 template- clé ne doit pas toujours apparaître dans la dernière partie d'un nom. Il peut apparaître au milieu devant 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

  • Au 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 classe. Cela est vrai pour les deux noms dans la liste de classe de base et la liste d'initialisation du constructeur:

     template <typename T>
     struct derive_from_Has_type : /* typename */ SomeBase<T>::type 
     { };
  • Dans l'utilisation de déclarations, il n'est pas possible d'utiliser templateaprès la dernière ::, et le comité C ++ a dit de 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
     };
Johannes Schaub - litb
la source
22
Cette réponse a été copiée de mon entrée de FAQ précédente que j'ai supprimée, car j'ai trouvé que je devrais mieux utiliser des questions similaires existantes au lieu de créer de nouvelles "pseudo-questions" juste dans le but d'y répondre. Merci à @Prasoon , qui a édité les idées de la dernière partie (cas où le nom / modèle est interdit) dans la réponse.
Johannes Schaub - litb
1
Pouvez-vous m'aider quand dois-je utiliser cette syntaxe? this-> modèle f <int> (); J'obtiens cette erreur 'template' (en tant que désambiguïsateur) n'est autorisé que dans les modèles mais sans le mot clé template, cela fonctionne très bien.
balki
1
J'ai posé une question similaire aujourd'hui, qui a rapidement été marquée comme doublon: stackoverflow.com/questions/27923722/… . J'ai été chargé de relancer cette question au lieu d'en créer une nouvelle. Je dois dire que je ne suis pas d'accord pour qu'il s'agisse de doublons, mais qui suis-je, non? Donc, y a-t-il une raison qui typenameest appliquée même lorsque la syntaxe ne permet aucune autre interprétation autre que les noms de type à ce stade?
JorenHeit
1
@Pablo vous ne manquez de rien. Mais il fallait quand même écrire la désambiguïsation même si la ligne complète ne serait plus ambiguë.
Johannes Schaub - litb
1
@Pablo le but est de simplifier le langage et les compilateurs. Il existe des propositions pour permettre à plus de situations de comprendre automatiquement les choses, de sorte que vous ayez besoin du mot-clé moins souvent. Notez que dans votre exemple, le jeton est ambigu et seulement après avoir vu ">" après le double, vous pouvez le lever en tant que support d'angle de modèle. Pour plus de détails, je ne suis pas la bonne personne à demander, car je n'ai aucune expérience dans l'implémentation d'un analyseur de compilateurs C ++.
Johannes Schaub - litb
136

C ++ 11

Problème

Alors que les règles en C ++ 03 sur le moment où vous en avez besoin typenameet templatesont largement raisonnables, il y a 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 voir, nous avons besoin du mot-clé de désambiguïsation même si le compilateur pouvait parfaitement comprendre lui-même qui A::result_typene peut être int(et est donc un type), et this->gne peut être que le modèle de membre gdéclaré plus tard (même s'il Aest explicitement spécialisé quelque part, ce serait n'affecte 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 suit 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 l' 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::NestedClasset Asont les deux instanciations actuelles).

Sur la base de cette notion, le langage dit que CurrentInstantiation::Foo, Fooet CurrentInstantiationTyped->Foo(comme A *a = this; a->Foo) sont tous membres de l'instanciation actuelle s'il s'avère qu'ils sont membres d'une classe qui est l'instanciation actuelle ou l'une de ses classes de base non dépendantes (en faisant simplement la recherche de nom immédiatement).

Les mots clés typenameet ne templatesont désormais plus requis si le qualificatif est membre de l'instanciation actuelle. Un point clé ici à retenir est qu'il A<T>s'agit toujours d' un nom dépendant du type (après tout, il Tdépend également du type). Mais il A<T>::result_typeest connu pour être un type - le compilateur examinera "comme 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 nécessite qu'une implémentation recherche à nouveau D::result_typelors de l'instanciation 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 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 définissons Ccomme ça

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

Un compilateur est nécessaire pour détecter l'erreur lors de l'instanciation D<int>::f. Ainsi, vous obtenez le meilleur des deux mondes: la recherche "différée" vous protège si vous pouvez avoir des problèmes avec les classes de base dépendantes, et aussi la recherche "immédiate" qui vous libère de typenameet template.

Spécialisations inconnues

Dans le code de D, le nom typename D::questionable_typen'est pas membre de l'instanciation actuelle. Au lieu de cela, la langue le marque comme membre d'une spécialisation inconnue . En particulier, ce qui est toujours le cas lorsque vous faites DependentTypeName::Fooou DependentTypedName->Fooet soit le type dépendant est pas l'instanciation (auquel cas le compilateur peut donner et dire « nous verrons plus loin ce qui Fooest) ou il est l'instanciation et la nom n'a été trouvé ni dans ses classes de base non dépendantes et il existe également des classes de base dépendantes.

Imaginez ce qui se passe si nous avions une fonction membre hdans le Amodèle de classe défini ci-dessus

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

En C ++ 03, le langage permettait d'attraper cette erreur car il ne pouvait jamais y avoir 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. Étant donné qu'il An'a pas de classes de base dépendantes et Ane déclare aucun membre questionable_type, le nom A<T>::questionable_typen'est ni membre de l'instanciation en cours nimembre d'une spécialisation inconnue. Dans ce cas, il ne devrait y avoir aucun moyen que ce code puisse compiler valablement au moment de l'instanciation, de sorte que le langage interdit qu'un nom où le qualificatif est l'instanciation actuelle ne soit ni membre d'une spécialisation inconnue ni membre de l'instanciation actuelle (cependant , cette violation n'a toujours pas besoin d'être diagnostiquée).

Exemples et anecdotes

Vous pouvez essayer cette connaissance sur cette réponse et voir si les définitions ci-dessus ont du sens pour vous sur un exemple du monde 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(); 
}

Cette valide C ++ 03 Code lierait this->fà A::fau moment de l' instanciation et tout va bien. Cependant, C ++ 11 le lie immédiatement B::fet nécessite une double vérification lors de l'instanciation, vérifiant si la recherche correspond toujours. Cependant, lors de l'instanciation C<A>::g, la règle de domination s'applique et la recherche sera trouvée à la A::fplace.

Johannes Schaub - litb
la source
fyi - cette réponse est référencée ici: stackoverflow.com/questions/56411114/… Une grande partie du code de cette réponse ne se compile pas sur divers compilateurs.
Adam Rackis
@AdamRackis en supposant que la spécification C ++ n'a pas changé depuis 2013 (date à laquelle j'ai écrit cette réponse), les compilateurs avec lesquels vous avez essayé votre code n'implémentent tout simplement pas cette fonctionnalité C ++ 11 +.
Johannes Schaub - litb
100

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?" typenameet templatedoit être appliqué.


Quel est le but de typenameet template?

typenameet templatesont utilisables dans des circonstances autres que lors de la déclaration d'un modèle.

Il existe certains contextes en C ++ où le compilateur doit être explicitement informé de la façon de traiter un nom, et tous ces contextes ont une chose en commun; ils dépendent d'au moins un paramètre de modèle .

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

Ce message offrira une explication de la relation entre les 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 modèle de fonction suivant , à 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) */ }


Cela pourrait ne pas être aussi simple qu'on le pense, plus précisément le résultat de l'évaluation ( A ) dépend fortement de la définition du type passé en tant que paramètre-modèle 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 le modèle de fonction avec le type X , comme dans ( C ), nous aurons une déclaration d'un pointeur vers 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 x déjà déclarée .



LA JUSTIFICATION

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

Pour éviter qu'une implémentation ne souffre potentiellement de mauvaises surprises, la norme exige que nous triions l'ambiguïté d'un nom dépendant en indiquant explicitement l'intention partout où nous aimerions traiter le nom comme un nom de type ou un modèle. id .

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



COMMENT GÉRER LES NOMS DÉPENDANTS ?

S'il s'agissait d'un film hollywoodien, les noms dépendants seraient 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é.

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

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 ci-dessus:

  • E )
    • "type" dépend de l'instanciation de SomeTrait<T>, qui incluent T, et;
  • F )
    • "NestedTrait" , qui est un modèle-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 membre , est indirectement un nom dépendant puisque le type de foo dépend de l'instanciation de SomeTrait<T>.

Aucune des déclarations ( E ), ( F ) ou ( G ) n'est valide si le compilateur interprète les noms dépendants comme des variables / fonctions (ce qui, comme indiqué précédemment, se produit si nous ne disons pas explicitement le contraire).

LA SOLUTION

Pour g_tmplavoir une définition valide, nous devons explicitement dire au compilateur que nous attendons un type dans ( E ), un modèle-id et un type dans ( F ), et un modèle-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 être des noms de type ou des espaces de noms , avec cela à l'esprit, il est assez facile de voir que nous appliquons typenameau début de notre nom complet .

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



PUIS-JE COLLER JUSTE LES MOTS CLÉS DEVANT UN NOM?

" Puis-je simplement rester typenameet templatedevant n'importe quel nom? Je ne veux pas me soucier 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 traitez avec 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 : L'application typenameou templatedans un contexte où cela n'est pas nécessaire n'est pas considérée comme une bonne pratique; ce n'est pas parce que vous pouvez faire quelque chose que vous devez le faire.


De plus, il existe des contextes où typenameet templatesont explicitement interdits:

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

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

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


  • Lorsque le template-id est celui auquel il est 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
     };
Filip Roséen - refp
la source
20
typedef typename Tail::inUnion<U> dummy;

Cependant, je ne suis pas sûr que l'implémentation de inUnion soit correcte. Si je comprends bien, cette classe n'est pas censée être instanciée, donc l'onglet "échouer" n'échouera jamais. Il serait peut-être 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 œil aux listes de types , notamment dans le livre d'Andrei Alexandrescu: Modern C ++ Design

Luc Touraille
la source
inUnion <U> serait instancié, si vous essayiez par exemple d'appeler Union <float, bool> :: operator = (U) avec U == int. Il appelle un ensemble privé (U, inUnion <U> * = 0).
MSalters
Et le travail avec result = true / false est que j'aurais besoin de boost :: enable_if <>, ce qui est incompatible avec notre chaîne d'outils OSX actuelle. Cependant, le modèle séparé est toujours une bonne idée.
MSalters
Luc signifie le mannequin typedef Tail :: inUnion <U>; ligne. qui va instancier Tail. mais pas dans l'Union <U>. il est instancié lorsqu'il a besoin de sa définition complète. cela se produit par exemple si vous prenez la taille de, ou accédez à un membre (en utilisant :: foo). @MSalters de toute façon, vous avez un autre problème:
Johannes Schaub - litb
-sizeof (U) n'est jamais négatif :) car size_t est un type entier non signé. vous obtiendrez un nombre très élevé. vous voulez probablement faire sizeof (U)> = 1? -1: 1 ou similaire :)
Johannes Schaub - litb
je le laisserais juste indéfini et le déclarerais seulement: template <typename U> struct inUnion; il ne peut donc certainement pas être instancié. je pense l'avoir avec le sizeof, le compilateur est autorisé à vous donner également une erreur même si vous ne l' instanciez pas, car si sait sizeof (U) est toujours> = 1 et ...
Johannes Schaub - litb
20

Cette réponse est censée être plutôt courte et douce pour répondre (en partie) à la question intitulée. Si vous voulez une réponse plus détaillée qui explique pourquoi vous devez les mettre là, veuillez aller ici .


La règle générale pour placer le typenamemot-clé est principalement lorsque vous utilisez un paramètre de modèle et que vous souhaitez accéder à un typedefalias imbriqué ou à l'aide, 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 éléments qui prennent également des paramètres de modèle génériques. Cependant, si le paramètre de modèle fourni est un type explicite, vous n'avez pas besoin de 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 templatequalificatif sont pour la plupart similaires, sauf qu'elles impliquent généralement des fonctions membres modèles (statiques ou non) d'une structure / classe qui est elle-même modèle, 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
}

Toute tentative d'accès t.get<int>()depuis 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 templatemot - clé au préalable et l'appelleriez ainsi:

t.template get<int>()

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

Rapptz
la source
2

Je place 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 à une "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 avant un nom dépendant indique qu'il s'agit du nom d'un type.


Sommaire

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

Nikos
la source