При использовании auto&& var = <initializer>
вы говорите: я приму любой инициализатор независимо от того, является ли это выражением lvalue или rvalue, и я сохраню его константу . Это обычно используется для пересылки (обычно с T&&
). Причина, по которой это работает, заключается в том, что «универсальная ссылка» auto&&
или T&&
будет привязана к чему-либо .
Вы можете сказать, ну почему бы просто не использовать a, const auto&
потому что это также будет связывать что-либо? Проблема с использованием const
ссылки в том, что это const
! Вы не сможете позже связать его с любыми неконстантными ссылками или вызывать любые функции-члены, которые не отмечены const
.
В качестве примера представьте, что вы хотите получить a std::vector
, перевести итератор к его первому элементу и каким-то образом изменить значение, на которое указывает этот итератор:
auto&& vec = some_expression_that_may_be_rvalue_or_lvalue;
auto i = std::begin(vec);
(*i)++;
Этот код будет прекрасно компилироваться независимо от выражения инициализатора. Альтернативы auto&&
потерпеть неудачу следующими способами:
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
Так что для этого auto&&
отлично работает! Примером такого использования auto&&
является for
цикл на основе диапазона . Смотрите мой другой вопрос для более подробной информации.
Если затем std::forward
на вашей auto&&
ссылку , чтобы сохранить тот факт , что он был первоначально либо именующим или Rvalue, ваш код говорит: Теперь, когда я получил свой объект либо от Lvalue или RValue выражения, я хочу , чтобы сохранить зависимости от того , valueness его первоначально так что я могу использовать его наиболее эффективно - это может сделать его недействительным. Как в:
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));
Это позволяет use_it_elsewhere
вырывать его изнутри ради производительности (избегая копий), когда исходный инициализатор был изменяемым значением.
Что это означает относительно того, можем ли мы или когда мы можем красть ресурсы var
? Ну, поскольку auto&&
воля к чему-либо привязана, мы не можем попытаться вырвать var
внутренности - это вполне может быть lvalue или даже const. Мы можем однако std::forward
это к другим функциям, которые могут полностью разрушить его внутренности. Как только мы сделаем это, мы должны var
быть в недопустимом состоянии.
Теперь давайте применим это к случаю auto&& var = foo();
, как указано в вашем вопросе, где foo возвращает T
значение по. В этом случае мы точно знаем, что тип var
будет выведен как T&&
. Так как мы точно знаем, что это ценность, нам не нужно std::forward
разрешение на кражу его ресурсов. В этом конкретном случае, зная, что foo
возвращается по значению , читатель должен просто прочитать его как: Я беру ссылку на rvalue на возвращаемое временное значение foo
, так что я могу с радостью отказаться от него.
В качестве дополнения, я думаю, стоит упомянуть, когда some_expression_that_may_be_rvalue_or_lvalue
может появиться такое выражение , кроме ситуации «хорошо, ваш код может измениться». Итак, вот надуманный пример:
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;
}
Вот get_vector<T>()
то прекрасное выражение, которое может быть lvalue или rvalue в зависимости от универсального типа T
. Мы существенно изменим тип возвращаемого значения get_vector
через параметр шаблона foo
.
Когда мы позвоним foo<std::vector<int>>
, get_vector
вернемся global_vec
по значению, которое дает выражение rvalue. В качестве альтернативы, когда мы вызываем foo<std::vector<int>&>
, get_vector
мы вернемся global_vec
по ссылке, что приведет к выражению lvalue.
Если мы делаем:
foo<std::vector<int>>();
std::cout << global_vec[0] << std::endl;
foo<std::vector<int>&>();
std::cout << global_vec[0] << std::endl;
Как и ожидалось, мы получаем следующий вывод:
2
1
2
2
Если вы должны были изменить auto&&
в коде любой из auto
, auto&
, const auto&
или const auto&&
мы не получим результат , который мы хотим.
Альтернативный способ изменить логику программы в зависимости от того auto&&
, инициализирована ли ваша ссылка выражением lvalue или rvalue, - использовать черты типа:
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&&
? Я думал о том, чтобы рассмотреть, почему основанный на диапазоне цикл for расширяется, чтобы использовать егоauto&&
в качестве примера, но до сих пор не дошел до этого. Возможно, тот, кто ответит, сможет это объяснить.