Używając auto&& var = <initializer>you, mówisz: zaakceptuję dowolny inicjalizator, niezależnie od tego, czy jest to wyrażenie l-wartość, czy r-wartość i zachowam jego stałość . Jest to zwykle używane do przekazywania (zwykle z T&&). Powodem, dla którego to działa, jest to, że „uniwersalne odniesienie” auto&&lub T&&będzie wiązać się z czymkolwiek .
Możesz powiedzieć, dlaczego nie użyć po prostu znaku, const auto&ponieważ to również będzie wiązać się z czymkolwiek? Problem z używaniem constreferencji polega na tym, że tak jest const! Nie będzie można później powiązać go z żadnymi odwołaniami niebędącymi stałymi ani wywoływać funkcji składowych, które nie są oznaczone const.
Jako przykład wyobraź sobie, że chcesz uzyskać a std::vector, przenieś iterator do jego pierwszego elementu i zmodyfikuj w jakiś sposób wartość wskazywaną przez ten iterator:
auto&& vec = some_expression_that_may_be_rvalue_or_lvalue;
auto i = std::begin(vec);
(*i)++;
Ten kod skompiluje się dobrze, niezależnie od wyrażenia inicjatora. Alternatywy, które mogą auto&&zawieść w następujący sposób:
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
Więc do tego auto&&działa idealnie! Przykładem takiego użycia auto&&jest forpętla oparta na zakresie . Zobacz moje drugie pytanie, aby uzyskać więcej informacji.
Jeśli następnie użyjesz std::forwardw swoim auto&&odwołaniu, aby zachować fakt, że pierwotnie była to lwartość lub rwartość, twój kod mówi: Teraz, gdy mam twój obiekt z wyrażenia lwartość lub rwartość, chcę zachować każdą pierwotną wartość tak, żebym mógł z niego korzystać najefektywniej - może to go unieważnić. Jak w:
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));
Pozwala use_it_elsewhereto wyrwać jego wnętrzności ze względu na wydajność (unikanie kopii), gdy oryginalny inicjator był modyfikowalną rvalue.
Co to oznacza, jeśli chodzi o to, czy możemy, lub kiedy możemy kraść zasoby var? Cóż, ponieważ auto&&wola wiąże się z czymkolwiek, nie możemy sami próbować wyrwać varwnętrzności - może to być wartość l, a nawet stała. Możemy jednak korzystać std::forwardz innych funkcji, które mogą całkowicie spustoszyć jego wnętrze. Gdy tylko to zrobimy, powinniśmy uważać, że jesteśmy varw stanie nieważności.
Teraz zastosujmy to do przypadku auto&& var = foo();, jak podano w pytaniu, gdzie foo zwraca Twartość a. W takim przypadku wiemy na pewno, że typ varzostanie wydedukowany jako T&&. Ponieważ wiemy na pewno, że jest to wartość r, nie potrzebujemy std::forwardpozwolenia na kradzież jej zasobów. W tym konkretnym przypadku, wiedząc, że foozwraca wartość , czytelnik powinien po prostu odczytać to jako: Biorę odwołanie do wartości r do tymczasowego zwróconego z foo, więc mogę z radością odejść od tego.
Jako uzupełnienie, myślę, że warto wspomnieć, kiedy some_expression_that_may_be_rvalue_or_lvaluemoże pojawić się wyrażenie takie jak , inne niż sytuacja, w której kod może się zmienić. Oto wymyślony przykład:
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;
}
Oto get_vector<T>()cudowne wyrażenie, które może być lwartością lub rwartością w zależności od typu ogólnego T. Zasadniczo zmieniamy typ zwracania get_vectorprzez parametr szablonu foo.
Kiedy wywołujemy foo<std::vector<int>>, get_vectorzwróci global_vecwartość, co daje wyrażenie rvalue. Alternatywnie, gdy wywołasz foo<std::vector<int>&>, get_vectorzwróci global_vecprzez odniesienie, co spowoduje wyrażenie lvalue.
Jeśli zrobimy:
foo<std::vector<int>>();
std::cout << global_vec[0] << std::endl;
foo<std::vector<int>&>();
std::cout << global_vec[0] << std::endl;
Zgodnie z oczekiwaniami otrzymujemy następujący wynik:
2
1
2
2
Jeśli było zmienić auto&&w kodzie, aby którykolwiek z auto, auto&, const auto&czy const auto&&wtedy nie będzie uzyskać wynik chcemy.
Alternatywnym sposobem zmiany logiki programu w zależności od tego, czy auto&&odwołanie jest zainicjowane wyrażeniem l-wartość, czy r-wartość, jest użycie cech typu:
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&&? Zastanawiałem się nad tym, dlaczego pętla for oparta na zakresie rozszerza się, aby użyć jejauto&&jako przykładu, ale nie doszedłem do tego. Być może ktokolwiek odpowie, może to wyjaśnić.