auto &&告诉我们什么?

MWi*_*Wid 153 c++ auto c++11 forwarding-reference

如果您阅读的代码如下

auto&& var = foo();

where foo是按类型值返回的任何函数T.然后var是左值类型的左值引用T.但这意味着var什么呢?这是不是意味着,我们被允许窃取资源var?是否有任何合理的情况,您应该使用auto&&告诉读者您的代码与您在返回时unique_ptr<>告诉您拥有独占所有权时的行为?例如什么T&&时候T类型?

我只是想了解,如果有任何其他用例auto&&比模板编程中的用例; 喜欢这篇文章中的例子中所讨论的那些通用参考斯科特迈尔斯.

Jos*_*eld 207

通过使用auto&& var = <initializer>你说:我将接受任何初始化器,无论它是左值还是右值表达式,我将保留其常量.这通常用于转发(通常用于T&&).这有效的原因是因为"通用引用",auto&&或者T&&,它将绑定到任何东西.

你可能会说,好吧,为什么不直接使用const auto&,因为这将绑定到什么?使用const引用的问题在于它const!稍后您将无法将其绑定到任何非const引用或调用未标记的任何成员函数const.

作为一个例子,假设您想要获取a std::vector,将迭代器作为其第一个元素并以某种方式修改该迭代器指向的值:

auto&& vec = some_expression_that_may_be_rvalue_or_lvalue;
auto i = std::begin(vec);
(*i)++;
Run Code Online (Sandbox Code Playgroud)

无论初始化表达式如何,此代码都将编译得很好.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
Run Code Online (Sandbox Code Playgroud)

所以,为此,auto&&完美的工作!auto&&这样使用的一个例子是基于范围的for循环.有关详细信息,请参阅我的其他问题

如果你随后使用std::forward你的auto&&引用来保留它最初是左值或右值的事实,你的代码说:既然我已经从左值或右值表达式获得了你的对象,我想保留它最初的值.所以我可以最有效地使用它 - 这可能会使它无效.如:

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));
Run Code Online (Sandbox Code Playgroud)

use_it_elsewhere当原始初始化程序是可修改的右值时,这允许为了性能(避免复制)而撕掉它的内容.

这对于我们是否可以或何时可以从中窃取资源意味着什么var?好吧,因为auto&&它将绑定到任何东西,我们不可能试图撕掉自己var的胆量 - 它很可能是一个左值甚至常量.然而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;
}
Run Code Online (Sandbox Code Playgroud)

在这里,get_vector<T>()可爱的表达式可以是左值或右值,具体取决于泛型类型T.我们实质上get_vector通过模板参数更改返回类型foo.

当我们调用时foo<std::vector<int>>,get_vector将按global_vec值返回,这将给出一个rvalue表达式.或者,当我们调用时foo<std::vector<int>&>,get_vectorglobal_vec通过引用返回,从而产生左值表达式.

如果我们这样做:

foo<std::vector<int>>();
std::cout << global_vec[0] << std::endl;
foo<std::vector<int>&>();
std::cout << global_vec[0] << std::endl;
Run Code Online (Sandbox Code Playgroud)

正如预期的那样,我们得到以下输出:

2
1
2
2
Run Code Online (Sandbox Code Playgroud)

如果你要改变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
}
Run Code Online (Sandbox Code Playgroud)

  • @SeventhSon84 右值引用不能绑定到左值。因此,当分配左值时,“auto&amp;&amp;”被推导为“int&amp;”(左值引用)。但 `int&amp;&amp;` 是无条件右值引用。因此,您必须编写“int&amp;&amp; j = std::move(i);”来明确您的意图,并同时承诺此后不会使用“i”。 (3认同)
  • 我们不能简单地说'T vec = get_vector <T>();`里面的函数foo?或者我将它简化为荒谬的水平:) (2认同)

Xeo*_*Xeo 12

首先,我建议阅读我的这个答案作为侧面阅读,逐步解释如何通用引用的模板参数推导工作.

这是不是意味着,我们被允许窃取资源var

不必要.如果foo()突然返回一个引用怎么办,或者你改了电话但是忘了更新使用var?或者,如果您使用的是通用代码,并且返回类型foo()可能会根据您的参数而改变?

想想auto&&T&&in 完全相同template<class T> void f(T&& v);,因为它(几乎)就是这样.当你需要传递它们或以任何方式使用它们时,你如何处理函数中的通用引用?您std::forward<T>(v)用来获取原始值类别.如果它在传递给你的函数之前是一个左值,它在传递之后会保持左值std::forward.如果它是一个右值,它将再次成为一个右值(记住,一个命名的右值引用是一个左值).

那么,你如何var以通用方式正确使用?使用std::forward<decltype(var)>(var).这将与std::forward<T>(v)上面的函数模板完全相同.如果var是a T&&,你将获得一个rvalue,如果是T&,你将获得一个左值.

那么,回到主题:代码库中的内容auto&& v = f();std::forward<decltype(v)>(v)代码告诉我们什么?他们告诉我们v将以最有效的方式获得并传递.但是请记住,在转发了这样一个变量之后,它可能会被移动,因此在不重置它的情况下进一步使用它是不正确的.

就个人而言,我使用auto&&的通用代码时,我需要一个modifyable变量.完美转发右值正在修改,因为移动操作可能会窃取它的内脏.如果我只是想要懒惰(即,即使我知道它也不拼写类型名称)并且不需要修改(例如,当只打印范围的元素时),我会坚持auto const&.


auto到目前为止是不同的,auto v = {1,2,3};将产生v一个std::initializer_list,而f({1,2,3})将是一个扣除失败.