通过使用auto&& var = <initializer>,您的意思是:我将接受任何初始化器,无论它是左值还是右值表达式,并且我将保留它的常量。这通常用于转发(通常使用T&&)。之所以可行,是因为“通用引用”auto&& 或 T&& 将绑定到任何东西。
你可能会说,为什么不直接使用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 循环中。详情请见my other question。
如果您随后在您的 auto&& 引用上使用 std::forward 以保留它最初是左值或右值的事实,您的代码将显示:现在我已经从左值中获取了您的对象或右值表达式,我想保留它最初具有的任何价值,以便我可以最有效地使用它 - 这可能会使它无效。 如:
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&& 将绑定到任何东西,我们不可能自己尝试撕掉vars 的胆量——它很可能是一个左值甚至是常量。但是,我们可以将其std::forward 用于可能完全破坏其内部的其他功能。一旦我们这样做,我们应该认为var 处于无效状态。
现在让我们将其应用于auto&& var = foo(); 的情况,如您的问题中给出的那样,其中 foo 按值返回T。在这种情况下,我们确定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。我们本质上是通过foo的模板参数来改变get_vector的返回类型。
当我们调用foo<std::vector<int>> 时,get_vector 将按值返回global_vec,它给出了一个右值表达式。或者,当我们调用foo<std::vector<int>&> 时,get_vector 将通过引用返回global_vec,从而产生一个左值表达式。
如果我们这样做:
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&& 引用是使用左值还是右值表达式初始化来更改程序逻辑的另一种方法是使用类型特征:
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
}