При использовании 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&&в качестве примера, но до сих пор не дошел до этого. Возможно, тот, кто ответит, сможет это объяснить.