Wenn auto&& var = <initializer>
Sie verwenden, sagen Sie: Ich akzeptiere jeden Initialisierer, unabhängig davon, ob es sich um einen l-Wert- oder einen r-Wert-Ausdruck handelt, und ich werde seine Konstanz beibehalten . Dies wird normalerweise für die Weiterleitung verwendet (normalerweise mit T&&
). Der Grund, warum dies funktioniert, ist, dass eine "universelle Referenz" auto&&
oder T&&
an irgendetwas gebunden ist .
Man könnte sagen, warum nicht einfach ein verwenden, const auto&
weil das auch an irgendetwas gebunden ist? Das Problem bei der Verwendung einer const
Referenz ist, dass es ist const
! Sie können es später nicht mehr an nicht konstante Referenzen binden oder Mitgliedsfunktionen aufrufen, die nicht markiert sind const
.
Stellen Sie sich als Beispiel vor, Sie möchten einen erhalten std::vector
, nehmen Sie einen Iterator zu seinem ersten Element und ändern Sie den Wert, auf den dieser Iterator zeigt, auf irgendeine Weise:
auto&& vec = some_expression_that_may_be_rvalue_or_lvalue;
auto i = std::begin(vec);
(*i)++;
Dieser Code wird unabhängig vom Initialisierungsausdruck problemlos kompiliert. Die Alternativen, um auto&&
auf folgende Weise zu scheitern:
auto => will copy the vector, but we wanted a reference
auto& => will only bind to modifiable lvalues
const auto& => will bind to anything but make it const, giving us const_iterator
const auto&& => will bind only to rvalues
Funktioniert also auto&&
perfekt! Ein Beispiel für eine solche Verwendung auto&&
ist eine bereichsbasierte for
Schleife. Siehe meine andere Frage für weitere Details.
Wenn Sie dann std::forward
Ihre auto&&
Referenz verwenden, um die Tatsache beizubehalten, dass es ursprünglich entweder ein l-Wert oder ein r-Wert war, lautet Ihr Code: Nachdem ich Ihr Objekt entweder aus einem l-Wert- oder einem r-Wert-Ausdruck erhalten habe, möchte ich die Wertigkeit beibehalten, die es ursprünglich hatte hatte, damit ich es am effizientesten nutzen kann - dies könnte es ungültig machen. Wie in:
auto&& var = some_expression_that_may_be_rvalue_or_lvalue;
// var was initialized with either an lvalue or rvalue, but var itself
// is an lvalue because named rvalues are lvalues
use_it_elsewhere(std::forward<decltype(var)>(var));
Dies ermöglicht es use_it_elsewhere
, die Eingeweide aus Gründen der Leistung (Vermeidung von Kopien) herauszureißen, wenn der ursprüngliche Initialisierer ein veränderbarer Wert war.
Was bedeutet dies, ob wir Ressourcen stehlen können oder wann var
? Nun, da der auto&&
Wille an irgendetwas var
gebunden ist , können wir unmöglich versuchen, uns selbst die Eingeweide herauszureißen - es kann sehr gut ein Wert oder sogar eine Konstante sein. Wir können es jedoch std::forward
auf andere Funktionen übertragen, die sein Inneres völlig verwüsten können. Sobald wir dies tun, sollten wir uns var
in einem ungültigen Zustand befinden.
Wenden wir dies nun auf den Fall an auto&& var = foo();
, wie in Ihrer Frage angegeben, bei dem foo einen By- T
Wert zurückgibt . In diesem Fall wissen wir sicher, dass die Art von var
als abgeleitet wird T&&
. Da wir mit Sicherheit wissen, dass es sich um einen Wert handelt, benötigen wir keine std::forward
Erlaubnis, um seine Ressourcen zu stehlen. In diesem speziellen Fall sollte der Leser in dem Wissen, dass die foo
Rückgabe nach Wert erfolgt , einfach Folgendes lesen: Ich nehme einen r-Wert-Verweis auf die temporäre Rückgabe von foo
, damit ich mich glücklich davon entfernen kann.
Als Nachtrag denke ich, dass es erwähnenswert ist, wann ein Ausdruck wie some_expression_that_may_be_rvalue_or_lvalue
auftauchen könnte, abgesehen von einer Situation, in der sich Ihr Code möglicherweise ändert. Hier ist ein erfundenes Beispiel:
std::vector<int> global_vec{1, 2, 3, 4};
template <typename T>
T get_vector()
{
return global_vec;
}
template <typename T>
void foo()
{
auto&& vec = get_vector<T>();
auto i = std::begin(vec);
(*i)++;
std::cout << vec[0] << std::endl;
}
Hier get_vector<T>()
ist dieser schöne Ausdruck, der je nach generischem Typ entweder ein l-Wert oder ein r-Wert sein kann T
. Wir ändern im Wesentlichen den Rückgabetyp von get_vector
durch den Vorlagenparameter von foo
.
Wenn wir aufrufen foo<std::vector<int>>
, get_vector
wird global_vec
by value zurückgegeben, was einen rvalue-Ausdruck ergibt. Wenn wir aufrufen foo<std::vector<int>&>
, get_vector
wird alternativ als global_vec
Referenz zurückgegeben, was zu einem lvalue-Ausdruck führt.
Wenn wir es tun:
foo<std::vector<int>>();
std::cout << global_vec[0] << std::endl;
foo<std::vector<int>&>();
std::cout << global_vec[0] << std::endl;
Wir erhalten erwartungsgemäß die folgende Ausgabe:
2
1
2
2
Wenn Sie die Änderungen waren auto&&
im Code zu einem auto
, auto&
, const auto&
, oder , const auto&&
dann werden wir nicht das Ergebnis bekommen wir wollen.
Eine alternative Möglichkeit, die Programmlogik basierend darauf zu ändern, ob Ihre auto&&
Referenz mit einem l-Wert- oder einem r-Wert-Ausdruck initialisiert wurde, besteht in der Verwendung von Typmerkmalen:
if (std::is_lvalue_reference<decltype(var)>::value) {
// var was initialised with an lvalue expression
} else if (std::is_rvalue_reference<decltype(var)>::value) {
// var was initialised with an rvalue expression
}
auto&&
? Ich habe darüber nachgedacht, warum eine bereichsbasierte for-Schleifeauto&&
als Beispiel erweitert wird, bin aber noch nicht dazu gekommen. Vielleicht kann jeder, der antwortet, es erklären.