Використовуючи, auto&& var = <initializer>
ви говорите: я прийму будь-який ініціалізатор незалежно від того, чи це вираз lvalue чи rvalue, і я збережу його чіткість . Зазвичай це використовується для переадресації (як правило, з T&&
). Причина цього працює в тому, що «універсальна посилання», auto&&
або T&&
, буде прив’язана до чого завгодно .
Ви можете сказати: ну чому б не просто використовувати a, const auto&
тому що це також буде пов'язане з чим-небудь? Проблема використання const
посилання полягає в тому, що це const
! Пізніше ви не зможете прив’язати його до будь-яких посилань, що не стосуються const, або викликати будь-які функції учасника, які не позначені const
.
Як приклад, уявіть, що ви хочете отримати 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, я хочу зберегти будь-яку цінність його спочатку було, тому я можу використовувати його найбільш ефективно - це може визнати його недійсним. А саме:
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
кишки - це може бути цілком вагомим чи навіть const. Однак ми можемо std::forward
це зробити для інших функцій, які можуть повністю розрушити його нутрощі. Як тільки ми це зробимо, ми повинні вважати var
себе недійсним.
Тепер давайте застосуємо це до випадку auto&& var = foo();
, як зазначено у вашому запитанні, де foo повертає T
значення a . У цьому випадку ми точно знаємо, що тип var
буде виведено як T&&
. Оскільки ми точно знаємо, що це реальне значення, нам не потрібен std::forward
дозвіл на крадіжку його ресурсів. У цьому конкретному випадку, знаючи, що foo
повертається за значенням , читач повинен просто читати це як: Я беру рецензуючу посилання на тимчасове повернене з 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>()
це прекрасний вираз, який може бути або значенням, або рівнем, залежно від родового типу 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&&
? Я думав розглянути, чому діапазон для циклу розширюється, щоб використовувати йогоauto&&
як приклад, але не обійшов його. Можливо, хто відповість, може пояснити це.