通过使用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循环中。详见我的另一个问题。
如果你在你的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_else为了性能(避免复制)而去掉它的部分。
这意味着我们是否可以或何时可以从var中窃取资源?因为auto&&会绑定到任何东西,我们不可能试图自己去掉vars的内脏——它很可能是一个左值,甚至是const。然而,我们可以std::将其转发给其他可能完全破坏其内部的函数。一旦我们这样做,我们应该认为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
}