Präzise Erläuterung der Regeln zum Zusammenklappen von Referenzen angefordert: (1) A& & -> A& , (2) A& && -> A& , (3) A&& & -> A& und (4) A&& && -> A&&

Lesezeit: 8 Minuten

Der folgende Link enthält die 4 Formen des Referenzzusammenbruchs (wenn ich richtig liege, dass dies die einzigen 4 Formen sind): http://thbecker.net/articles/rvalue_references/section_08.html.

Aus dem Link:

  1. Aus A& & wird A&
  2. A& && wird zu A&
  3. A&& & wird zu A&
  4. A&& && wird zu A&&

Obwohl ich eine begründete Vermutung anstellen kann, hätte ich gerne eine kurze Erklärung für die Gründe hinter jeder dieser Regeln zum Zusammenfallen von Referenzen.

Eine verwandte Frage, wenn ich darf: Werden diese Referenz-Reduzierungsregeln verwendet? in C++11 intern durch solche STL-Dienstprogramme wie z std::move(), std::forward(), und dergleichen, in typischen realen Anwendungsfällen? (Hinweis: Ich frage ausdrücklich, ob die Verweisreduzierungsregeln in C++11 verwendet werden, im Gegensatz zu C++03 oder früher.)

Ich stelle diese verwandte Frage, weil mir solche C ++ 11-Dienstprogramme wie bekannt sind std::remove_referenceaber ich weiß nicht, ob die referenzbezogenen Dienstprogramme wie z std::remove_reference werden routinemäßig in C++11 verwendet Notwendigkeit vermeiden die Regeln zum Zusammenfassen von Referenzen oder ob sie verwendet werden in Verbindung mit die Regeln zum Zusammenfallen von Referenzen.

  • Es würde Sie wahrscheinlich interessieren Dieses Video von Scott Meyers.

    – Benjamin Lindley

    5. Dezember 2012 um 14:50 Uhr

  • Betrachte es jetzt. Danke!

    – Dan Nissenbaum

    5. Dezember 2012 um 14:53 Uhr

  • “typische reale Anwendungsfälle” – Perfekte Weiterleitung (was ich als sehr nützlichen Anwendungsfall in der realen Welt bezeichnen würde) baut vollständig auf diesen Referenzkollapsregeln auf (und könnte wiederum der praktische Grund für die Gestaltung dieser Regeln gewesen sein, wie sie sind, denke ich). Durch die Einführung einer neuen Art von Referenzen zur Nutzung der Bewegungssemantik erhielten sie auch die Möglichkeit, die entsprechenden Collapsing-Regeln zu definieren, um eine perfekte Weiterleitung zu ermöglichen und somit zwei Probleme mit einem einzigen Feature zu lösen.

    – Christian Rau

    5. Dezember 2012 um 15:49 Uhr

  • Ich denke A& & becomes A& ist nicht Referenz zusammenbricht.

    – Nawaz

    24. September 2015 um 12:06 Uhr


Prazise Erlauterung der Regeln zum Zusammenklappen von Referenzen angefordert 1
Nicol Bola

Die Referenz-Komprimierungsregeln (außer für A& & -> A&also C++98/03) gibt es aus einem Grund: um eine perfekte Weiterleitung zu ermöglichen.

“Perfekte” Weiterleitung bedeutet, Parameter effektiv weiterzuleiten, als ob der Benutzer die Funktion direkt aufgerufen hätte (minus Elision, die durch die Weiterleitung unterbrochen wird). Es gibt drei Arten von Werten, die der Benutzer übergeben kann: lvalues, xvalues ​​und prvalues, und es gibt drei Möglichkeiten, wie der empfangende Speicherort einen Wert annehmen kann: per Wert, per (möglicherweise konstanter) Lvalue-Referenz und per (möglicherweise konstanter) rvalue Hinweis.

Betrachten Sie diese Funktion:

template<class T>
void Fwd(T &&v) { Call(std::forward<T>(v)); }

Nach Wert

Wenn Call nimmt seinen Parameter nach Wert, dann muss ein Kopieren/Verschieben in diesen Parameter erfolgen. Welche hängt davon ab, was der eingehende Wert ist. Wenn der eingehende Wert ein Lvalue ist, muss er den Lvalue kopieren. Wenn der eingehende Wert ein rvalue ist (die zusammen xvalues ​​und prvalues ​​sind), muss er sich von ihm bewegen.

Wenn Sie anrufen Fwd mit einem lvalue bedeuten die Typableitungsregeln von C++ dies T wird abgeleitet als Type&wo Type ist der Typ des lvalue. Offensichtlich, wenn der lvalue ist constes wird abgeleitet als const Type&. Das bedeuten die Referenzkollapsregeln Type & && wird Type & zum v, eine Lvalue-Referenz. Genau das müssen wir anrufen Call. Der Aufruf mit einer Lvalue-Referenz erzwingt eine Kopie, genau so, als ob wir sie direkt aufgerufen hätten.

Wenn Sie anrufen Fwd mit einem rvalue (also: a Type vorübergehender Ausdruck oder bestimmte Type&& Ausdrücke), dann T wird abgeleitet als Type. Die zusammenfassenden Referenzregeln geben uns Type &&was eine Bewegung/Kopie provoziert, was fast genau so ist, als hätten wir es direkt aufgerufen (minus Elision).

Per Lvalue-Referenz

Wenn Call erhält seinen Wert durch Lvalue-Referenz, dann sollte es nur aufrufbar sein, wenn der Benutzer Lvalue-Parameter verwendet. Wenn es sich um eine const-lvalue-Referenz handelt, kann sie von allem aufgerufen werden (lvalue, xvalue, prvalue).

Wenn Sie anrufen Fwd mit einem lvalue erhalten wir wieder Type& als Art von v. Dies wird an eine nicht konstante lvalue-Referenz gebunden. Wenn wir es mit einem konstanten lvalue aufrufen, erhalten wir const Type&die nur an ein konstantes lvalue-Referenzargument in gebunden wird Call.

Wenn Sie anrufen Fwd mit einem xvalue erhalten wir wieder Type&& als Art von v. Dieser Wille nicht ermöglichen es Ihnen, eine Funktion aufzurufen, die einen nicht konstanten Lvalue akzeptiert, da ein xvalue nicht an eine nicht konstante Lvalue-Referenz gebunden werden kann. Es kann an eine konstante Lvalue-Referenz gebunden werden, also if Call verwendet a const&könnten wir anrufen Fwd mit einem x-Wert.

Wenn Sie anrufen Fwd mit einem prvalue erhalten wir wieder Type&&, also funktioniert alles wie vorher. Sie können keine temporäre Funktion an eine Funktion übergeben, die einen nicht konstanten L-Wert annimmt, daher wird unsere Weiterleitungsfunktion bei dem Versuch, dies zu tun, ebenfalls ersticken.

Per rvalue-Referenz

Wenn Call erhält seinen Wert durch Rvalue-Referenz, dann sollte es nur aufrufbar sein, wenn der Benutzer xvalue- oder rvalue-Parameter verwendet.

Wenn Sie anrufen Fwd mit einem lvalue erhalten wir Type&. Dies wird nicht an einen rvalue-Referenzparameter gebunden, sodass ein Kompilierungsfehler resultiert. EIN const Type& wird auch nicht an einen rvalue-Referenzparameter gebunden, so dass es immer noch fehlschlägt. Und genau das würde passieren, wenn wir anrufen würden Call direkt mit einem lvalue.

Wenn Sie anrufen Fwd mit einem xvalue erhalten wir Type&&was funktioniert (Lebenslauf-Qualifikation natürlich trotzdem).

Dasselbe gilt für die Verwendung eines Prvalue.

std::weiter

std::forward selbst verwendet Regeln zum Zusammenfassen von Referenzen auf ähnliche Weise, um eingehende rvalue-Referenzen als xvalues ​​(Funktionsrückgabewerte, die Type&& sind xvalues) und eingehende lvalue-Referenzen als lvalues ​​(returning Type&).

  • Ausgezeichnete Antwort. Ich häng mich da auf: If you call Fwd with an lvalue, C++'s type-deduction rules mean that T will be deduced as Type&. Vielleicht geht mein Unverständnis über den Rahmen eines Kommentars hinaus, und ich muss das genauer studieren (oder eine andere Frage stellen). Aber ich verstehe nicht, warum Typabzug wählen würde T als Type& eher, als Type.

    – Dan Nissenbaum

    5. Dezember 2012 um 15:59 Uhr

  • @DanNissenbaum: Mehrere Gründe, aber der einfachste ist der offensichtlichste: Der Wert wird nicht kopiert (der möglicherweise nicht kopierbar ist).

    – Nicol Bolas

    5. Dezember 2012 um 16:34 Uhr


  • Wenn T wird abgeleitet als Typeals das Argumenttyp zu Fwd() wäre Type&&, aber, oder? Ich glaube, das ist es, was ich nicht verstehe.

    – Dan Nissenbaum

    5. Dezember 2012 um 16:49 Uhr

  • @DanNissenbaum: Und dann wäre die Weiterleitung nicht perfekt. Wenn ich einen Lvalue an übergebe Fwd, Fwd muss einen lvalue übergeben Call. Fwd leitet ein Type&& als ein xWert, kein lvalue. Somit ist die Weiterleitung nicht perfekt. Außerdem ist diese Regel Teil von C++98, wo es keine rvalue-Referenzen gibt.

    – Nicol Bolas

    5. Dezember 2012 um 17:09 Uhr


  • Als ich darüber nachgedacht habe, ist es klar, dass der Compiler könnte nicht ableiten T zu sein Type wenn das argument zu einem anruf Fwd ist ein lvalue, weil die Funktionssignatur dann wäre Fwd(Type &&), und der Compiler würde nicht zulassen, dass ein lvalue an eine Funktion übergeben wird, die eine rvalue-Referenz akzeptiert. Die nächste mögliche Wahl ist die T ist Type &was einer Funktionssignatur entspricht Fwd(Type & &&)die (nach Referenz-Klappregeln) wird Fwd(Type &)was eine akzeptable Funktionssignatur ist, also wird sie verwendet.

    – Dan Nissenbaum

    5. Dezember 2012 um 18:35 Uhr

Die Regeln sind eigentlich ziemlich einfach. Rvalue reference ist ein Verweis auf einen temporären Wert, der nicht über den Ausdruck hinaus bestehen bleibt, der ihn verwendet – im Gegensatz zu lvalue reference die auf persistente Daten verweist. Wenn Sie also einen Verweis auf persistente Daten haben, egal mit welchen anderen Verweisen Sie sie kombinieren, sind die tatsächlich referenzierten Daten ein Lvalue – dies deckt die ersten 3 Regeln ab. Die vierte Regel ist ebenfalls natürlich – rvalue-Referenz auf rvalue-Referenz ist immer noch eine Referenz auf nicht persistente Daten, daher wird rvalue-Referenz zurückgegeben.

Ja, die C++11-Dienstprogramme verlassen sich auf diese Regeln, die von Ihrem Link bereitgestellte Implementierung entspricht den echten Headern: http://en.cppreference.com/w/cpp/utility/forward

Und ja, die Zusammenbruchsregeln zusammen mit der Regel zum Abzug von Vorlagenargumenten werden bei der Verwendung angewendet std::move und std::forward Dienstprogramme, genau wie in Ihrem Link erklärt.

Die Verwendung von Typeigenschaften wie z remove_reference Das hängt wirklich von Ihren Bedürfnissen ab; move und forward Deckung für die lässigsten Fälle.

  • In Fall 3 haben Sie es jedoch A&& &also “beginnen” Sie mit einem Verweis auf a vorübergehend Wert (A&&), und nehmen Sie dann einen Verweis darauf (A&& &), würde dies auch nicht nicht in der Kategorie sein ‘if you have a reference to a persisting data, no matter what other references you combine it with...‘ (da Sie nicht mit einem Verweis auf persistente Daten beginnen)?

    – Dan Nissenbaum

    5. Dezember 2012 um 17:59 Uhr


  • @DanNissenbaum das ist eine gute Beobachtung. Ich nehme an, dass der Compiler trotz des Ausgangspunkts als Rvalue-Referenz den gesamten Ausdruck überprüft, um eine fundiertere Entscheidung zu treffen. Die Beziehung zwischen & und && ist wie eine logische and zwischen false(&) und true(&&).

    – SomeWittyBenutzername

    5. Dezember 2012 um 18:06 Uhr

  • „lvalue“ ist eine Ausdruckskategorie, es ist nicht möglich, „die tatsächlich referenzierten Daten“ als lvalue zu beschreiben. Auch Rvalue-Referenzen können sich auf nicht-temporäre Objekte beziehen (z X&& y = std::move(x);)

    – MM

    4. September 2017 um 21:16 Uhr

993650cookie-checkPräzise Erläuterung der Regeln zum Zusammenklappen von Referenzen angefordert: (1) A& & -> A& , (2) A& && -> A& , (3) A&& & -> A& und (4) A&& && -> A&&

This website is using cookies to improve the user-friendliness. You agree by using the website further.

Privacy policy