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?


  • Mod

    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 durch std::decay entfernt... gibts da sowas ähnliches wie std::decay , welches const erhät oder muss ich mir das mittels is_const selbst zusammen basteln?


  • Mod

    happystudent schrieb:

    Allerdings würde ich gerne const -qualifizierer behalten

    Warum?

    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 von container 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 Typ std::vector<int> , daher müsste *(container.begin()) doch auch vom Typ int sein und nicht int& oder?

    Diese Logik kann ich momentan nicht nachvollziehen...


  • Mod

    happystudent schrieb:

    Wieso kann ich hier das decltype über die Deklaration von container 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 Typ std::vector<int> , daher müsste *(container.begin()) doch auch vom Typ int sein und nicht int& 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 ...


  • Mod

    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 als const deklariert (bzw. T ist const ) - in dem Fall gibt begin einen const_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.


  • Mod

    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 zu

    T& 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ämlich operator = ) aufgerufen.
    Und das darf man mit Rvalues:

    http://ideone.com/Bhzyma

    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.


  • Mod

    @hustbaer:

    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.
    Bei

    std::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.


  • Mod

    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

    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?


Log in to reply