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 const
referencji 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 for
pętla oparta na zakresie . Zobacz moje drugie pytanie, aby uzyskać więcej informacji.
Jeśli następnie użyjesz std::forward
w 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_elsewhere
to 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ć var
wnętrzności - może to być wartość l, a nawet stała. Możemy jednak korzystać std::forward
z innych funkcji, które mogą całkowicie spustoszyć jego wnętrze. Gdy tylko to zrobimy, powinniśmy uważać, że jesteśmy var
w stanie nieważności.
Teraz zastosujmy to do przypadku auto&& var = foo();
, jak podano w pytaniu, gdzie foo zwraca T
wartość a. W takim przypadku wiemy na pewno, że typ var
zostanie wydedukowany jako T&&
. Ponieważ wiemy na pewno, że jest to wartość r, nie potrzebujemy std::forward
pozwolenia na kradzież jej zasobów. W tym konkretnym przypadku, wiedząc, że foo
zwraca 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_lvalue
moż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_vector
przez parametr szablonu foo
.
Kiedy wywołujemy foo<std::vector<int>>
, get_vector
zwróci global_vec
wartość, co daje wyrażenie rvalue. Alternatywnie, gdy wywołasz foo<std::vector<int>&>
, get_vector
zwróci global_vec
przez 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ć.