什么是 auto&&告诉我们? [英] What does auto&& tell us?

查看:45
本文介绍了什么是 auto&&告诉我们?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

如果你读过类似的代码

auto&&var = foo();

其中 foo 是按 T 类型的值返回的任何函数.那么 var 是对 T 的右值引用类型的左值.但这对 var 意味着什么?这是否意味着,我们可以窃取var的资源?是否有任何合理的情况,您应该使用 auto&& 来告诉代码的读者,就像您返回 unique_ptr<> 一样告诉您拥有独家所有权?当 T 是类类型时,例如 T&& 怎么样?

我只是想了解一下,auto&& 是否还有其他用例而不是模板编程中的用例;就像本文示例中讨论的那些通用参考 作者:斯科特·迈耶斯.

解决方案

通过使用 auto&&var = <initializer> 您是在说:我将接受任何初始化程序,无论它是左值还是右值表达式,并且我将保留其常量性.这通常用于转发(通常与 T&& 一起使用).这样做的原因是因为通用引用",auto&&T&&,将绑定到任何东西.>

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

举个例子,假设你想得到一个 std::vector,将迭代器带到它的第一个元素,并以某种方式修改该迭代器指向的值:

auto&&vec = some_expression_that_may_be_rvalue_or_lvalue;自动 i = std::begin(vec);(*i)++;

无论初始化表达式如何,这段代码都可以很好地编译.auto&& 的替代方案在以下方面失败:

auto =>将复制向量,但我们想要一个参考自动&=>只会绑定到可修改的左值const auto&=>将绑定到任何东西,但使它成为常量,给我们 const_iteratorconst auto&&=>将只绑定到右值

因此,auto&& 完美运行!像这样使用 auto&& 的一个例子是在基于范围的 for 循环中.有关详细信息,请参阅我的其他问题.

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

auto&&var = some_expression_that_may_be_rvalue_or_lvalue;//var 被初始化为左值或右值,但 var 本身//是左值,因为命名的右值是左值use_it_elsewhere(std::forward(var));

这允许 use_it_elsewhere 在原始初始值设定项是可修改的右值时为了性能(避免复制)而撕掉它的内脏.

这对于我们是否可以或何时可以从 var 窃取资源意味着什么?好吧,既然 auto&& 会绑定到任何东西,我们不可能自己尝试撕掉 var 的胆量——它很可能是一个左值甚至是 const.然而,我们可以将它std::forward 到其他可能完全破坏其内部的函数.一旦我们这样做,我们应该认为 var 处于无效状态.

现在让我们将其应用于 auto&&var = foo();,如您的问题所示,其中 foo 按值返回 T .在这种情况下,我们肯定知道 var 的类型将被推导出为 T&&.因为我们确定它是一个右值,所以我们不需要 std::forward 的许可来窃取它的资源.在这种特定情况下,知道 foo 按值返回,读者应该将其读作:我正在对从 <返回的临时对象进行右值引用code>foo,所以我可以愉快地离开它.

<小时>

作为一个附录,我认为值得一提的是,当像 some_expression_that_may_be_rvalue_or_lvalue 这样的表达式可能出现时,而不是你的代码可能会改变"的情况.所以这是一个人为的例子:

std::vectorglobal_vec{1, 2, 3, 4};模板 T get_vector(){返回 global_vec;}模板 空 foo(){自动&&vec = get_vector();自动 i = std::begin(vec);(*i)++;std::cout <<vec[0]<

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

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

如果我们这样做:

foo>();std::cout <<global_vec[0] <<std::endl;foo<std::vector<int>&>();std::cout <<global_vec[0] <<std::endl;

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

2122

如果您要将代码中的 auto&& 更改为 autoauto&const auto&const auto&& 那么我们将不会得到我们想要的结果.

<小时>

根据您的 auto&& 引用是使用左值还是右值表达式初始化来更改程序逻辑的另一种方法是使用类型特征:

if (std::is_lvalue_reference::value) {//var 是用左值表达式初始化的} else if (std::is_rvalue_reference::value) {//var 是用右值表达式初始化的}

If you read code like

auto&& var = foo();

where foo is any function returning by value of type T. Then var is an lvalue of type rvalue reference to T. But what does this imply for var? Does it mean, we are allowed to steal the resources of var? Are there any reasonable situations when you should use auto&& to tell the reader of your code something like you do when you return a unique_ptr<> to tell that you have exclusive ownership? And what about for example T&& when T is of class type?

I just want to understand, if there are any other use cases of auto&& than those in template programming; like the ones discussed in the examples in this article Universal References by Scott Meyers.

解决方案

By using auto&& var = <initializer> you are saying: I will accept any initializer regardless of whether it is an lvalue or rvalue expression and I will preserve its constness. This is typically used for forwarding (usually with T&&). The reason this works is because a "universal reference", auto&& or T&&, will bind to anything.

You might say, well why not just use a const auto& because that will also bind to anything? The problem with using a const reference is that it's const! You won't be able to later bind it to any non-const references or invoke any member functions that are not marked const.

As an example, imagine that you want to get a std::vector, take an iterator to its first element and modify the value pointed to by that iterator in some way:

auto&& vec = some_expression_that_may_be_rvalue_or_lvalue;
auto i = std::begin(vec);
(*i)++;

This code will compile just fine regardless of the initializer expression. The alternatives to auto&& fail in the following ways:

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

So for this, auto&& works perfectly! An example of using auto&& like this is in a range-based for loop. See my other question for more details.

If you then use std::forward on your auto&& reference to preserve the fact that it was originally either an lvalue or an rvalue, your code says: Now that I've got your object from either an lvalue or rvalue expression, I want to preserve whichever valueness it originally had so I can use it most efficiently - this might invalidate it. As in:

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));

This allows use_it_elsewhere to rip its guts out for the sake of performance (avoiding copies) when the original initializer was a modifiable rvalue.

What does this mean as to whether we can or when we can steal resources from var? Well since the auto&& will bind to anything, we cannot possibly try to rip out vars guts ourselves - it may very well be an lvalue or even const. We can however std::forward it to other functions that may totally ravage its insides. As soon as we do this, we should consider var to be in an invalid state.

Now let's apply this to the case of auto&& var = foo();, as given in your question, where foo returns a T by value. In this case we know for sure that the type of var will be deduced as T&&. Since we know for certain that it's an rvalue, we don't need std::forward's permission to steal its resources. In this specific case, knowing that foo returns by value, the reader should just read it as: I'm taking an rvalue reference to the temporary returned from foo, so I can happily move from it.


As an addendum, I think it's worth mentioning when an expression like some_expression_that_may_be_rvalue_or_lvalue might turn up, other than a "well your code might change" situation. So here's a contrived example:

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;
}

Here, get_vector<T>() is that lovely expression that could be either an lvalue or rvalue depending on the generic type T. We essentially change the return type of get_vector through the template parameter of foo.

When we call foo<std::vector<int>>, get_vector will return global_vec by value, which gives an rvalue expression. Alternatively, when we call foo<std::vector<int>&>, get_vector will return global_vec by reference, resulting in an lvalue expression.

If we do:

foo<std::vector<int>>();
std::cout << global_vec[0] << std::endl;
foo<std::vector<int>&>();
std::cout << global_vec[0] << std::endl;

We get the following output, as expected:

2
1
2
2

If you were to change the auto&& in the code to any of auto, auto&, const auto&, or const auto&& then we won't get the result we want.


An alternative way to change program logic based on whether your auto&& reference is initialised with an lvalue or rvalue expression is to use type traits:

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&amp;&amp;告诉我们?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆