Si les classes ci-dessous n'étaient pas des modèles, je pourrais simplement en avoir x
dans la derived
classe. Cependant, avec le code ci-dessous, je dois utiliser this->x
. Pourquoi?
template <typename T>
class base {
protected:
int x;
};
template <typename T>
class derived : public base<T> {
public:
int f() { return this->x; }
};
int main() {
derived<int> d;
d.f();
return 0;
}
x
avecthis->
, à savoir: 1) Utilisez le préfixebase<T>::x
, 2) Ajoutez une instructionusing base<T>::x
, 3) Utilisez un commutateur de compilation global qui active le mode permissif. Les avantages et les inconvénients de ces solutions sont décrits dans stackoverflow.com/questions/50321788/…Réponses:
Réponse courte: pour faire
x
un nom dépendant, afin 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. Cela s'appelle une compilation en deux phases, et MSVC ne le fait pas, mais il est requis par la norme et implémenté par les autres principaux compilateurs. Si vous le souhaitez, le compilateur doit compiler le modèle dès qu'il le voit (sur une sorte de représentation d'arbre d'analyse interne) 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 de celui-ci, nécessitent que le compilateur soit capable de résoudre la grammaire du code dans le modèle.
En C ++ (et C), afin de résoudre la grammaire du code, vous devez parfois savoir si quelque chose est un type ou non. Par exemple:
si A est un type, qui déclare un pointeur (sans autre effet que de masquer le global
x
). Si A est un objet, c'est une multiplication (et à moins qu'un opérateur ne surcharge, c'est illégal, l'affecter à une valeur r). Si elle est erronée, cette erreur doit être diagnostiquée dans la phase 1 , elle est définie par la norme comme une erreur dans le modèle , pas 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é, tout comme il ne le seraitfoo
pas du tout, mais une simple fonction.Maintenant, la norme dit que les noms qui ne dépendent pas des paramètres du modèle doivent ê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 quel que soit le typeT
. Il doit donc être défini avant la définition du modèle afin d'être trouvé et vérifié dans la phase 1.T::A
serait un nom qui dépend de T. Nous ne pouvons pas savoir dans la phase 1 si c'est un type ou non. Le type qui sera finalement utilisé commeT
dans une instanciation n'est probablement pas encore défini, et même s'il l'était, nous ne savons pas quel (s) type (s) sera utilisé comme paramètre de modèle. Mais nous devons résoudre la grammaire afin de faire nos précieuses vérifications de phase 1 pour les modèles mal formés. Ainsi, la norme a une règle pour les noms dépendants - le compilateur doit supposer qu'ils ne sont pas des types, sauf s'ils sont qualifiés avectypename
pour spécifier qu'ils sont des types, ou utilisés dans certains contextes non ambigus. Par exemple, danstemplate <typename T> struct Foo : T::A {};
,T::A
est utilisé comme classe de base et est donc sans ambiguïté un type. SiFoo
est instancié avec un type qui a un membre de donnéesA
au lieu d'un type A imbriqué, c'est une erreur dans le code faisant 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?
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 chaque type utilisé dans Foo (sauf les types intégrés) doit être qualifié. À l'intérieur de Foo, vous devez écrire:
car
std::string
serait un nom dépendant, et donc supposé être un non-type, sauf indication contraire. Aie!Un deuxième problème lié à l'autorisation de votre code préféré (
return x;
) est que même s'ilBar
est défini avantFoo
etx
n'est pas membre de cette définition, quelqu'un pourrait ultérieurement définir une spécialisation deBar
pour un certain typeBaz
, tel qu'ilBar<Baz>
a un membre de donnéesx
, puis instancierFoo<Baz>
. Ainsi, dans cette instanciation, votre modèle retournerait le membre de données au lieu de renvoyer le globalx
. Ou à l'inverse, si la définition de modèle de base deBar
hadx
, ils pourraient définir une spécialisation sans elle, et votre modèle rechercherait un globalx
à retournerFoo<Baz>
. Je pense que cela a été jugé tout aussi surprenant et pénible que le problème que vous avez, mais c'est silence surprenant, par opposition à jeter une erreur surprenante.Pour éviter ces problèmes, la norme en vigueur indique que les classes de base dépendantes des modèles de classe ne sont tout simplement pas prises en compte pour la recherche, sauf demande explicite. Cela empêche tout d'être dépendant juste parce qu'il pourrait être trouvé dans une base dépendante. Cela a également l'effet indésirable que vous voyez - vous devez qualifier des éléments de la classe de base ou ils ne sont pas trouvés. Il existe trois façons courantes de rendre
A
dépendant:using Bar<T>::A;
dans la classe -A
se réfère maintenant à quelque chose enBar<T>
, donc dépendant.Bar<T>::A *x = 0;
au point d'utilisation - Encore une fois,A
c'est définitivement le casBar<T>
. Il s'agit d'une multiplication car elletypename
n'a pas été utilisée, donc peut-être un mauvais exemple, mais nous devrons attendre l'instanciation pour savoir sioperator*(Bar<T>::A, x)
renvoie une valeur r. Qui sait, peut-être que oui ...this->A;
au point d'utilisation -A
est un membre, donc s'il n'est pas dansFoo
, il doit être dans la classe de base, encore une fois la norme dit que cela le rend dépendant.La compilation en deux phases est difficile et difficile, et introduit des exigences surprenantes pour un verbiage supplémentaire dans votre code. Mais un peu comme la démocratie, c'est probablement la pire façon de faire les choses, à part toutes les autres.
Vous pourriez raisonnablement affirmer que dans votre exemple, cela
return x;
n'a pas de sens six
s'agit d'un type imbriqué 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 sansthis->
. Dans une certaine mesure, vous êtes victime de dommages collatéraux de la solution à un problème qui ne s'applique pas dans votre cas, mais il y a toujours le problème de votre classe de base qui pourrait introduire sous vous des noms qui masquent les globaux, ou ne pas avoir de noms que vous pensiez ils avaient, et un être global à la place.Vous pouvez également argumenter que la valeur par défaut devrait être l'opposé pour les noms dépendants (supposez que le type à moins qu'il ne soit spécifié d'une manière ou d'une autre comme un objet), ou que la valeur par défaut devrait être plus sensible au contexte (dans
std::string s = "";
,std::string
pourrait être lu comme un type car rien d'autre ne rend grammatical sens, même si ellestd::string *s = 0;
est ambiguë). Encore une fois, je ne sais pas exactement comment les règles ont été convenues. Je suppose que le nombre de pages de texte qui serait nécessaire, atténué contre la création d'un grand nombre de règles spécifiques pour quels contextes prennent un type et lequel non-type.la source
-fpermissive
ou similaire, oui c'est possible. Je ne connais pas les détails de la façon dont il est implémenté, mais le compilateur doit différer la résolutionx
jusqu'à ce qu'il connaisse la classe de base tempate réelleT
. Donc, en principe, en mode non permissif, il peut enregistrer le fait qu'il l'a différé, le reporter, faire la recherche une fois qu'il l'a faitT
, et lorsque la recherche réussit, émettez le texte que vous proposez. Ce serait une suggestion très précise si elle n'est faite que dans les cas où cela fonctionne: les chances que l'utilisateur en ait voulu un autrex
d'une autre portée sont assez minuscules!(Réponse originale du 10 janvier 2011)
Je pense avoir trouvé la réponse: problème GCC: utilisation d'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 , du projet N3337 de la norme C ++ 11:
Que "parce que la norme le dit" compte comme réponse, je ne sais pas. Nous pouvons maintenant demander pourquoi la norme l'exige, mais comme le soulignent l'excellente réponse de Steve Jessop et d'autres, la réponse à cette dernière question est plutôt longue et discutable. Malheureusement, en ce qui concerne la norme C ++, il est souvent presque impossible de donner une explication courte et autonome des raisons pour lesquelles la norme impose quelque chose; cela vaut également pour cette dernière question.
la source
Le
x
est caché pendant l'héritage. Vous pouvez afficher via:la source