Les modèles C ++ masquent les membres parents

Habituellement, quand A hérite de B , tous les membres de A sont automatiquement visibles par les fonctions de B , par exemple

 class A { protected: int a; }; class B : public A { int getA() {return a;} //no need to use A::a, it is automatically visible }; 

Cependant, lorsque j’hérite avec des modèles, ce code devient illégal (au moins dans gcc )

 template class A { protected: int a; }; template class B : public A { int getA() {return a;} }; templt.cpp: In member function `int B::getA()': templt.cpp:9: error: `a' undeclared (first use this function) templt.cpp:9: error: (Each undeclared identifier is reported only once for each function it appears in.) 

Je dois faire l’un des

 class B : public A { using B::a; int getA() {return a;} }; class B : public A { using A::a; int getA() {return a;} }; class B : public A { int getA() {return B::a;} }; 

etc. Comme si la variable a était masquée par une autre variable de B , dans le cas suivant:

 class HiddenByOverload {void hidden(){}} class HidesByOverload : public HiddenByOverload { void hidden(int i) {} //different signature, now `hidden` is hidden void usehidden() { HiddenByOverload::hidden(); // I must expose it explicitly } } 

Pourquoi est-ce? Existe-t-il d’autres moyens d’empêcher C ++ de masquer les variables de la classe de modèle parent?

Edit: merci à tous pour cette discussion fascinante. Je dois admettre que je n’ai pas suivi l’argument qui citait des paragraphes de la norme C ++. Il m’est difficile de le suivre sans lire la source réelle.

La meilleure chose que je puisse faire pour résumer la discussion consiste à citer une courte phrase de “The Zen of Python”:

Si la mise en œuvre est difficile à expliquer, c’est (probablement) une mauvaise idée.

Vous pouvez aussi faire

 class B : public A { int getA() {return this->a;} }; 

Le problème est que le membre est dans une base, qui dépend d’un paramètre de modèle. La recherche normale non qualifiée est effectuée au sharepoint définition, pas au moment d’instanciation, et par conséquent, elle ne recherche pas les bases dépendantes.

Puisqu’il existe des questions sur la manière dont les noms non qualifiés peuvent être dépendants ou sur la manière dont la recherche de noms non qualifiés peut s’appliquer aux noms dépendants:


Trail parse: Déterminer comment nous analysons une déclaration

Si un nom dépendant est rencontré dans un modèle, il est toujours supposé ne pas nommer un type, sauf si la recherche de nom applicable trouve qu’il s’agit d’un type ou si nous ajoutons le nom avec le nom de typename :

 template void f() { T f0; // T is a template type parameter => type T *f1; typename T::name g1; // "name" is assumed to be a type. T::name g0; // "name" cannot be looked up here => non-type } 

Cette recherche d’un nom pour déterminer s’il s’agit d’un type est toujours effectuée au sharepoint la définition du modèle pour tous les noms dépendants: Elle guide l’parsing suivante dans une certaine direction. Dans la deuxième déclaration, nous parsingrons T *f1 tant que déclaration d’un pointeur, mais pas en tant que multiplication. Dans la dernière déclaration, nous avons supposé, lors de la désambiguïsation préalable à l’parsing, que T::name n’était pas un type, et nous avons essayé de l’parsingr comme une expression. Cela échouera, car nous nous attendons à un point-virgule ou à un opérateur après T::name . Cette recherche, que le nom soit ou non un type, n’a aucune influence sur la signification du nom dans les phases ultérieures: elle ne liera pas encore le nom à une déclaration.


Analyse réelle

Une fois que nous avons déterminé quels noms sont des types et quels types ne le sont pas, nous allons parsingr le modèle. Les noms qui ne sont pas dépendants – c’est-à-dire ceux qui ne sont pas recherchés dans une scope dépendante ou qui ne sont pas explicitement dépendants par d’autres règles – sont recherchés au moment où ils sont utilisés dans le modèle et leur signification est non influencé par aucune déclaration visible lors de l’instanciation.

Les noms qui dépendent sont recherchés lors de l’instanciation, à la fois dans la définition du modèle où ils sont utilisés et dans lesquels leur modèle est instancié. Cela est vrai également pour les noms non qualifiés dépendants:

 template struct Bar { void bar() { foo(T()); } }; namespace A { struct Baz { }; void foo(Baz); // found! } int main() { Bar b; b.bar(); } 

Le standard non qualifié est rendu dépendant de la norme car l’argument T() dépend du type. Lors de l’instanciation, nous rechercherons des fonctions appelées foo utilisant une recherche de nom non qualifiée autour de la définition du modèle, et en utilisant une recherche dépendante de l’argument (signification approximative dans l’espace de nom de T ) autour de la définition du modèle et du point où nous l’instancions ). La recherche dépendante de l’argument trouvera alors foo .

Si Bar maintenant une classe de base dépendante, la recherche non qualifiée doit ignorer cette classe de base dépendante:

 template struct HasFoo { }; template struct Bar : HasFoo { void bar() { foo(T()); } }; namespace A { struct Baz { }; void foo(Baz); // found! } template<> struct HasFoo { void foo(); }; int main() { Bar b; b.bar(); } 

Cela doit toujours trouver A::foo , malgré le fait qu’une recherche de nom non qualifiée trouverait une fonction de membre de classe si elle était faite (ADL ne trouvera pas de fonctions supplémentaires si une fonction de membre de classe était trouvée). Namelookup non qualifié ne trouvera pas cette fonction, car il fait partie d’une classe de base dépendante, et ceux-ci sont ignorés lors de la recherche de nom non qualifié. Un autre cas intéressant:

 template struct A { typedef int foo; operator int() { return 0; } }; // makes sure applicable name-lookup // classifies "foo" as a type (so parsing will work). struct TypeNameSugar { typedef int foo; }; template struct C : A, TypeNameSugar { void c() { A *p = this; int i = p->operator foo(); } }; int main() { C().c(); } 

La norme stipule que lors de la recherche de foo dans le nom de l’ operator foo , nous rechercherons indépendamment dans l’étendue de p-> (qui est dans l’étendue de la classe A ) et dans l’étendue dans laquelle l’expression complète apparaît ( qui est la scope de C::c comme un nom non qualifié), et comparez les deux noms, s’ils le trouvent, s’ils désignent le même type. Si nous n’ignorons pas la classe de base dépendante A lors de la recherche dans l’étendue de l’expression complète, nous trouverons foo dans deux classes de base, ce qui créerait une ambiguïté. Ignorer A signifiera que nous trouvons le nom une fois lorsque nous effectuons une recherche dans p-> , et une autre fois lorsque nous recherchons TypeNameSugar .

C’est un problème courant, mais il est parfaitement possible de le contourner, que ce soit pour des fonctions, des types ou des atsortingbuts.

Le problème vient avec la mise en œuvre de l’évaluation en 2 phases des classes de modèles et des fonctions. Normalement, la norme exige que les modèles soient évalués deux fois:

  • Une première fois lorsqu’il est rencontré, pour valider qu’il est correctement formé
  • La seconde, quand instanciée, pour produire le code pour les types donnés

Lors de la première évaluation, les parameters du modèle sont inconnus, il est donc impossible de dire quelle sera la classe de base … et notamment si elle contient a membre. Tout symbole qui ne dépend pas de l’un des parameters du modèle doit être clairement défini et sera vérifié.

En définissant explicitement l’étendue, vous retardez la vérification à la deuxième évaluation en rendant le symbole dépendant des parameters du modèle.

En utilisant Boost comme inspiration:

 template  class MyClass: public Base { public: typedef Base base_; void foo() { // Accessing type bar_type x; // ERROR: Not dependent on template parameter typename base_::bar_type x; // Accessing method bar(); // ERROR: Not dependent on template parameter this->bar(); base_::bar(); // Accessing atsortingbute mBar; // ERROR: Not dependent on template parameter this->mBar; base_::mBar; }; }; // class MyClass 

J’aime l’idiome Boost de la définition d’un typedef base_ inner. Premièrement, il est certainement utile de définir les constructeurs et deuxièmement, en précisant explicitement ce qui vient de la classe Base, cela clarifie les choses pour ceux qui parcourent le code.