(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 t
signifie. 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::x
fait référence, s'il t
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:
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::x
est un nom dépendant, alors nous devons le préfixer typename
pour 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 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 avec T *f;
, quand T
est 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 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:
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::function
et 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::function
contre zéro ( int()
), et utilise ensuite plus-que l' opérateur de comparer le résultat bool
contre 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 template
immé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 N
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 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 + 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
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 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 template
aprè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
};