std::move einsetzen
-
krümelkacker schrieb:
Oh nee ... Das ist völliger Quatsch.
Noe. Sieht mir voellig legitim aus.
krümelkacker schrieb:
1. string&& is not the return type you are looking for!
Doch.
krümelkacker schrieb:
2. Die Signatur hat mit perfect forwardings nichts zu tun
Korrekt. Hier braucht man std::move.
krümelkacker schrieb:
4. std::begin/std::end geben im Falle eines Rvalue strings
const_iterator
en zurück.'text' ist ein LValue.
krümelkacker schrieb:
5. Wo soll denn das neue Objekt herkommen, wenn das Argument ein Lvalue war?
Der Overload funktioniert eben nur mit RValues.
Edit: NVM, ging um perfect forwarding. War zur voreilig mit antworten.
Edit 2: Um noch was sinnvolles in diesem Post zu schreiben: Meines wissens nach ist ein Move beim returnen einer lokalen Variable garantiert, wenn T einen move ctor hat. Nix RVO/NRVO also.
-
Eisflamme schrieb:
Eine Frage fällt mir noch direkt ein:
Schreibst du ein Funktions-Template, was einen Funktionsparameter vom Typ T&& entgegen nimmt, wobei T vom Compiler deduziert wird, dann wird T zu einer Lvalue-Referenz deduziert genau dann wenn das Funktionsargument ein Lvalue war.
Bringt das noch irgendeine Information dazu, die nicht sowieso durch Reference Collapsing impliziert ist? Wahrscheinlich steckt hier gerade noch ein Detail drin (also im ganzen Absatz), ich finde es aber noch nicht (oder ich habe wieder irgendeinen Logikfehler).
Wenn ich dir Frage richtig verstehe, ist dir nicht klar, wozu es diese Deduktionsregel gibt. In C++11 sieht es aktuell mit dieser Regel so aus:
template<class T> void foo(T&& x); int main() { int i = 1729; foo(1234); // T=int, T&&=int&&, x ist eine Rvaluereferenz, alles ok foo(i); // T=int&, T&&=int&, x ist eine Lvaluereferenz, alles ok }
Würdest du diese Deduktionsregel rausschmeißen, dann hättest du auch bei foo(i) nur T=int und x wäre damit eine Rvaluereferenz. x kannst du aber dann nicht mit i initialisieren. Das ist nicht erlaubt. Du hättest dann einen Compile-Fehler. Du könntest zwar immer noch foo<int&>(i) schreiben, was dann Dank Reference Collapsing auch funktioniert. Aber es ist ja gerade der Witz an Perfect Forwarding, dass man keine Templateparameter explizit angeben muss.
Eisflamme schrieb:
Und bei expliziter Typangabe statt Deduktion wäre die Aussage ja trotzdem noch korrekt, oder? (aber wahrscheinlich nicht erwähnenswert, weil das eh klar ist?)
Da die obere Aussage sich auf Deduktion bezieht und du die Deduktion per expliziter Angabe übergehst, sehe ich nicht, wie ich sinnvoll auf diese Frage antworten kann. Du kannst aber natürlich T manuell wählen, z.B.
foo<const int&>(1234); // T=const int&, T&&=const int&, // foo::x ist eine Lvaluereferenz auf const
Aber wenn es sich vermeiden lässt, sollte man lieber die Finger von expliziten Template-Argumenten bei Funktionstemplates lasen.
Eisflamme schrieb:
Da muss man aber echt höllisch aufpassen und das Verständnis basiert hier auch auf sehr viel Liebe zum Detail. Ist das einfach so oder denke ich vielleicht falsch?
Nee, ist leider ein kompliziertes Thema. Habe auch lange gebraucht, das zu verstehen.
-
Ja, aber als Überladung für RValue-Referenzen wäre die Funktion doch auch Käse, oder? Ich meine, move statt forward ist doch genauer (wobei forward auch klappen sollte, wenn das Template-Argument hinzukommt, richtig?).
Und da fällt mir noch eine Frage ein: Wieso braucht forward denn eine explizite Typangabe? Wenn die Funktion T ohne & und && braucht, könnte sie das doch selbst über remove_reference rausfinden oder nicht?
-
Eisflamme schrieb:
Und da fällt mir noch eine Frage ein: Wieso braucht forward denn eine explizite Typangabe? Wenn die Funktion T ohne & und && braucht, könnte sie das doch selbst über remove_reference rausfinden oder nicht?
Benannte RValue Referenzen sind LValues.
-
Kellerautomat schrieb:
krümelkacker schrieb:
1. string&& is not the return type you are looking for!
Doch.
Nö. Das Recycling von temporären Objekten ist zwar löblich aber die Information, dass es sich um ein kurzlebiges Objekt handelt, geht mit diesem Rückgabetyp verloren, so dass du z.B. hier unedfiniertes Verhalten hervorrufen würdest
for (auto c : flip("hello world")) { cout << c; }
weil der temporäre String hier schon zu früh zerstört würde. Die Iteratoren holt sich das for-range hier von einer baumelnden Referenz ==> U.B.
Kellerautomat schrieb:
krümelkacker schrieb:
4. std::begin/std::end geben im Falle eines Rvalue strings
const_iterator
en zurück.'text' ist ein LValue.
Er schrieb std::begin(std::forward(text)). Die richtige Anwendung von forward hätte hier ein Rvalue geliefert.
Kellerautomat schrieb:
krümelkacker schrieb:
5. Wo soll denn das neue Objekt herkommen, wenn das Argument ein Lvalue war?
Der Overload funktioniert eben nur mit RValues.
Da es eine perfect forward lösung sein wollte, hätte sie auch Lvalues geschluckt, die dann irrtümlicherweise verändert würden, obwohl der Aufrufer das gar nicht möchte...
Kellerautomat schrieb:
Edit 2: Um noch was sinnvolles in diesem Post zu schreiben: Meines wissens nach ist ein Move beim returnen einer lokalen Variable garantiert, wenn T einen move ctor hat. Nix RVO/NRVO also.
Es wird in diesem Fall nur gemoved, wenn der Compiler zu dämlich ist, NRVO durchzuführen. Bei pass-by-value Funktionsparametern hast du aber recht, da dort NRVO nicht mehr erlaubt ist. Diesen Fall konnte eh kein Compiler optimieren.
-
Kellerautomat schrieb:
Eisflamme schrieb:
Und da fällt mir noch eine Frage ein: Wieso braucht forward denn eine explizite Typangabe? Wenn die Funktion T ohne & und && braucht, könnte sie das doch selbst über remove_reference rausfinden oder nicht?
Benannte RValue Referenzen sind LValues.
Dementsprechend wüsste forward nicht, ob du eine benannte Lvalue- oder benannte Rvalue-Referenz reinsteckst, weil beide Ausdrücke Lvalues sind.
Aber ich habe neulich das hier gesehen:
#define FORWARD(x) std::forward<decltype(x)>(x)
Ob ich das toll finden soll, weiß ich aber noch nicht.
-
krümelkacker schrieb:
Nö. Das Recycling von temporären Objekten ist zwar löblich aber die Information, dass es sich um ein kurzlebiges Objekt handelt, geht mit diesem Rückgabetyp verloren, so dass du z.B. hier unedfiniertes Verhalten hervorrufen würdest
for (auto c : flip("hello world")) { cout << c; }
weil der temporäre String hier schon zu früh zerstört würde. Die Iteratoren holt sich das for-range hier von einer baumelnden Referenz ==> U.B.
Noe. Die Lebenszeit des Objekts wird in den Scope der Schleife verlaengert.
krümelkacker schrieb:
Kellerautomat schrieb:
Korrekt. Hier braucht man std::move.
So ein Quatsch.
Natuerlich tut man das.
So ein Quatsch.krümelkacker schrieb:
Kellerautomat schrieb:
'text' ist ein LValue.
Er schrieb std::begin(std::forward(text)). Die richtige Anwendung von forward hätte hier ein Rvalue geliefert.
Die beiden forwards hab ich in der Tat uebersehen.
krümelkacker schrieb:
Kellerautomat schrieb:
krümelkacker schrieb:
5. Wo soll denn das neue Objekt herkommen, wenn das Argument ein Lvalue war?
Der Overload funktioniert eben nur mit RValues.
...und mutiert irrtümlicher weise Lvalue-Argumente obwohl der Aufrufer das gar nicht möchte...
Und du behauptest jetzt ernsthaft, dass dieses Fuktionstemplate keinen Quatsch ist? Wow!
Der Aufruf mutiert gar nichts, wenn er mit LValues *nicht kompiliert*. Und wo du hier ein Template siehst, weiss ich nicht.
-
Hi,
krümelkacker:
Zu Antwort 1)
Okay, alles verstanden, meine Frage war falsch gestellt, weil ich den Fall, den Du mit Deinem Code skizziert hast, gar nicht gesehen hatte. Die darauf folgende Frage erübrigt sich dann natürlich.Nee, ist leider ein kompliziertes Thema. Habe auch lange gebraucht, das zu verstehen.
Beruhigend zu wissen.
Zu Antwort 2)
krümelkacker schrieb:
Kellerautomat schrieb:
Eisflamme schrieb:
Und da fällt mir noch eine Frage ein: Wieso braucht forward denn eine explizite Typangabe? Wenn die Funktion T ohne & und && braucht, könnte sie das doch selbst über remove_reference rausfinden oder nicht?
Benannte RValue Referenzen sind LValues.
Dementsprechend wüsste forward nicht, ob du eine benannte Lvalue- oder benannte Rvalue-Referenz reinsteckst, weil beide Ausdrücke Lvalues sind.
Okay, das hat bei mir lange gedauert, weil ich kurz vergessen hatte, dass T ja auch wieder Wert-Kategorien beinhaltet, aber jetzt hab ich das gerafft, super.
-
Kellerautomat schrieb:
Noe. Die Lebenszeit des Objekts wird in den Scope der Schleife verlaengert.
Nicht, wenn dein flip eine Rvaluereferenz zurück gibt! Guck mal nach, wie for-range im Standard definiert ist und in welchen Fällen die Lebenszeit eines Objekts an die der gebundenen Referenz angepasst wird.
Vielleicht glaubst du ja auch diesem Test-Programm. Was dir auffallen sollte ist, dass im zweiten Fall das Objekt zerstört wird bevor die Ausgabe stattfindet.
Referenzen auf etwas zurück geben, was sich womöglich auf ein temporäres Objekt bezieht ist eher eine schlechte Idee, wie man sehen kann. Nicht ohne Grund wurde ein früherer C++ Standard Entwurf korrigiert, in dem
string&& opeartor+(string&& lhs, string const& rhs); string&& opeartor+(string const& lhs, string&& rhs); string&& opeartor+(string&& lhs, string&& rhs);
durch
string opeartor+(string&& lhs, string const& rhs); string opeartor+(string const& lhs, string&& rhs); string opeartor+(string&& lhs, string&& rhs);
ersetzt wurde.
Kellerautomat schrieb:
krümelkacker schrieb:
Kellerautomat schrieb:
Korrekt. Hier braucht man std::move.
So ein Quatsch.
Natuerlich tut man das.
So ein Quatsch.Das sollte doch eine perfect forwarding lösung sein!
Kellerautomat schrieb:
Der Aufruf mutiert gar nichts, wenn er mit LValues *nicht kompiliert*. Und wo du hier ein Template siehst, weiss ich nicht.
Das sollte doch eine perfect forwarding lösung sein!
-
Kann es sein dass ihr euch hier an der perfect forwarding Definition aufhaltet?
- Kein move/copy mehr als nötig
vs
- Type-Deduction