RValue-Reference in VC 2010



  • Hallo

    dieses Stück Code kompiliert mit VC 2010 nicht, mit GCC 4.5 schon.

    template<typename T>
    struct S
    {};
    
    template<typename T>
    struct S<T&>
    {};
    
    template<typename T>
    struct S<T&&>
    {};
    
    template<typename T>
    void t(T&&)
    {
    	S<T&&> s;
    }
    
    int main()
    {
    	t(main);
    }
    
    1>main.cpp(16): error C2752: 'S<T>' : more than one partial specialization matches the template argument list
    1>          with
    1>          [
    1>              T=int (__cdecl &)(void)
    1>          ]
    1>          main.cpp(11): could be 'S<T&&>'
    1>          main.cpp(7): or       'S<T&>'
    1>          main.cpp(21) : see reference to function template instantiation 'void t<int(__cdecl &)(void)>(T)' being compiled
    1>          with
    1>          [
    1>              T=int (__cdecl &)(void)
    1>          ]
    1>
    1>Build FAILED.
    

    Ist VC hier falsch?


  • Mod

    template<typename T> struct S<T&>
    

    ist in meinen Augen spezieller als

    template<typename T> struct S<T&&>
    

    so dass hier keine Mehrdeutigkeit vorliegen dürfte. Möglicherwiese kommt VC nicht formal mit Funktionsrvalues zurecht.



  • camper schrieb:

    template<typename T> struct S<T&>
    

    ist in meinen Augen spezieller als

    template<typename T> struct S<T&&>
    

    so dass hier keine Mehrdeutigkeit vorliegen dürfte. Möglicherwiese kommt VC nicht formal mit Funktionsrvalues zurecht.

    Wenn das erste "spezieller" sein soll, klingt das so, als wäre es ein Spezialfall des Zweiten. Aber spätestens seit einem Jahr ("Rvalue references version 2.0") haben diese beiden Referenz-Typen nicht viel gemein:

    int i = 23;
    int &  r1 = i;   // OK
    int && r2 = i;   // ill-formed
    
    int &  r3 = i+1; // ill-formed
    int && r4 = i+1; // OK
    

    Konnte man vorher && noch als alles-bindende Referenz verstehen, trifft dies nicht mehr zu. eine Rvalue-Referenz lässt sich jetzt nur noch mit Rvalues initialisieren. Eine kompakte tabellarische Übersicht bzgl Initialisierung von Referenzen gibt es hier. Der GCC (zumindest bis Version 4.4) akzeptiert die Initialisierung von r2 noch. Das ist aber nicht mehr im Sinne des kommenden Standards.

    Für mich sieht das Verhalten des Microsoft Compilers bei diesem Stück Code nach einem Bug aus.

    kk



  • Upps, ich seh ja jetzt erst, dass es sich hier um Funktions-Referenzen handelt. Da kann ich spontan nicht viel zu sagen. Ich denke, Funktionen werden als lvalues behandelt. Bin mir aber nicht sicher.



  • Ihr redet ja so, ob der neue Standard schon aktuell sei 😮


  • Administrator

    Zeus schrieb:

    Ihr redet ja so, ob der neue Standard schon aktuell sei 😮

    Naja, es gibt ja einen aktuellen Entwurf, den man sich herunterladen kann. Und viel wird darin wohl nicht mehr verändert werden. Wenn ich mich recht erinnere gibt es nur noch 1-2 Konferenzen und danach wird es der ISO zur Standardisierung übergeben, was dann nochmals ein halbes bis ganzes Jahr dauern dürfte. Der Standard ist somit fast fertig. Oder wie man dies immer so schön sagt: Soon™.

    Grüssli


  • Mod

    krümelkacker schrieb:

    Wenn das erste "spezieller" sein soll, klingt das so, als wäre es ein Spezialfall des Zweiten.

    Spezieller im Sinne von 14.5.5.2 des aktuellen Entwurfs in Hinblick auf die partielle Ordnung von Templatespezialisierungen.

    Dank der Kombinationsregel in 8.3.2/6 schluckt eine Funktion

    template <typename T> void foo(T&&)
    

    jede Art von Argument (l- oder rvalue), wobei T im Falle eines lvalue-Arguments als Referenz deduziert wird - das ist ja auch die Grundlage für perfektes Forwarding.

    Umgekehrt schluckt

    template <typename T> void foo(T&)
    

    wirklich nur lvalues - und ist somit spezieller (sollte jedenfalls, der neue Abschnitt 14.8.2.4 Absatz 5 ist möglicherweise ein Problem). Da die partielle Ordnung von Templatespezialisierungen auf die von Überladenen Funktionstemplates verweist, muss das für obiges Problem entsprechend gelten.

    krümelkacker schrieb:

    int i = 23;
    int &  r1 = i;   // OK
    int && r2 = i;   // ill-formed
    
    int &  r3 = i+1; // ill-formed
    int && r4 = i+1; // OK
    

    Alles richtig, aber hier irrelevant.

    template <typename T> void foo(T&);
    template <typename T> void bar(T&&);
    int i;
    foo(i); // ok, foo<int>
    foo(1); // Fehler
    bar(i); // ok, bar<int&>
    bar(1); // ok, bar<int>
    


  • camper schrieb:

    Dank der Kombinationsregel in 8.3.2/6 schluckt eine Funktion

    template <typename T> void foo(T&&)
    

    jede Art von Argument (l- oder rvalue), wobei T im Falle eines lvalue-Arguments als Referenz deduziert wird - das ist ja auch die Grundlage für perfektes Forwarding.

    Ja. Aber das hat meinem Verständnis nach nichts mit der Deduktion beim Auffinden der richtigen Spezialisierung zu tun. Ich hatte mal eine ähnliche Frage, und zwar: "Kann bei folgender concept_map T eine Lvalue-Referenz sein?"

    concept RvalueReference<typename T> {}
    
    template<typename T>
    concept_map RvalueReference<T&&> {}
    

    und Doug Gregor verneinte das. Ich denke, dass es wegen der Ähnlichkeit zur partiellen Spezialisierung von Klassentemplates sich hier auch so verhält. Dann wäre die eine Spezialisierung nur für Lvalue-Referenzen, die andere nur für Rvalue-Referenzen da. Das hieße auch, dass das eine nicht spezieller ist als das andere ist. Selbst wenn die Spezialisierung mit T& weggelassen wird, sollte die Spezialisierung mit <T&&> immer noch nur für Rvalue-Referenzen herhalten.

    Perfect forwarding funktioniert wegen zwei Sonderregeln: (a) reference collapsing (b) Die Deduktionsregel. Die Deduktionsregel müsste nur für Funktionsaufrufe bei Funktionstemplates gelten und hier in diesem Fall keine Rolle spielen.

    kk



  • camper schrieb:

    krümelkacker schrieb:

    Alles richtig, aber hier irrelevant.

    template <typename T> void foo(T&);
    template <typename T> void bar(T&&);
    int i;
    foo(i); // ok, foo<int>
    foo(1); // Fehler
    bar(i); // ok, bar<int&>
    bar(1); // ok, bar<int>
    

    Alles richtig, aber hier irrelevant.
    😉

    ... oder ich vertue mich grad hier ganz doll

    kk


  • Mod

    krümelkacker schrieb:

    Die Deduktionsregel müsste nur für Funktionsaufrufe bei Funktionstemplates gelten und hier in diesem Fall keine Rolle spielen.

    14.8.2.1/3 - Da hast du offenbar recht.

    Dann liegt VC allerdings erst recht falsch, denn S<T&&> kann kein Kandidat für ein Argument des Typs int(&)() sein.



  • krümelkacker schrieb:

    int && r4 = i+1; // OK
    

    Hätte zu dieser Zeile grad 2 Fragen:

    1. Was passiert hier genau ?

    2. Auf was referenziert r4 nun?



  • Dweb schrieb:

    krümelkacker schrieb:

    int && r4 = i+1; // OK
    

    Hätte zu dieser Zeile grad 2 Fragen:
    1. Was passiert hier genau ?
    2. Auf was referenziert r4 nun?

    i+1 ist ein rvalue. Damit bezieht es sich erstmal auf den Wert/das Ergebnis und kein "Objekt" im Sinne des C++ Standards. Aber Rvalue-Referenzen sind ja dazu dar, dass man auf temporäre Objekte verweisen kann. Es wird also ein int-Objekt erzeugt und r4 so initialisiert, dass es auf dieses int-Objekt verweist. Wegen einer Sonderregel wird die Lebenszeit des int-Objektes verlängert, so dass die Referenz auch danach noch gültig ist.

    Dieser spezieller Code ist natürlich sinnfrei. In Verbindung mit auto aber auch nicht ganz unpraktisch:

    {
      auto&& range = …;
      for (auto itr_ = begin(range), end_ = end(range);
           itr_ != end_;
           ++itr_)
      {
        int dings = *itr_;
      }
    }
    

    Jenachdem, was für "…" eingesetzt wird, kann range eine Rvalue-Referenz oder eine Lvalue-Referenz sein. So wird dann auch folgendes möglich:

    vector<int> quelle();
    …
    {
      …
      for (int dings : quelle()) {
        cout << ' ' << dings;
      }
      …
    }
    

    ohne dass der vektor unnötig kopiert wird oder quelle() mehr als einmal aufgerufen wird...

    camper schrieb:

    Umgekehrt schluckt

    template <typename T> void foo(T&)
    

    wirklich nur lvalues

    oder auch const rvalues.



  • krümelkacker schrieb:

    i+1 ist ein rvalue. Damit bezieht es sich erstmal auf den Wert/das Ergebnis und kein "Objekt" im Sinne des C++ Standards. Aber Rvalue-Referenzen sind ja dazu dar, dass man auf temporäre Objekte verweisen kann. Es wird also ein int-Objekt erzeugt und r4 so initialisiert, dass es auf dieses int-Objekt verweist. Wegen einer Sonderregel wird die Lebenszeit des int-Objektes verlängert, so dass die Referenz auch danach noch gültig ist.

    danke dir 😉



  • Joachim, regestrier dich mal!


  • Mod

    krümelkacker schrieb:

    camper schrieb:

    Umgekehrt schluckt

    template <typename T> void foo(T&)
    

    wirklich nur lvalues

    oder auch const rvalues.

    Das wäre mir neu.

    Ich kenne nur, dass

    template <typename T> void foo(const T&)
    

    auch rvalues nimmt.



  • Man kann den Templateparameter T vorgeben oder sich darauf verlassen, dass er zu "const U" für einen Klassentyp U deduziert wird, siehe:

    #include <iostream>
    #include <string>
    
    template<class T>
    void ausgabe(T & ref)  // <-- hier steht kein const
    {
      std::cout << ref << '\n';
    }
    
    const std::string rvalue()
    {
      return "dings";
    }
    
    int main()
    {
      ausgabe(rvalue());
    }
    

    kk


Anmelden zum Antworten