Wenn die folgenden Klassen keine Vorlagen wären, könnte ich sie einfach x
in der derived
Klasse haben. Mit dem folgenden Code muss ich jedoch verwenden this->x
. Warum?
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
mitthis->
: 1) Verwenden Sie das Präfixbase<T>::x
, 2) Fügen Sie eine Anweisung hinzuusing base<T>::x
, 3) Verwenden Sie einen globalen Compiler-Schalter, der den zulässigen Modus aktiviert. Die Vor- und Nachteile dieser Lösungen sind in stackoverflow.com/questions/50321788/…Antworten:
Kurze Antwort: Um
x
einen abhängigen Namen zu erstellen, wird die Suche verschoben, bis der Vorlagenparameter bekannt ist.Lange Antwort: Wenn ein Compiler eine Vorlage sieht, soll er bestimmte Überprüfungen sofort durchführen, ohne den Vorlagenparameter zu sehen. Andere werden verschoben, bis der Parameter bekannt ist. Es wird als zweiphasige Kompilierung bezeichnet, und MSVC tut dies nicht, aber es wird vom Standard verlangt und von den anderen großen Compilern implementiert. Wenn Sie möchten, muss der Compiler die Vorlage kompilieren, sobald er sie sieht (zu einer Art interner Analysebaumdarstellung), und die Kompilierung der Instanziierung auf später verschieben.
Die Überprüfungen, die an der Vorlage selbst und nicht an bestimmten Instanziierungen durchgeführt werden, erfordern, dass der Compiler in der Lage ist, die Grammatik des Codes in der Vorlage aufzulösen.
In C ++ (und C) müssen Sie manchmal wissen, ob etwas ein Typ ist oder nicht, um die Grammatik des Codes aufzulösen. Beispielsweise:
Wenn A ein Typ ist, deklariert dies einen Zeiger (ohne andere Wirkung als das Schatten des Globalen
x
). Wenn A ein Objekt ist, ist dies eine Multiplikation (und wenn ein Operator nicht überlastet wird, ist dies unzulässig und wird einem r-Wert zugewiesen). Wenn es falsch ist, muss dieser Fehler in Phase 1 diagnostiziert werden. Der Standard definiert ihn als Fehler in der Vorlage und nicht in einer bestimmten Instanziierung. Selbst wenn die Vorlage niemals instanziiert wird, wenn A ein ist,int
ist der obige Code falsch geformt und muss diagnostiziert werden, so wie es wäre, wenn überhauptfoo
keine Vorlage, sondern eine einfache Funktion wäre.Der Standard besagt nun, dass Namen, die nicht von Vorlagenparametern abhängig sind, in Phase 1 auflösbar sein müssen.
A
Hier handelt es sich nicht um einen abhängigen Namen, sondern um dasselbe, unabhängig vom TypT
. Es muss also definiert werden, bevor die Vorlage definiert wird, um in Phase 1 gefunden und überprüft zu werden.T::A
wäre ein Name, der von T abhängt. Wir können unmöglich in Phase 1 wissen, ob das ein Typ ist oder nicht. Der Typ, derT
wahrscheinlich wie in einer Instanziierung verwendet wird, ist noch nicht einmal definiert, und selbst wenn dies der Fall wäre, wissen wir nicht, welche Typen als Vorlagenparameter verwendet werden. Aber wir müssen die Grammatik auflösen, um unsere wertvollen Phase-1-Prüfungen auf schlecht geformte Vorlagen durchzuführen. Der Standard hat also eine Regel für abhängige Namen - der Compiler muss davon ausgehen, dass es sich nicht um Typen handelt, es sei denn, er ist qualifiziert,typename
um anzugeben, dass es sich um Typen handelt, oder er wird in bestimmten eindeutigen Kontexten verwendet. Zum Beispiel wird intemplate <typename T> struct Foo : T::A {};
,T::A
als Basisklasse verwendet und ist daher eindeutig ein Typ. IfFoo
wird mit einem Typ instanziiert, der ein Datenelement hatA
Anstelle eines verschachtelten Typs A ist dies ein Fehler im Code, der die Instanziierung durchführt (Phase 2), nicht ein Fehler in der Vorlage (Phase 1).Aber was ist mit einer Klassenvorlage mit einer abhängigen Basisklasse?
Ist A ein abhängiger Name oder nicht? Bei Basisklassen kann ein beliebiger Name in der Basisklasse angezeigt werden. Wir könnten also sagen, dass A ein abhängiger Name ist, und ihn als Nicht-Typ behandeln. Dies hätte den unerwünschten Effekt, dass jeder Name in Foo abhängig ist und daher jeder in Foo verwendete Typ (mit Ausnahme der eingebauten Typen) qualifiziert werden muss. Innerhalb von Foo müssten Sie schreiben:
da
std::string
wäre ein abhängiger Name und würde daher als Nicht-Typ angenommen, sofern nicht anders angegeben. Autsch!Ein zweites Problem beim Zulassen Ihres bevorzugten Codes (
return x;
) besteht darin, dass jemand , selbst wenn erBar
zuvor definiertFoo
wurde undx
kein Mitglied dieser Definition ist, später eine SpezialisierungBar
für einen bestimmten Typ definieren kannBaz
, z. B.Bar<Baz>
ein Datenelementx
, und dann instanziiertFoo<Baz>
. In dieser Instanziierung würde Ihre Vorlage das Datenelement zurückgeben, anstatt das globale Element zurückzugebenx
. Oder umgekehrt, wenn die Basisvorlagendefinition vonBar
hattex
eine Spezialisierung ohne diese definieren würde und Ihre Vorlage nach einer globalen Version suchen würde,x
in die sie zurückkehren könnteFoo<Baz>
. Ich denke, dies wurde als genauso überraschend und beunruhigend beurteilt wie das Problem, das Sie haben, aber es ist stillschweigend überraschend, im Gegensatz zu einem überraschenden Fehler.Um diese Probleme zu vermeiden, besagt der Standard, dass abhängige Basisklassen von Klassenvorlagen nur dann für die Suche berücksichtigt werden, wenn dies ausdrücklich angefordert wird. Dies verhindert, dass alles abhängig ist, nur weil es in einer abhängigen Basis gefunden werden kann. Es hat auch den unerwünschten Effekt, den Sie sehen - Sie müssen Dinge aus der Basisklasse qualifizieren oder es wird nicht gefunden. Es gibt drei Möglichkeiten,
A
abhängig zu machen :using Bar<T>::A;
in der Klasse -A
bezieht sich jetzt auf etwas inBar<T>
, daher abhängig.Bar<T>::A *x = 0;
am Point of Use - WiederA
ist definitiv inBar<T>
. Dies ist eine Multiplikation, da sietypename
nicht verwendet wurde, also möglicherweise ein schlechtes Beispiel, aber wir müssen bis zur Instanziierung warten, um herauszufinden, oboperator*(Bar<T>::A, x)
ein r-Wert zurückgegeben wird. Wer weiß, vielleicht tut es das ...this->A;
at point of use -A
ist ein Mitglied. Wenn es also nicht in istFoo
, muss es in der Basisklasse sein. Wieder sagt der Standard, dass dies es abhängig macht.Die zweiphasige Kompilierung ist umständlich und schwierig und führt einige überraschende Anforderungen für zusätzliche Redewendungen in Ihren Code ein. Aber ähnlich wie bei der Demokratie ist es wahrscheinlich die schlechteste Art, Dinge zu tun, abgesehen von allen anderen.
Sie könnten vernünftigerweise argumentieren, dass in Ihrem Beispiel
return x;
kein Sinn ergibt, wennx
es sich um einen verschachtelten Typ in der Basisklasse handelt. Daher sollte die Sprache (a) sagen, dass es sich um einen abhängigen Namen handelt, und (2) ihn als Nicht-Typ behandeln und Ihr Code würde ohne funktionierenthis->
. Bis zu einem gewissen Grad sind Sie das Opfer von Kollateralschäden durch die Lösung eines Problems, das in Ihrem Fall nicht zutrifft, aber es gibt immer noch das Problem, dass Ihre Basisklasse möglicherweise Namen unter Ihnen einführt, die Globals beschatten, oder keine Namen hat, die Sie gedacht haben sie hatten und stattdessen ein globales Wesen gefunden.Sie könnten möglicherweise auch argumentieren, dass die Standardeinstellung für abhängige Namen das Gegenteil sein sollte (Typ annehmen, sofern nicht irgendwie angegeben, dass es sich um ein Objekt handelt), oder dass die Standardeinstellung kontextsensitiver sein sollte (in
std::string s = "";
,std::string
könnte als Typ gelesen werden, da nichts anderes grammatikalisch macht Sinn, obwohlstd::string *s = 0;
mehrdeutig ist). Auch hier weiß ich nicht genau, wie die Regeln vereinbart wurden. Ich vermute, dass die Anzahl der erforderlichen Textseiten verringert wird, um viele spezifische Regeln zu erstellen, für die Kontexte einen Typ annehmen und für welche nicht.quelle
-fpermissive
oder ähnliches implementieren , ist dies möglich. Ich kenne die Details der Implementierung nicht, aber der Compiler muss die Auflösung verschieben,x
bis er die tatsächliche temporäre Basisklasse kenntT
. Im nicht zulässigen Modus kann es also im Prinzip die Tatsache aufzeichnen, dass es verschoben wurde, es verschieben, die Suche durchführen, sobald dies der Fall istT
, und wenn die Suche erfolgreich ist, den von Ihnen vorgeschlagenen Text ausgeben. Es wäre ein sehr genauer Vorschlag, wenn er nur in den Fällen gemacht würde, in denen er funktioniert: Die Chancen, dass der Benutzer einen anderenx
aus einem anderen Bereich meinte, sind ziemlich gering!(Ursprüngliche Antwort vom 10. Januar 2011)
Ich glaube, ich habe die Antwort gefunden: GCC-Problem: Verwenden eines Mitglieds einer Basisklasse, das von einem Vorlagenargument abhängt . Die Antwort ist nicht spezifisch für gcc.
Update: Als Antwort auf mmichaels Kommentar aus dem Entwurf N3337 des C ++ 11-Standards:
Ob "weil der Standard es sagt" als Antwort zählt, weiß ich nicht. Wir können jetzt fragen, warum der Standard dies vorschreibt, aber wie Steve Jessops ausgezeichnete Antwort und andere hervorheben, ist die Antwort auf diese letztere Frage ziemlich lang und fraglich. Wenn es um den C ++ - Standard geht, ist es leider oft fast unmöglich, eine kurze und in sich geschlossene Erklärung zu geben, warum der Standard etwas vorschreibt. Dies gilt auch für die letztere Frage.
quelle
Das
x
ist während der Vererbung versteckt. Sie können einblenden über:quelle