Si les classes ci-dessous n’étaient pas des modèles, je pourrais simplement avoir x
dans la classe derived
. Cependant, avec le code ci-dessous, je dois utiliser this->x
. Pourquoi?
template class base { protected: int x; }; template class derived : public base { public: int f() { return this->x; } }; int main() { derived d; df(); return 0; }
Réponse courte: pour créer un nom dépendant de x
, de sorte que la recherche soit différée jusqu’à ce que le paramètre du modèle soit connu.
Réponse longue: lorsqu’un compilateur voit un modèle, il est censé effectuer certaines vérifications immédiatement, sans voir le paramètre du modèle. D’autres sont différés jusqu’à ce que le paramètre soit connu. Elle s’appelle la compilation en deux phases et MSVC ne le fait pas, mais elle est requirejse par la norme et implémentée par les autres compilateurs principaux. Si vous le souhaitez, le compilateur doit comstackr le modèle dès qu’il le voit (à une sorte de représentation interne de l’arbre d’parsing), et reporter la compilation de l’instanciation à plus tard.
Les vérifications effectuées sur le modèle lui-même, plutôt que sur des instanciations particulières, nécessitent que le compilateur soit capable de résoudre la grammaire du code dans le modèle.
En C ++ (et C), pour résoudre la grammaire du code, vous devez parfois savoir si quelque chose est un type ou non. Par exemple:
#if WANT_POINTER typedef int A; #else int A; #endif static const int x = 2; template void foo() { A *x = 0; }
si A est un type, il déclare un pointeur (sans autre effet que l’ombre du x
global). Si A est un object, c’est de la multiplication (et à moins que l’opérateur ne soit surchargé, c’est illégal, assignant une valeur). Si elle est incorrecte, cette erreur doit être diagnostiquée dans la phase 1 , elle est définie par la norme comme étant une erreur dans le modèle , et non dans une instanciation particulière de celui-ci. Même si le modèle n’est jamais instancié, si A est un int
le code ci-dessus est mal formé et doit être diagnostiqué, comme ce serait le cas si foo
n’était pas du tout un modèle, mais une fonction simple.
Maintenant, la norme dit que les noms qui ne dépendent pas des parameters du modèle doivent pouvoir être résolus dans la phase 1. A
ici n’est pas un nom dépendant, il fait référence à la même chose indépendamment du type T
Il doit donc être défini avant que le modèle ne soit défini pour être trouvé et vérifié en phase 1.
T::A
serait un nom qui dépend de T. On ne peut pas savoir en phase 1 si c’est un type ou non. Le type qui sera éventuellement utilisé comme T
dans une instanciation n’est probablement pas encore défini, et même si c’était le cas, nous ne soaps pas quel type sera utilisé comme paramètre de modèle. Mais nous devons résoudre la grammaire afin de faire nos vérifications de phase 1 précieuses pour les modèles mal formés. Donc, la norme a une règle pour les noms dépendants – le compilateur doit supposer qu’ils ne sont pas des types, à moins d’être qualifié avec typename
pour spécifier qu’ils sont des types, ou utilisés dans certains contextes non ambigus. Par exemple, dans le template
, T::A
est utilisé comme classe de base et est donc sans ambiguïté un type. Si Foo
est instancié avec un type qui a un membre de données A
au lieu d’un type nested A, c’est une erreur dans le code effectuant l’instanciation (phase 2), pas une erreur dans le modèle (phase 1).
Mais qu’en est-il d’un modèle de classe avec une classe de base dépendante?
template struct Foo : Bar { Foo() { A *x = 0; } };
A est-il un nom dépendant ou non? Avec les classes de base, n’importe quel nom peut apparaître dans la classe de base. On pourrait donc dire que A est un nom dépendant et le traiter comme un non-type. Cela aurait l’effet indésirable que chaque nom dans Foo est dépendant, et donc tout type utilisé dans Foo (à l’exception des types intégrés) doit être qualifié. A l’intérieur de Foo, il faudrait écrire:
typename std::ssortingng s = "hello, world";
car std::ssortingng
serait un nom dépendant, et donc supposé être un non-type, sauf indication contraire. Aie!
Un deuxième problème avec l’autorisation de votre code préféré ( return x;
) est que même si Bar
est défini avant Foo
et que x
n’est pas membre de cette définition, quelqu’un pourrait plus tard définir une spécialisation pour certains types Baz
, tels que Bar
a un membre de données x
, puis instancie Foo
. Ainsi, dans cette instanciation, votre modèle renvoie le membre de données au lieu de renvoyer le x
global. Ou inversement, si la définition du modèle de base de Bar
avait x
, ils pourraient définir une spécialisation sans elle, et votre modèle chercherait un x
global pour retourner dans Foo
. Je pense que cela a été jugé aussi surprenant et pénible que le problème que vous avez, mais il est silencieux , au lieu de provoquer une erreur surprenante.
Pour éviter ces problèmes, la norme en vigueur stipule que les noms des classes de base dépendantes des modèles de classe ne sont recherchés que si les noms dépendent déjà pour une autre raison. Cela empêche que tout soit dépendant simplement parce qu’il pourrait être trouvé dans une base dépendante. Il a également l’effet indésirable que vous voyez – vous devez qualifier des choses de la classe de base ou elles ne sont pas trouvées. Il existe trois façons courantes de rendre A
personne à charge:
using Bar::A;
dans la classe – A
fait maintenant référence à quelque chose dans Bar
, donc dépendant. Bar::A *x = 0;
au point d’utilisation – A nouveau, A
est définitivement dans Bar
. C’est une multiplication puisque typename
n’a pas été utilisé, donc peut-être un mauvais exemple, mais nous devrons attendre l’instanciation pour savoir si operator*(Bar::A, x)
renvoie une valeur. Qui sait, peut-être que ça marche … this->A;
au point d’utilisation – A
est un membre, donc si ce n’est pas dans Foo
, il doit être dans la classe de base, là encore la norme dit que cela la rend dépendante. La compilation en deux phases est délicate et difficile, et introduit des exigences surprenantes en matière de verbiage supplémentaire dans votre code. Mais, au même titre que la démocratie, c’est probablement la pire façon de faire les choses, à part les autres.
Vous pourriez raisonnablement soutenir que dans votre exemple, return x;
n’a pas de sens si x
est un type nested dans la classe de base, donc le langage devrait (a) dire que c’est un nom dépendant et (2) le traiter comme un non-type, et votre code fonctionnerait sans this->
. Dans une certaine mesure, vous êtes victime de dommages collatéraux de la solution à un problème qui ne s’applique pas à votre cas, mais la classe de base pose toujours des noms sous-jacents aux ombres globales, ou n’a pas de nom. ils avaient, et un être global trouvé à la place.
Vous pourriez également argumenter que la valeur par défaut devrait être l’inverse pour les noms dépendants (supposez un type sauf si vous spécifiez qu’il s’agit d’un object) ou que la valeur par défaut devrait être plus sensible au contexte (dans std::ssortingng s = "";
, std::ssortingng
peut être lu comme un type puisque rien d’autre n’a de sens grammatical, même si std::ssortingng *s = 0;
est ambigu). Encore une fois, je ne sais pas très bien comment les règles ont été adoptées. Je suppose que le nombre de pages de texte qui serait nécessaire, atténué par la création de nombreuses règles spécifiques pour lesquelles les contextes prennent un type et un non-type.
Le x
est caché pendant l’inheritance. Vous pouvez afficher via:
template class derived : public base { public: using base ::x; // added "using" statement int f() { return x; } };
(Réponse originale du 10 janvier 2011)
Je pense avoir trouvé la réponse: problème GCC: utiliser un membre d’une classe de base qui dépend d’un argument de modèle . La réponse n’est pas spécifique à gcc.
Mise à jour: En réponse au commentaire de mmichael , extrait du projet de norme N3337 du standard C ++ 11:
14.6.2 Noms dépendants [temp.dep]
[…]
3 Dans la définition d’un modèle de classe ou de classe, si une classe de base dépend d’un paramètre de modèle, l’étendue de la classe de base n’est pas examinée lors de la recherche de nom non qualifiée au sharepoint définition du modèle de classe ou du membre. le modèle de classe ou le membre.
Que “parce que la norme le dit” compte comme une réponse, je ne sais pas. Nous pouvons maintenant demander pourquoi la norme impose cela, mais comme l’ exprime l’excellente réponse de Steve Jessop et d’autres personnes, la réponse à cette dernière question est plutôt longue et discutable. Malheureusement, en ce qui concerne le standard C ++, il est souvent presque impossible de donner une explication courte et autonome sur les raisons pour lesquelles la norme impose quelque chose; cela s’applique également à cette dernière question.