Warum wird der Template-Argumentabzug mit std::forward deaktiviert?

Lesezeit: 9 Minuten

Warum wird der Template Argumentabzug mit stdforward deaktiviert
David

In VS2010 ist std::forward wie folgt definiert:

template<class _Ty> inline
_Ty&& forward(typename identity<_Ty>::type& _Arg)
{   // forward _Arg, given explicitly specified type parameter
    return ((_Ty&&)_Arg);
}

identity scheint ausschließlich verwendet zu werden, um die Ableitung von Vorlagenargumenten zu deaktivieren. Was bringt es in diesem Fall, es absichtlich zu deaktivieren?

  • Beachten Sie das derzeit std::forward wird als deklariert template<typename T> T&& forward(typename std::remove_reference<T>::type& t); template<typename T> T&& forward(typename std::remove_reference<T>::type&& t); (und beide kehren zurück static_cast<T&&>

    – Luc Danton

    15. Oktober 2011 um 19:13 Uhr

  • Interessant. Wie unterscheidet sich das von template<typename T> T&& forward(T&& t){ return static_cast<T&&>

    – David

    15. Oktober 2011 um 19:32 Uhr

  • Die Version mit zwei Überladungen arbeitet mit rvalues ​​und erfordert keine std:identity das war tatsächlich Dropper ganz aus dem Standard.

    – Luc Danton

    15. Oktober 2011 um 19:37 Uhr

Warum wird der Template Argumentabzug mit stdforward deaktiviert
David

Wenn Sie eine Rvalue-Referenz an ein Objekt vom Typ übergeben X zu einer Vorlagenfunktion, die Typ annimmt T&& als dessen Parameter leitet die Template-Argument-Deduktion ab T zu sein X. Daher hat der Parameter einen Typ X&&. Wenn das Funktionsargument ein lvalue oder ein konstanter lvalue ist, leitet der Compiler seinen Typ als eine lvalue-Referenz oder eine konstante lvalue-Referenz dieses Typs ab.

Wenn std::forward verwendeter Vorlagenargumentabzug:

Seit objects with names are lvalues das einzige Mal std::forward würde richtig zu werfen T&& wäre, wenn das Eingabeargument ein unbenannter Rvalue wäre (wie 7 oder func()). Bei einwandfreier Weiterleitung der arg Sie gehen zu std::forward ist ein lvalue, weil es einen Namen hat. std::forwardDer Typ von würde als lvalue-Referenz oder const lvalue-Referenz abgeleitet werden. Referenzkollabierende Regeln würden dies verursachen T&& in static_cast<T&&>(arg) in std::forward, um immer als Lvalue-Referenz oder konstante Lvalue-Referenz aufzulösen.

Beispiel:

template<typename T>
T&& forward_with_deduction(T&& obj)
{
    return static_cast<T&&>(obj);
}

void test(int&){}
void test(const int&){}
void test(int&&){}

template<typename T>
void perfect_forwarder(T&& obj)
{
    test(forward_with_deduction(obj));
}

int main()
{
    int x;
    const int& y(x);
    int&& z = std::move(x);

    test(forward_with_deduction(7));    //  7 is an int&&, correctly calls test(int&&)
    test(forward_with_deduction(z));    //  z is treated as an int&, calls test(int&)

    //  All the below call test(int&) or test(const int&) because in perfect_forwarder 'obj' is treated as
    //  an int& or const int& (because it is named) so T in forward_with_deduction is deduced as int& 
    //  or const int&. The T&& in static_cast<T&&>(obj) then collapses to int& or const int& - which is not what 
    //  we want in the bottom two cases.
    perfect_forwarder(x);           
    perfect_forwarder(y);           
    perfect_forwarder(std::move(x));
    perfect_forwarder(std::move(y));
}

  • Toll, dass du dir Gedanken darüber gemacht hast, wie und warum std::forward funktioniert so wie es geht. 🙂 +1

    – Xeo

    15. Januar 2012 um 3:37 Uhr

  • Beachten Sie, dass Sie nur entfernen remove_reference hält alles am Laufen - solange forward akzeptiert durch & und Sie behalten beim Aufruf die explizite Template-Spezifikation bei. Damit dies funktioniert, ist die explizite Vorlagenspezifikation erforderlich remove_reference sorgt dafür, dass wir es nicht vergessen. Ihr Beispiel mit dem Weiterleitungsverweis (&&) ist ein Ablenkungsmanöver, IMHO

    – Eli Bendersky

    4. November 2014 um 14:02 Uhr

  • "Objekte mit Namen„sind streng genommen xvalues. std::move(lvalue) produziert ein xvalue.

    – rostig

    30. Juni 2016 um 20:48 Uhr


  • @rustyx, sind nicht auch einfache Variablen Objekte, immer noch keine xvalues?

    – Enliko

    18. Dezember 2018 um 18:03 Uhr


1646258415 869 Warum wird der Template Argumentabzug mit stdforward deaktiviert
Luca Danton

Weil std::forward(expr) ist nicht nützlich. Das einzige, was es tun kann, ist ein No-Op, dh sein Argument perfekt weiterzuleiten und wie eine Identitätsfunktion zu agieren. Die Alternative wäre, dass es dasselbe ist wie std::moveaber wir bereits habe das. Mit anderen Worten, vorausgesetzt, es wäre möglich, in

template<typename Arg>
void generic_program(Arg&& arg)
{
    std::forward(arg);
}

std::forward(arg) ist semantisch äquivalent zu arg. Andererseits, std::forward<Arg>(arg) ist im allgemeinen Fall kein No-Op.

Also durch verbieten std::forward(arg) Es hilft, Programmierfehler abzufangen, und wir verlieren nichts seit einer möglichen Verwendung std::forward(arg) werden trivialerweise ersetzt durch arg.


Ich denke, Sie würden die Dinge besser verstehen, wenn wir uns auf das konzentrieren, was genau std::forward<Arg>(arg) tuteher als was std::forward(arg) tun würde (da es ein uninteressanter No-Op ist). Lassen Sie uns versuchen, eine No-Op-Funktionsvorlage zu schreiben, die ihr Argument perfekt weiterleitet.

template<typename NoopArg>
NoopArg&& noop(NoopArg&& arg)
{ return arg; }

Dieser naive erste Versuch ist nicht ganz gültig. Wenn wir anrufen noop(0) dann NoopArg wird abgeleitet als int. Dies bedeutet, dass der Rückgabetyp ist int&& und wir können eine solche rvalue-Referenz nicht aus dem Ausdruck binden arg, was ein lvalue ist (es ist der Name eines Parameters). Wenn wir dann versuchen:

template<typename NoopArg>
NoopArg&& noop(NoopArg&& arg)
{ return std::move(arg); }

dann int i = 0; noop(i); scheitert. Diesmal, NoopArg wird abgeleitet als int& (Reference Collapsed Rules garantieren das int& && bricht zusammen int&), daher ist der Rückgabetyp int&und dieses Mal können wir eine solche lvalue-Referenz nicht aus dem Ausdruck binden std::move(arg) was ein xvalue ist.

Im Rahmen einer Perfect-Forwarding-Funktion wie z noop, manchmal wir wollen uns bewegen, aber manchmal tun wir es nicht. Die Regel zu wissen, ob wir uns bewegen sollten, hängt davon ab Arg: Wenn es sich nicht um einen Lvalue-Referenztyp handelt, bedeutet dies noop wurde ein rvalue übergeben. Wenn es sich um einen Lvalue-Referenztyp handelt, bedeutet dies noop wurde ein lvalue übergeben. Also rein std::forward<NoopArg>(arg), NoopArg ist ein notwendig Argument zu std::forward damit die Funktionsvorlage das Richtige tut. Ohne sie gibt es nicht genug Informationen. Dies NoopArg ist nicht der gleiche Typ wie das, was die T Parameter von std::forward würde im allgemeinen Fall abgeleitet werden.

  • Also was macht std::forward<T>

    – Dani

    15. Oktober 2011 um 19:12 Uhr

  • Ich verstehe nicht, warum es ein No-Op wäre, wenn es den Template-Argumenttyp ableiten würde, anstatt dass ich ihn explizit gebe. Könnten Sie das erläutern/erklären?

    – David

    15. Oktober 2011 um 19:14 Uhr

  • @avakar Nein. Da übergeben wir eine Variable an std::forward, T würde zu einem lvalue-Referenztyp abgeleitet werden, und Verweisreduzierungsregeln würden sicherstellen, dass der Rückgabetyp derselbe lvalue-Referenztyp ist, sodass der Aufruf ein lvalue wäre. Ähnlich, std::forward(0) wäre ein xvalue. Daher eine Ableitung std::forward würde sein Argument perfekt weiterleiten und ein No-Op sein.

    – Luc Danton

    15. Oktober 2011 um 19:40 Uhr


  • @Dave Richtig. Aber t nicht als Rvalue-Referenz verwendet wird, bin ich mir nicht sicher, warum Sie das erwähnen. Meinst du die Bindung an den Parameter von std::forward? Verstehst du das in template<typename T> void foo(T&&); dann T&& kann eine Rvalue-Referenz sein oder nicht?

    – Luc Danton

    15. Oktober 2011 um 20:29 Uhr


  • @hvd Funktionsvorlagen können prvalues ​​nicht von xvalues ​​unterscheiden. Das letzte, gebundene Argument ist ein 'Wert' vom Typ int&&, und es ist auch der Rückgabewert. Daher ein No-Op.

    – Luc Danton

    27. April 2014 um 11:08 Uhr


1646258415 300 Warum wird der Template Argumentabzug mit stdforward deaktiviert
Bogenschütze

Kurze Antwort:

Denn für std::forward arbeiten als vorgesehen(, dh um die ursprünglichen Typinformationen treu zu übergeben), ist es gemeint zu sein benutzt INNERER VORLAGENKONTEXTund es muss den abgeleiteten Typ param verwenden von dem umschließenden Vorlagenkontext, stattdessen der Ableitung des Typs param von selbst(da nur die einschließenden Templates die Möglichkeit haben, auf die wahre Typ-Info zu schließen, wird dies im Detail erklärt), daher muss der Typ-Param angegeben werden.

Obwohl mit std::forward innerhalb eines Nicht-Template-Kontexts ist möglich, das ist es zwecklos(, wird im Detail erklärt).

Und falls sich jemand an die Umsetzung wagt std::forward Typableitung zuzulassen, ist er/sie zum schmerzhaften Scheitern verurteilt.

Einzelheiten:

Beispiel:

template <typename T>
auto someFunc(T&& arg){ doSomething(); call_other_func(std::forward<T>(para)); }

Beobachte das arg wird als deklariert T&&,( es ist der Schlüssel, um den übergebenen wahren Typ abzuleiten, und) es ist keine rvalue-Referenz, obwohl es dieselbe Syntax hat, wird es als an bezeichnet universelle Referenz (Terminologie geprägt von Scott Meyers), da T ist ein generischer Typ (ebenfalls in string s; auto && ss = s; ss ist keine rvalue-Referenz).

Dank an universelle Referenzeinige Art ableiten Magie passiert, wenn someFunc wird instanziiert, insbesondere wie folgt:

  • Wenn ein rvalue-Objekt, das den Typ hat _T oder _T &wird weitergegeben someFunc, T wird abgeleitet als _T &(, ja, auch wenn die Art von X ist nur _TBitte lesen Sie Artikel von Meyers);
  • Wenn ein rvalue vom Typ _T && übergeben wird someFuncT wird abgeleitet als _T &&

Jetzt können Sie ersetzen T mit dem wahren Typ im obigen Code:

Wenn lvalue obj übergeben wird:

auto someFunc(_T & && arg){ doSomething(); call_other_func(std::forward<_T &>(arg)); }

Und nach der Bewerbung Referenzkollapsregel(Bitte lesen Artikel von Meyers), wir bekommen:

auto someFunc(_T & arg){ doSomething(); call_other_func(std::forward<_T &>(arg)); }

Wenn rvalue obj übergeben wird:

auto someFunc(_T && && arg){ doSomething(); call_other_func(std::forward<_T &&>(arg)); }

Und nach Anwendung der Referenzkollapsregel (bitte lesen Artikel von Meyers), wir bekommen:

auto someFunc(_T && arg){ doSomething(); call_other_func(std::forward<_T &&>(arg)); }

Jetzt können Sie erraten, was std::forwrd im Wesentlichen tut static_cast<T>(para)(eigentlich in klirren 11-Implementierung ist es static_cast<T &&>(para), was auch nach der Anwendung der Referenzzusammenbruchsregel der Fall ist). Alles klappt gut.

Aber wenn Sie darüber nachdenken, std::fowrd den Typparam selbst ableiten zu lassen, werden Sie das schnell herausfinden Innerhalb someFunc, std::forward buchstäblich IST NICHT IN DER LAGE, auf den ursprünglichen Typ zu schließen von arg.

Wenn Sie versuchen, den Compiler dazu zu bringen, wird er es tun niemals als abgeleitet werden _T &&(Ja, sogar wann arg ist an eine gebunden _T &&es ist immer noch ein lvaule obj drinnen someFunckann daher nur als abgeleitet werden _T oder _T &.... sollten Sie unbedingt lesen Artikel von Meyers).

Zuletzt, warum sollten Sie nur verwenden std::forward in Vorlagen? Denn im Nicht-Template-Kontext wissen Sie genau, welche Art von Objekt Sie haben. Wenn Sie also eine haben Wert binden zu einem rvalue-Referenzund Sie müssen Übergeben Sie es als Wert zu einer anderen Funktion, übergeben Sie es einfach, oder wenn Sie müssen Übergeben Sie es als rvalue, mach einfach std::move. Sie einfach NICHT BRAUCHEN std::weiter innerhalb des Nicht-Template-Kontexts.

916560cookie-checkWarum wird der Template-Argumentabzug mit std::forward deaktiviert?

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

Privacy policy