decltype mit template-member variable
-
Hallo,
wieso geht der folgende Code nicht:
template <class T> struct foo { T container; decltype(*std::begin(container)) container_content; // Das geht nicht }; int _tmain(int argc, _TCHAR* argv[]) { foo<std::vector<int>> f; }
Ich erhalte hier die Fehlermeldung: "error C2784: "_Ty *std::begin(_Ty (&)[_Size])": template-Argument für "_Ty (&)[_Size]" konnte nicht von "unknown" hergeleitet werden."
Wieso "unknown"? Der Typ von "container" muss doch an der Stelle des
decltype
schon bekannt sein?
-
Muss ein Bug in Visual Studio sein, das sollte funktionieren, abgesehen davon, dass du wohl keine Referenz als Member haben möchtest:
typename std::decay<decltype(*std::begin(container))>::type
-
msvcbug schrieb:
Muss ein Bug in Visual Studio sein, das sollte funktionieren,
Na toll, das ist nervig
msvcbug schrieb:
abgesehen davon, dass du wohl keine Referenz als Member haben möchtest:
typename std::decay<decltype(*std::begin(container))>::type
Geht leider auch nicht, gleicher Fehler. Irgendwelche Alternativen verfügbar?
-
typename std::decay<decltype(*std::begin(std::declval<T>()))>::type
-
camper schrieb:
typename std::decay<decltype(*std::begin(std::declval<T>()))>::type
Das klappt schonmal, danke
Allerdings würde ich gerne
const
-qualifizierer behalten und diese werden ja durchstd::decay
entfernt... gibts da sowas ähnliches wiestd::decay
, welchesconst
erhät oder muss ich mir das mittelsis_const
selbst zusammen basteln?
-
happystudent schrieb:
Allerdings würde ich gerne
const
-qualifizierer behaltenWarum?
In dem Fall schreib' einfach
typename std::remove_reference<decltype(*std::begin(std::declval<T>()))>::type
(Oder seit C++1Y
std::remove_reference_t<decltype(*std::begin(std::declval<T>()))>
)
-
Arcoth schrieb:
In dem Fall schreib' einfach
typename std::remove_reference<decltype(*std::begin(std::declval<T>()))>::type
Hm, das geht komischerweise nicht, ich bekomme hier die Fehlermeldung "error C2512: 'foo<std::vector<int,std::allocator<_Ty>>>': Kein geeigneter Standardkonstruktor verfügbar". Auch ein Bug? Mit
std::decay
funktionierts auf jeden Fall...Aber ich hab hier auch gleich noch ein paar neue Fragen. Dieser Code:
template <class T> struct foo { void bar() { decltype(*(container.begin())) val; } T container; }; int _tmain(int argc, _TCHAR* argv[]) { foo<std::vector<int>> f; f.bar(); }
- Wieso kann ich hier das
decltype
über die Deklaration voncontainer
schreiben, bei meinem vorherigen Beispiel ging das nicht. - Wieso muss ich überhaupt das
decltype
unterhalb der Deklaration eines Members schreiben auf den dieses sich bezieht? Innerhalb einer Klasse kann ich doch sonst auch von überall auf alle Member zugreifen ohne dass ich darauf achten muss wo diese deklariert sind? - Wieso bekomme ich bei diesem Beispiel einen Compile Fehler "error C2530: 'val': Verweise müssen initialisiert werden"? Ich hab hier doch gar keine Verweise?
container
ist vom Typstd::vector<int>
, daher müsste*(container.begin())
doch auch vom Typint
sein und nichtint&
oder?
Diese Logik kann ich momentan nicht nachvollziehen...
- Wieso kann ich hier das
-
happystudent schrieb:
Wieso kann ich hier das
decltype
über die Deklaration voncontainer
schreiben, bei meinem vorherigen Beispiel ging das nicht.Weil du dich dort innerhalb einer Funktionsdefinition befindest.
happystudent schrieb:
Wieso muss ich überhaupt das
decltype
unterhalb der Deklaration eines Members schreiben auf den dieses sich bezieht? Innerhalb einer Klasse kann ich doch sonst auch von überall auf alle Member zugreifen ohne dass ich darauf achten muss wo diese deklariert sind?Eine Klasse gilt als vollständig definiert innerhalb des Funktionskörpers von Memberfunktionen, in Defaultargumenten, Exceptionspezifikationen und Memberinitialisierern. An diesen Stellen können also auch Member verwendet werden, die lexikalisch erst später deklariert werden. Ansonsten gilt wie üblich, dass Namen erst nach ihrer Deklaration verwendet werden können.
happystudent schrieb:
Wieso bekomme ich bei diesem Beispiel einen Compile Fehler "error C2530: 'val': Verweise müssen initialisiert werden"? Ich hab hier doch gar keine Verweise?
container
ist vom Typstd::vector<int>
, daher müsste*(container.begin())
doch auch vom Typint
sein und nichtint&
oder?Der Ausdruck *(container.begin()) hat den Typ int und ist ein lvalue (du könntest z.B. *(container.begin())=0 schreiben). decltype liefert eine Referenz, wenn der Ausdruck, auf dass es angwandt wird, ein lvalue ist.
-
Ok, vielen Dank, das erklärt einiges
Allerdings
camper schrieb:
Der Ausdruck *(container.begin()) hat den Typ int und ist ein lvalue (du könntest z.B. *(container.begin())=0 schreiben). decltype liefert eine Referenz, wenn der Ausdruck, auf dass es angwandt wird, ein lvalue ist.
funktioniert bei mir keine dieser Möglichkeiten:
decltype(*(container.begin()) = 0) val; // error C2679: Binärer Operator '=': Es konnte kein Operator gefunden werden, der einen rechtsseitigen Operanden vom Typ 'int' akzeptiert decltype(*(container.begin() = 0)) val; // error C2530: 'val': Verweise müssen initialisiert werden decltype(*(container.begin())) val = 0; // error C2440: 'Initialisierung': 'int' kann nicht in 'int &' konvertiert werden
Ist das jetzt auch wieder auf Bugs zurückzuführen? Weil wenn ja dann glaub ich verzichte ich in Zukunft auf
decltype
...
-
Ist das jetzt auch wieder auf Bugs zurückzuführen?
decltype(*(container.begin()) = 0) val;
Richtig, falls der Elementtyp des Containers eine derartige Zuweisung zulässt.
decltype(*(container.begin() = 0)) val;
Das ist Unsinn und kompiliert zu Recht nicht - selbst wenn der Iteratortyp des Containers ein Zeiger ist. Du kannst einem rvalue nichts zuweisen.
decltype(*(container.begin())) val = 0;
Ebenfalls Unsinn außer
container
ist alsconst
deklariert (bzw.T
istconst
) - in dem Fall gibt begin einenconst_iterator
zurück. Das Dereferenzieren gibt eine Const-Referenz, die du mit einem rvalue initialisieren kannst.
-
Arcoth schrieb:
Ist das jetzt auch wieder auf Bugs zurückzuführen?
decltype(*(container.begin()) = 0) val;
Richtig, falls der Elementtyp des Containers eine derartige Zuweisung zulässt.
Damn, da hab ich grade das Feature gelernt und für cool befunden und kanns schon nicht mehr benutzen.
Arcoth schrieb:
decltype(*(container.begin() = 0)) val;
Das ist Unsinn und kompiliert zu Recht nicht - selbst wenn der Iteratortyp des Containers ein Zeiger ist. Du kannst einem rvalue nichts zuweisen.
Schon, aber die Fehlermeldung dazu ist doch Quatsch, oder? Aber ist auch egal, hängt vermutlich auch mit dem obigen Bug zusammen.
-
happystudent schrieb:
Schon, aber die Fehlermeldung dazu ist doch Quatsch, oder? Aber ist auch egal, hängt vermutlich auch mit dem obigen Bug zusammen.
Also Quatsch kann man das denke ich nicht nennen.
Das einzige was fehlt, wäre der Hinweis dass
'int'
hier eben keine Lvalue ist, und daher nicht nach'int&'
konvertiert werden kann.
Davon abgesehen... ist halt knapp gehalten, aber "'Initialisierung': XXX" ist doch irgendwie klar: es geht um eine Initialisierung (nämlich die von'val'
), und die funktioniert nicht weil XXX.
-
Das einzige was fehlt, wäre der Hinweis dass 'int' hier eben keine Lvalue ist, und daher nicht nach 'int&' konvertiert werden kann.
Du redest von der falschen Fehlermeldung. Uns geht es um die Zuweisung des Iterators, nicht eines Elementtyps.
-
*facepalm* Ja, falsche Zeile geguckt. Sorry
Also diese da, right?
decltype(*(container.begin() = 0)) val; // error C2530: 'val': Verweise müssen initialisiert werden
Ich vermute mal dass hier das nonstandard Verhalten von MSVC zuschlägt (*), und die Zuweisung des Iterators noch funktioniert (obwohl sie nicht sollte).
Dann zerfällt das was übrig bleibt zuT& val; // error C2530: 'val': Verweise müssen initialisiert werden // Englisch: error C2530: 'val' : references must be initialized
...und auf einmal macht die Fehlermeldung auch wieder Sinn.
*:
MSVC 2005 (2012/2013 hab ich grad nicht zur Hand) compiliert sowas ohne zu murren:std::vector<int> vec; vec.begin() = vec.end(); // Ja, nur ein "="!
EDIT: MSVC hast dieses nonstandard Verhalten zwar, spielt hier aber keine Rolle, weil es um UDTs geht. Und die Zuweisung von UDTs ist der Aufruf von
operator =
, und der Aufruf von Memberfunktionen auf Rvalues ist erlaubt.
(OK, der Iterator könnte ein einfacher Zeiger sein, aber er muss nicht. Und wenn er nicht ist, ist er ein UDT, und es sollte das gelten was ich geschrieben habe. Wenn ich mich nicht schon wieder irre. Ach, C++ ist zu kompliziert ;))
-
Es wird ja keine Rvalue zugewiesen.
Es wird ne Memberfunktion (nämlichoperator =
) aufgerufen.
Und das darf man mit Rvalues:Was natürlich beknackt ist, aber ist halt so.
Oder täuscht mich jetzt meine Erinnerung, und GCC hat zufällig gleichzeitig nen Bug der das erlaubt? Würde mich schon wundern.
-
Oder täuscht mich jetzt meine Erinnerung, und GCC hat zufällig gleichzeitig nen Bug der das erlaubt?
Nein, das ist schon richtig so - ich habe mich etwas irreführend ausgedrückt. Für die eingebauten Operatoren (welche für Zeiger aufgerufen werden) gilt, dass sie nur lvalues nehmen.
Aber wenn der Iterator kein Zeiger ist, dann sollte eine Zuweisung à la= 0
auch gar nicht funktionieren. Ist Schwachsinn.
-
Hmmmmmmm...
Da hast du jetzt wieder Recht.
Beistd::vector<int> vec; vec.begin() = 0;
bekomme ich mit MSVC 2005
error C2679: binary '=' : no operator found which takes a right-hand operand of type 'int' (or there is no acceptable conversion)
Ich vermute also dass er wirklich die Zuweisung akzeptiert - warum auch immer.
Müsste ich mit MSVC 2013 nochmal probieren. Vielleicht einfach mit dem Debugger reinsteppen falls er es compiliert, dann hätte man zumindest die Funktion schnell gefunden die er da nimmt.
ps: Dass es Schwachsinn ist ist eh klar -- egal ob und warum es funktioniert. Ich frag mich nur wie die Fehlermeldung zustande kommt.
-
Ich frag mich nur wie die Fehlermeldung zustande kommt.
Naja: Er deklariert den Member als eine Referenz und sieht dann dass in einem Konstruktor die Referenz uninitialisiert bleibt. (Der Default-Konstruktor wird bei einem Referenz-Member ohne Initializer automatisch als
deleted
definiert).Die entscheidende Frage ist mit welchem Typ er die Referenz deklariert.
Ich hätte gesagt, einem Iterator der ein Klassenobjekt ist kann man schlecht
0
zuweisen, daher wird bei MSVC der Iterator ein Zeiger sein. Also erlaubt er wohl bei Zeigern diese Zuweisung (welche natürlich nicht standardkonform ist).Das wäre die intuitive Erklärung, nur leider ist sie offenbar falsch:
error C2679: binary '=' : no operator found which takes a right-hand operand of type 'int' (or there is no acceptable conversion)
Oder hat MSVC mittlerweile einen Zeiger d'raus gemacht?
-
Arcoth schrieb:
Ich frag mich nur wie die Fehlermeldung zustande kommt.
Naja: Er deklariert den Member als eine Referenz und sieht dann dass in einem Konstruktor die Referenz uninitialisiert bleibt. (Der Default-Konstruktor wird bei einem Referenz-Member ohne Initializer automatisch als
deleted
definiert).DER Teil ist mir klar, aber das kann ja nur der Grund sein wenn der Compiler die Zuweisung erstmal gefressen hat.
Und ich frag' mich eben warum er die frisst.Ich hätte gesagt, einem Iterator der ein Klassenobjekt ist kann man schlecht
0
zuweisen, daher wird bei MSVC der Iterator ein Zeiger sein. Also erlaubt er wohl bei Zeigern diese Zuweisung (welche natürlich nicht standardkonform ist).Nö, bei Zeigern erlaubt er es nicht.
error C2106: '=' : left operand must be l-value
MSVC erlaubt nur (fälschlicherweise) Rvalues an Lvalue Referenzen zu binden. Bzw. weiss ich nicht ob das in 2012/2013 immer noch so ist, bis 2008 war es sicher so.Naja, ich hab Studio 2012 und 2013 zu Hause installiert, da kann ich das mal ausprobieren.
ps: MSVC hat per Default auch "checked iterators", also keine Zeiger als Vektor-Iteratoren.
-
happystudent schrieb:
decltype(*(container.begin()) = 0) val; // error C2679: Binärer Operator '=': Es konnte kein Operator gefunden werden, der einen rechtsseitigen Operanden vom Typ 'int' akzeptiert decltype(*(container.begin() = 0)) val; // error C2530: 'val': Verweise müssen initialisiert werden decltype(*(container.begin())) val = 0; // error C2440: 'Initialisierung': 'int' kann nicht in 'int &' konvertiert werden
Ich glaube du hast einfach die Fehlermeldungen von Zeile 1 und 2 vertauscht. Ich bekomme mit MSVC 2013 folgendes:
decltype(*(container.begin()) = 0) val; // error C2530: 'val' : references must be initialized decltype(*(container.begin() = 0)) val2; // error C2679: binary '=' : no operator found which takes a right-hand operand of type 'int' (or there is no acceptable conversion) decltype(*(container.begin())) val3 = 0; // error C2440: 'initializing' : cannot convert from 'int' to 'int &'
1: Iterator holen und dereferenzieren, und auf die so erhaltene Referenz ne Zuweisung => Ergebnistyp der Zuweisung ist ne Referenz, und die müssen initialisiert werden. Fehlermeldung korrekt.
2: Iterator holen und nen Nuller-Literal zuweisen. Ginge nur wenn der Iterator ein Zeiger wäre, was er nicht sein muss, und in diesem Fall eben auch nicht ist. Fehlermeldung korrekt.
(OK, es ginge auch wenn die Iterator Implementierung eine nicht vorgeschriebene aber vermutlich auch nicht verbotene Extension hätte, die das erlaubt. Wobei ich nicht wüsste wozu das gut sein sollte.)3: Iterator holen und dereferenzieren. Typ ist ne Lvalue-Referenz, und an die kann man keinen Literal (Rvalue) binden. Fehlermeldung korrekt.
Übersehe ich was?