Usando auto&& var = <initializer>
stai dicendo: accetterò qualsiasi inizializzatore indipendentemente dal fatto che sia un'espressione lvalue o rvalue e conserverò la sua costanza . Questo è in genere utilizzato per l' inoltro (di solito con T&&
). Il motivo per cui funziona è perché un "riferimento universale", auto&&
o T&&
, si legherà a qualsiasi cosa .
Potresti dire, beh, perché non usare solo un const auto&
perché si legherà anche a qualcosa? Il problema con l'utilizzo di un const
riferimento è che è const
! Non sarai in grado di associarlo in seguito a riferimenti non costanti o invocare funzioni membro non contrassegnate const
.
Ad esempio, immagina di voler ottenere un std::vector
, porta un iteratore al suo primo elemento e modifica in qualche modo il valore indicato da quell'iteratore:
auto&& vec = some_expression_that_may_be_rvalue_or_lvalue;
auto i = std::begin(vec);
(*i)++;
Questo codice verrà compilato correttamente indipendentemente dall'espressione dell'inizializzatore. Le alternative auto&&
falliscono nei seguenti modi:
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
Quindi, per questo, auto&&
funziona perfettamente! Un esempio di utilizzo auto&&
come questo è in un for
ciclo basato sull'intervallo. Vedi la mia altra domanda per maggiori dettagli.
Se poi usi std::forward
il tuo auto&&
riferimento per preservare il fatto che originariamente era un valore lvalue o un valore rvalue, il tuo codice dice: Ora che ho ottenuto il tuo oggetto da un'espressione lvalue o rvalue, voglio preservare qualunque valore originariamente così ho potuto usarlo nel modo più efficiente - questo potrebbe invalidarlo. Come 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));
Ciò consente use_it_elsewhere
di strappare il fegato per motivi di prestazioni (evitando copie) quando l'inizializzatore originale era un valore modificabile.
Cosa significa se possiamo o quando possiamo rubare risorse var
? Bene, dato che la auto&&
volontà si lega a qualcosa, non possiamo assolutamente tentare di strapparci le var
viscere - potrebbe benissimo essere un valore o addirittura una const. Possiamo tuttavia std::forward
ad altre funzioni che possono devastare totalmente la sua parte interna. Non appena lo facciamo, dovremmo considerare var
di essere in uno stato non valido.
Ora applichiamo questo al caso di auto&& var = foo();
, come indicato nella tua domanda, in cui foo restituisce un T
valore. In questo caso sappiamo con certezza che il tipo di var
verrà dedotto come T&&
. Dato che sappiamo per certo che si tratta di un valore, non abbiamo bisogno std::forward
del permesso per rubare le sue risorse. In questo caso specifico, sapendo che foo
ritorna per valore , il lettore dovrebbe semplicemente leggerlo come: sto prendendo un riferimento di valore al temporaneo restituito da foo
, così posso felicemente spostarmi da esso.
Come addendum, penso che valga la pena menzionare quando some_expression_that_may_be_rvalue_or_lvalue
potrebbe apparire un'espressione simile , a parte una situazione "bene il tuo codice potrebbe cambiare". Quindi ecco un esempio inventato:
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;
}
Ecco l' get_vector<T>()
espressione adorabile che può essere sia un valore che un valore a seconda del tipo generico T
. Modifichiamo essenzialmente il tipo di ritorno di get_vector
tramite il parametro template di foo
.
Quando chiamiamo foo<std::vector<int>>
, get_vector
tornerà global_vec
per valore, che dà un'espressione di valore. In alternativa, quando chiamiamo foo<std::vector<int>&>
, get_vector
tornerà global_vec
per riferimento, risultando in un'espressione lvalue.
Se lo facciamo:
foo<std::vector<int>>();
std::cout << global_vec[0] << std::endl;
foo<std::vector<int>&>();
std::cout << global_vec[0] << std::endl;
Otteniamo il seguente output, come previsto:
2
1
2
2
Se si dovesse cambiare il auto&&
nel codice per qualsiasi auto
, auto&
, const auto&
, o const auto&&
allora noi non otteniamo il risultato che vogliamo.
Un modo alternativo per modificare la logica del programma in base all'inizializzazione del auto&&
riferimento con un'espressione lvalue o rvalue consiste nell'utilizzare tratti di tipo:
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&&
? Ho pensato di capire perché un loop basato su range si espande per usareauto&&
come esempio, ma non ci sono riuscito. Forse chi risponde può spiegarlo.