typename vs. T::template nochmal
-
Hi,
typename sagt, dass der abhängige Name ein Typ ist, gut. Aber irritiert bin ich noch von T::template. Das sagt, dass der abhängige Name ein Template ist, aber mich stört das etwas. Man würde ja sagen:
T::template C<int> z; // decreed template (Code von zwei verschiedenen Stackoverflow-Threads)in einer Funktion/Klasse/..., die T als Templateparameter erwartet. Es wird also gesagt: C ist ein Template. Also ist der Bezug des template-Schlüsselwortes quasi:
(T::template C)<int> z; // mir geht es hier nicht um korrekte Syntax, sondern wie gesagt um den BezugSchließlich ist C<int> kein Template, sondern ein Typ.
Wenn ich jetzt aber eine nested Klasse habe:
template<typename T> struct outer { template<typename U> struct inner { }; template<typename U> inner<U> createInner(); }; template<typename T> template<typename U> typename /* <-- dort! */ outer<T>::inner<U> outer<T>::createInner() { return inner(); }, so wird dort offensichtlich ein typename verlangt. An dieser Stelle soll ich also inner<U> als Typ klassifizieren, nicht aber inner als Template (was in Codefetzen 1 genau umgekehrt war). Das wirkt für mich inkonsequent, wobei es das mit Sicherheit nicht ist. Welches Detail führt dazu, dass man das hier anders ausweist?
Daran, dass da ein <U> steht, kann es nicht liegen, schließlich ist das in Codefetzen 1 ja auch der Fall. Auch bin ich nicht ganz sicher, welche Irritation für den Compiler man in Codefetzen 1 erledigt, was sollte C denn sein wenn kein Template? Eine Funktion? (ist Funktion ein anderes Tier als Wert/Typ/Template, oder zählt das als Typ? Wenn es als Typ zählt, könnte das doch auch wieder Rückgabe der Methode in Codefetzen 3 sein).
Vielen Dank im Voraus und beste Grüße!
-
Eisflamme schrieb:
An dieser Stelle soll ich also inner<T> als Typ klassifizieren, nicht aber inner als Template (was in Codefetzen 1 genau umgekehrt war).
Auch dort ist nat. das Schlüsselwort template erforderlich
template<typename T> template<typename U> typename outer<T>::template inner<U> outer<T>::createInner() { return inner<U>(); }oder
template<typename T> template<typename U> auto outer<T>::createInner() -> inner<U> { return inner<U>(); }
-
Ist das dann MSVC10s Fehler? Das (Variante 1 mit ::template) habe ich nämlich auch probiert, weil es mir sinnvoll erschien und dann:
error C2244: 'outer<T>::createInner': Keine Übereinstimmung für Funktionsdefinition mit vorhandener Deklaration gefunden
1> Definition
1> 'outer<T>::inner<U> outer<T>::createInner(void)'
1> Vorhandene Deklarationen
1> 'outer<T>::inner<U> outer<T>::createInner(void)'
1>
1>Fehler beim ErstellenLasse ich das template hingegen weg, so kompiliert es.
--
Variante2 gefällt mir natürlich am besten, hatte ich mir noch gar nicht angewöhnt, habe aber Mal einen Artikel über die "nachgelagerte return type"-Notation gelesen und mich gefreut, danke für den Hinweis.

-
Eisflamme schrieb:
Ist das dann MSVC10s Fehler?
Ja, template ist an dieser Stelle auch in C++03 erlaubt.
Ich korrigiere allerdings meine vorherige Aussage:
weder template noch typename sind an dieser Stelle erforderlich (aber einzeln oder zusammen zulässig), weil
outer<T>::inner ein member der current instantiation ist und damit bereits in der Templatedefinition
beim Namelookup gefunden und als Klassentemplate identifiziert werden kann.
Das ist allerdings für den Compiler schwer zu parsen (g++ weigert sich, clang hat kein Problem).
Da es beim flüchtigen Lesen auch verwirren kann, würde ich an dieser Stelle immer beide Schlüsselwörter verwenden.
-
Hi,
ah, verstehe! Erfordert das dieselben Parse-Fähigkeiten wie z.B. hier:
class Test { enum SomeEnum {A, B} enum; public: SomeEnum GetEnum() const; }; SomeEnum Test::GetEnum() const // will Test::SomeEnum { return enum; }was bei mir auch nie kompiliert? Da erscheint die auto-Notation für Rückgabetypen ja fast auch als Reaktion darauf in den Standard aufgenommen worden zu sein, dass die Compiler "unfähig" (will nicht urteilen) sind (wobei ich damit natürlich nicht meine, dass das der einzige Vorteil dieser Notation ist).
-
Eisflamme schrieb:
Hi,
ah, verstehe! Erfordert das dieselben Parse-Fähigkeiten wie z.B. hier:
class Test { enum SomeEnum {A, B} enum; public: SomeEnum GetEnum() const; }; SomeEnum Test::GetEnum() const // will Test::SomeEnum { return enum; }was bei mir auch nie kompiliert? Da erscheint die auto-Notation für Rückgabetypen ja fast auch als Reaktion darauf in den Standard aufgenommen worden zu sein, dass die Compiler "unfähig" (will nicht urteilen) sind (wobei ich damit natürlich nicht meine, dass das der einzige Vorteil dieser Notation ist).
Dieses Beispiel ist definitv ill-formed und nicht vergleichbar (wobei ich verstehe, dass es wie ein verwandtes Problem aussieht). SomeEnum wird hier durch unqualifiziertes Lookup gesucht, und der Stadard ist klar, das erst nach dem Funktionsnamen (also ab der öffnenden Klammer, folglich funktioniert es auch hier mit einem trailing Returntyp) dieses Lookup im Scope der Klasse stattfindet.
-
Achso, krass. Hat es besondere Gründe, dass beim Rückgabetyp kein ADL (hoffe, ich benutze den Begriff jetzt nicht falsch) greifen darf? Fände ich intuitiver und ich sehe jetzt keinen Nachteil, der nicht auch für Parameter gelten würde.
-
"nachgelagerte return type"
trailing-return-type.
weder template noch typename sind an dieser Stelle erforderlich (aber einzeln oder zusammen zulässig), weil
outer<T>::inner ein member der current instantiation ist und damit bereits in der Templatedefinition
beim Namelookup gefunden und als Klassentemplate identifiziert werden kann.Ich kann die Standardstelle nicht genau finden. Ich denke, es ist
[temp.dep.type]/1 schrieb:
A name refers to the current instantiation if it is
— in the definition of [...]a member of a class template [...] the injected-class-name (Clause 9) of the [...] nested class,// will Test::SomeEnum
Jo, aber natürlich wird
SomeEnumnicht gefunden. Ich finde, das würde auch sehr komisch sein, wenn das tatsächlich so funktionieren würde
Dieses Beispiel ist definitv ill-formed und nicht vergleichbar

dass die Compiler "unfähig" [...] sind
Ich denke, daran liegt es nicht. Wäre das so schwer, hätte man es aus dem Standard entfernt (wie
export).
Aber der Standard definiert es ähnlich:A trailing-return-type is most useful for a type that would be more complicated to specify before the
declarator-id.(camper meint übrigens die declarator-id)
Hat es besondere Gründe, dass beim Rückgabetyp kein ADL [...] greifen darf?
Ich dachte, ADL ist nur für Funktionsaufrufe und hat hier gar nix verloren
- aber wie so oft kann ich mich ja auch irren 
When the postfix-expression in a function call is an unqualified-id, other namespaces not considered
during the usual unqualified lookup (3.4.1) may be searched [...]
-
Okay, danke für die Anmerkungen.

Hast Du dich jetzt bzgl. ADL geirrt oder nicht? Klingt für mich doch, als hättest Du damit recht gehabt. Hat hier wohl wirklich überhaupt nichts verloren.
Trotzdem erscheint es mir nach wie vor intuitiv, dass er auch im Klassenscope sucht, wenn es um den Rückgabetypen geht. Für mich gehört der einfach zur Methode dazu und die Methode gehört für mich zur Klasse. Bin ich da allein?
-
Wenn ich mich nicht irre:
ADL ist ja dafür da, dass wenn du eine Funktion mit unqualifizierten Namen aufrufst, für den Funktionsnamen auch in anderen Namensräumen in festgelegter Reihenfolge gesucht wird, oder entsprechend für Operatorfunktionen - diese Namensräume werden durch die Argumente festgelegt (argument dependent name-lookup).Der Rückgabewert wird aber bereits bei der ersten Deklaration der Funktion geparst und steht damit vor jedem Funktionsaufruf fest, bzw. bei der Instantiierung der entsprechenden Spezialisierung einer Funktion.
Edit: Du Schlingel! Hast ja die Anmerkung wegeditiert :p
-
Ja, habe ADL Mal nachgeschlagen statt doof rumzuraten und fest gestellt, dass ich mir mein Gelaber hätte sparen können.
Bleibt also nach wie vor nur meine Anmerkung:
Trotzdem erscheint es mir nach wie vor intuitiv, dass er auch im Klassenscope sucht, wenn es um den Rückgabetypen geht. Für mich gehört der einfach zur Methode dazu und die Methode gehört für mich zur Klasse. Bin ich da allein?

-
Eisflamme schrieb:
Bleibt also nach wie vor nur meine Anmerkung:
Trotzdem erscheint es mir nach wie vor intuitiv, dass er auch im Klassenscope sucht, wenn es um den Rückgabetypen geht. Für mich gehört der einfach zur Methode dazu und die Methode gehört für mich zur Klasse. Bin ich da allein?
Leider habe ich, und wahrscheinlich auch du, zu wenig Erfahrung um darüber eine vernünftige Aussage machen zu können. Ich sage mal pauschal nein, weil ich es erstens, wie camper, unübersichtlich finde. Und zweitens da ich denke dass das öfters, gerade bei Programmen die davon Gebrauch machen, zu Mehrdeutigkeit führen kann.*
*Natürlich ist das nur eine Qualifizierung nötig um alles wieder ins Lot zu bringen - aber dann haben wir dadurch auch nichts gewonnen.
-
LOL
Gerade überarbeite ich ein altes Projekt von mir - und gerade ändere ich Zehn out-of-class Memberfunktions-Definitionen zu trailing-return-types.
-
Im Allgemeinen wird eine Name unmittelbar aufgelöst, sobald er auftritt (nicht zuletzt, weil häufig erst diese Auflösung es ermöglicht, den Rest des Codes richtig zu interpretieren).
Außerhalb einer Templatedeklaration sind die Regeln recht einfach:
Sobald irgendein Name (ohne vorangestelltes :: ) auftritt, wird er unqualifiziert gesucht, und zwar in dem Scope, in dem in dem er lexikalisch auftritt.
Wird eine entsprechende Entität (oder ggf. mehrere) gefunden, muss entsprechend kontextabhängig je nach Art der Entität weiter geparst werden.
Wird der Name nicht gefunden, muss die Stelle daraufhin untersucht werden, ob es sich (syntaktisch) um einen Funktionsaufruf handelt. Ist das der Fall und erlauben es die Argumente, so ist der Funktionsname per ADL zu suchen (ADL interessiert sich grundsätzlich nur für Funktionen oder Funktionstemplates - außerdem kommt es nat. ggf. zu ADL, wenn zuvor bereits eine Funktion gefunden wurde).Innerhalb einer Templatedeklaration sind diese Regeln auch einfach:
es sind die Gleichen (außer dass ein zweiter ADL-Pass ggf. erst bei der Instantiierung vorzunehmen ist).Erfolgt eine Deklaration eines Members außerhalb der Klasse, so ist der lexikalische Scope der Deklaration (die bei Membern auch eine Definition sein muss) zu beachten, nur die Teile, die der deklarator-id folgen befinden sich im Scope der Klasse.
namespace foo { struct bar { typedef int type; type fun(); }; } typedef char type; type foo::bar::fun(); // ill-formed - type ist ::typeSowohl im Nicht-Template- als auch im Templatefall muss also im Allgemeinen ein qualifizierter Bezeichner verwendet werden, wenn diese Syntax benutzt wird.
Wesentliche Unterschiede zwischen Nicht-Templates und Templates gibt es hingegen, wenn es um qualifizierte Bezeichner oder Memberzugriffe geht, und dieser Bezeichner von einem Templateparameter abhängt.
Im Wesentlichen:
Abhängige Bezeichner sind solche, die
1. von einem Templateparameter abhängig sind,
2. nicht Member der current instantiation sind (ich traue mich nicht das zu übersetzen, ich finde auch den englischen Begriff furchtbar)
Abhängige Bezeichner benötigen jeweils die Schlüsselworte typename oder template, wenn es sich um Typen bzw. Templates handelt, es sei denn der Kontext verbietet sie ausdrücklich. Bezeichner, die von einem Templateparameter abhängen aber keine Abhängigen Bezeichner sind (also Member der current instantitiation) dürfen, wenn der Kontext es erlaubt mit typename/template versehen werden (sowohl in C++03 als auch C++11) - und das ist gut so, weil man ggf. erst 10mal hinschauen muss, um die Unterscheidung machen zu können.
Und C++11 erlaubt typename/template ausserdem noch an fast allen anderen Stellen, an denen die Syntax es hergibt, insbesondere auch ausserhalb von Templates.
-
2. nicht Member der current instantiation sind (ich traue mich nicht das zu übersetzen, ich finde auch den englischen Begriff furchtbar)
Er ist auch nicht korrekt. Die Instantiierung ist ein Prozess. Was sie meinen ist die aktuell instantiierte Spezialisierung.
-
Sone schrieb:
2. nicht Member der current instantiation sind (ich traue mich nicht das zu übersetzen, ich finde auch den englischen Begriff furchtbar)
Er ist auch nicht korrekt. Die Instantiierung ist ein Prozess. Was sie meinen ist die aktuell instantiierte Spezialisierung.
Das macht ihn furchtbar aber nicht inkorrekt.
Er wird so kursiv im Standard an dieser Stelle verwendet, was auf einen explizit definierten Begriff verweist (schließe ich mal, ich vermute das wird in einem der in 1.2 genannten Standards (2382 hab ich nicht) so vorgegeben, in 1.6 wird nur gesagt, was Kursivschrift innerhalb von grammatikalischen Spezifikationen bedeuten soll).Deine Übersetzung geht in die richtige Richtungg, ist aber auch nicht korrekt. Weil des um Bezeichner innerhalb einer Templatedefinition geht. Nicht um die Instantiierung derselben (nat. ist da eine Verbindung).