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 typedef Tail::inUnion<U> dummy
ligne. Je suis assez certain que inUnion
c'est un nom dépendant, et VC ++ a tout à fait raison de s'y étouffer.
Je sais également que je devrais pouvoir ajouter template
quelque 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?
Réponses:
(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:
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
t
signifie. Si c'est un type, ce sera la déclaration d'un pointeurf
. Cependant, si ce n'est pas un type, ce sera une multiplication. Ainsi, la norme C ++ dit au paragraphe (3/7):Comment le compilateur découvrira-t-il à quoi un nom
t::x
fait référence, s'ilt
fait référence à un paramètre de type de modèle?x
pourrait ê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:
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::x
est un nom dépendant, alors nous devons le préfixertypename
pour dire au compilateur de l'analyser d'une certaine manière. La norme dit à (14,6 / 2):Il existe de nombreux noms qui
typename
ne 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 avecT *f;
, quandT
est un paramètre de modèle de type. Mais pourt::x * f;
être une déclaration, elle doit s'écriretypename 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: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 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:
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::function
etf
:C'est en fait une expression valable ! Il utilise le moins que l' opérateur de comparer
boost::function
contre zéro (int()
), et utilise ensuite plus-que l' opérateur de comparer le résultatbool
contref
. 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):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érertemplate
immédiatement avant le nom du modèle, comme spécifié par14.2/4
. Cela ressemble à ceci: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é: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:
T
)N
)(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'ilN
s'agit d'une expression dépendante d'une valeur ou d'T
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 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::x
rechercher é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 identifiant n'est qu'une simple séquence de caractères / chiffres, tandis que les deux suivants sont la forme
operator +
etoperator type
. La dernière forme esttemplate-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 + N
n'est pas un nom, mais l'N
est. 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)
,f
est 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
typename
ettemplate
. Votre code devrait ressembler à ce qui suitLe 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 suivantDans 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:Dans l'utilisation de déclarations, il n'est pas possible d'utiliser
template
après la dernière::
, et le comité C ++ a dit de ne pas travailler sur une solution.la source
typename
est appliquée même lorsque la syntaxe ne permet aucune autre interprétation autre que les noms de type à ce stade?C ++ 11
Problème
Alors que les règles en C ++ 03 sur le moment où vous en avez besoin
typename
ettemplate
sont largement raisonnables, il y a un inconvénient gênant de sa formulationComme 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_type
ne peut êtreint
(et est donc un type), etthis->g
ne peut être que le modèle de membreg
déclaré plus tard (même s'ilA
est 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 deA
!).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::NestedClass
etA
sont les deux instanciations actuelles).Sur la base de cette notion, le langage dit que
CurrentInstantiation::Foo
,Foo
etCurrentInstantiationTyped->Foo
(commeA *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
typename
et netemplate
sont désormais plus requis si le qualificatif est membre de l'instanciation actuelle. Un point clé ici à retenir est qu'ilA<T>
s'agit toujours d' un nom dépendant du type (après tout, ilT
dépend également du type). Mais ilA<T>::result_type
est connu pour être un type - le compilateur examinera "comme par magie" ce type de types dépendants pour le comprendre.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_type
lors de l'instanciationD::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éfinissonsC
comme çaUn 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 detypename
ettemplate
.Spécialisations inconnues
Dans le code de
D
, le nomtypename D::questionable_type
n'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 faitesDependentTypeName::Foo
ouDependentTypedName->Foo
et soit le type dépendant est pas l'instanciation (auquel cas le compilateur peut donner et dire « nous verrons plus loin ce quiFoo
est) 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
h
dans leA
modèle de classe défini ci-dessusEn 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 donniezT
). 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'ilA
n'a pas de classes de base dépendantes etA
ne déclare aucun membrequestionable_type
, le nomA<T>::questionable_type
n'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é)
Cette valide C ++ 03 Code lierait
this->f
àA::f
au moment de l' instanciation et tout va bien. Cependant, C ++ 11 le lie immédiatementB::f
et nécessite une double vérification lors de l'instanciation, vérifiant si la recherche correspond toujours. Cependant, lors de l'instanciationC<A>::g
, la règle de domination s'applique et la recherche sera trouvée à laA::f
place.la source
Quel est le but de
typename
ettemplate
?typename
ettemplate
sont 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 )?
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
T
s peuvent changer radicalement la sémantique impliquée.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 .
Nous avons quatre noms dépendants dans l'extrait ci-dessus:
SomeTrait<T>
, qui incluentT
, et;SomeTrait<T>
, et;SomeTrait<T>
, et;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_tmpl
avoir 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 ).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
typename
au début de notre nom complet .template
cependant, 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 appliquonstemplate
directement devant tout nom que nous aimerions traiter comme tel.PUIS-JE COLLER JUSTE LES MOTS CLÉS DEVANT UN NOM?
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 ).
Remarque : L'application
typename
outemplate
dans 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ù
typename
ettemplate
sont 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
typename
est à la fois mal formé et redondant.Lorsque le template-id est celui auquel il est fait référence dans la directive using d' une classe dérivée
la source
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.
PS: Jetez un oeil à Boost :: Variante
PS2: Jetez un œil aux listes de types , notamment dans le livre d'Andrei Alexandrescu: Modern C ++ Design
la source
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
typename
mot-clé est principalement lorsque vous utilisez un paramètre de modèle et que vous souhaitez accéder à untypedef
alias imbriqué ou à l'aide, par exemple: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:Les règles générales pour l'ajout du
template
qualificatif 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:
Toute tentative d'accès
t.get<int>()
depuis l'intérieur de la fonction entraînera une erreur:Ainsi, dans ce contexte, vous auriez besoin du
template
mot - 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
.la source
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.
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.
la source