简体   繁体   English

auto &&告诉我们什么?

[英]What does auto&& tell us?

If you read code like 如果您阅读类似的代码

auto&& var = foo();

where foo is any function returning by value of type T . 其中foo是按T类型的值返回的任何函数。 Then var is an lvalue of type rvalue reference to T . var是对T右值引用的左值。 But what does this imply for var ? 但这对var意味着什么? Does it mean, we are allowed to steal the resources of var ? 这是否意味着我们被允许窃取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? 是否有任何合理的情况,当您使用auto&&告诉代码的读者时,就像您返回unique_ptr<>来告诉您具有独占所有权一样? And what about for example T&& when T is of class type? 而当T是类类型时,例如T&&呢?

I just want to understand, if there are any other use cases of auto&& than those in template programming; 我只是想了解一下,除了模板编程中的auto&&以外,还有其他用例auto&& like the ones discussed in the examples in this article Universal References by Scott Meyers. 就像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 . 通过使用auto&& var = <initializer>您的意思是: 我将接受任何初始化程序,无论它是左值表达式还是右值表达式,并且我将保留其constness This is typically used for forwarding (usually with T&& ). 通常用于转发 (通常与T&& )。 The reason this works is because a "universal reference", auto&& or T&& , will bind to anything . 之所以起作用,是因为“通用引用”( auto&&T&& )将绑定到任何东西

You might say, well why not just use a const auto& because that will also bind to anything? 你可能会说,好吧,为什么不直接使用const auto& ,因为这将绑定到什么? The problem with using a const reference is that it's const ! 使用const引用的问题在于它是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 . 您以后将无法将其绑定到任何非const引用或调用未标记为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: 例如,假设您要获取std::vector ,将迭代器带到其第一个元素,然后以某种方式修改该迭代器指向的值:

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&&的替代方法以下列方式失败:

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! 因此, auto&&可以完美运行! An example of using auto&& like this is in a range-based for loop. 像这样使用auto&&的示例是在基于范围的for循环中。 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. 如果然后在auto&&引用上使用std::forward保留原来是左值或右值的事实,则代码会说: 现在,我已经从左值或右值表达式中获取了对象,我想保留其最初具有的任何价值,以便我可以最有效地使用它-这可能会使它失效。 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. 当原始的初始值设定项是可修改的右值时,这可以使use_it_elsewhere其胆量以提高性能(避免复制)。

What does this mean as to whether we can or when we can steal resources from var ? 对于我们是否可以或何时可以从var窃取资源,这意味着什么? Well since the auto&& will bind to anything, we cannot possibly try to rip out var s guts ourselves - it may very well be an lvalue or even const. 好吧,既然auto&&将绑定到任何东西,我们就不可能尝试自己摆脱var guts-它很可能是左值甚至是const。 We can however std::forward it to other functions that may totally ravage its insides. 但是,我们可以将std::forward传递给可能完全破坏其内部的其他函数。 As soon as we do this, we should consider var to be in an invalid state. 一旦这样做,我们应该考虑var处于无效状态。

Now let's apply this to the case of auto&& var = foo(); 现在,将其应用于auto&& var = foo(); , as given in your question, where foo returns a T by value. ,如您的问题中给出的那样,其中foo通过值返回T In this case we know for sure that the type of var will be deduced as T&& . 在这种情况下,我们肯定知道var的类型将推导为T&& Since we know for certain that it's an rvalue, we don't need std::forward 's permission to steal its resources. 由于我们可以肯定地知道它是一个右值,因此我们不需要std::forward的许可来窃取其资源。 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. 在这种特定情况下, 知道foo按值返回 ,读者应该将其读取为: 我正在对foo返回的临时值进行右值引用,因此我可以很高兴地从中移出。


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. 作为附录,我认为值得一提的是诸如some_expression_that_may_be_rvalue_or_lvalue类的表达式可能出现,而不是出现“代码可能会更改”的情况。 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 . 在这里, get_vector<T>()是一个可爱的表达式,根据泛型T ,它可以是左值或右值。 We essentially change the return type of get_vector through the template parameter of foo . 我们实质上是通过foo的template参数get_vector更改get_vector的返回类型。

When we call foo<std::vector<int>> , get_vector will return global_vec by value, which gives an rvalue expression. 当我们调用foo<std::vector<int>>get_vector将按值返回global_vec ,从而给出一个右值表达式。 Alternatively, when we call foo<std::vector<int>&> , get_vector will return global_vec by reference, resulting in an lvalue expression. 另外,当我们调用foo<std::vector<int>&>get_vector将通过引用返回global_vec ,从而产生一个左值表达式。

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. 如果将代码中的auto&&更改为autoauto&const auto&const auto&&则我们将无法获得所需的结果。


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: 根据是使用左值表达式还是右值表达式初始化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
}

First, I recommend reading this answer of mine as a side-read for a step-by-step explanation on how template argument deduction for universal references works. 首先,我建议将我的答案作为侧面阅读,以逐步解释通用引用的模板参数推导如何工作。

Does it mean, we are allowed to steal the resources of var ? 这是否意味着我们被允许窃取var的资源?

Not necessarily. 不必要。 What if foo() all of a sudden returned a reference, or you changed the call but forgot to update the use of var ? 如果foo()突然返回了引用,或者您更改了调用但忘了更新var的使用怎么办? Or if you're in generic code and the return type of foo() might change depending on your parameters? 或者,如果您使用的是通用代码,并且foo()的返回类型可能会根据您的参数而变化?

Think of auto&& to be exactly the same as the T&& in template<class T> void f(T&& v); 认为auto&&template<class T> void f(T&& v);T&&完全相同template<class T> void f(T&& v); , because it's (nearly ) exactly that. ,因为(几乎是 )。 What do you do with universal references in functions, when you need to pass them along or use them in any way? 当您需要传递或以任何方式使用它们时,如何使用函数中的通用引用? You use std::forward<T>(v) to get the original value category back. 您使用std::forward<T>(v)返回原始值类别。 If it was an lvalue before being passed to your function, it stays an lvalue after being passed through std::forward . 如果在传递给函数之前是左值,则在通过std::forward传递后将保持左值。 If it was an rvalue, it will become an rvalue again (remember, a named rvalue reference is an lvalue). 如果是右值,它将再次变为右值(请记住,命名的右值引用是左值)。

So, how do you use var correctly in a generic fashion? 那么,如何以通用方式正确使用var Use std::forward<decltype(var)>(var) . 使用std::forward<decltype(var)>(var) This will work exactly the same as the std::forward<T>(v) in the function template above. 这将与上面的函数模板中的std::forward<T>(v)完全相同。 If var is a T&& , you'll get an rvalue back, and if it is T& , you'll get an lvalue back. 如果varT&& ,则将返回右值;如果是T& ,则将返回左值。

So, back on topic: What do auto&& v = f(); 因此,回到主题: auto&& v = f();是什么? and std::forward<decltype(v)>(v) in a codebase tell us? 代码库中的std::forward<decltype(v)>(v)告诉我们吗? They tell us that v will be acquired and passed on in the most efficient way. 他们告诉我们,将以最有效的方式获取和传递v Remember, though, that after having forwarded such a variable, it's possible that it's moved-from, so it'd be incorrect use it further without resetting it. 但是请记住,在转发了这样的变量之后,可能会将其移出,因此在不重新设置它的情况下进一步使用它是不正确的。

Personally, I use auto&& in generic code when I need a modifyable variable. 就个人而言,我用auto&&在通用的代码时,我需要一个modifyable变量。 Perfect-forwarding an rvalue is modifying, since the move operation potentially steals its guts. 完美转发右值正在修改,因为移动操作可能会窃取其胆量。 If I just want to be lazy (ie, not spell the type name even if I know it) and don't need to modify (eg, when just printing elements of a range), I'll stick to auto const& . 如果我只是想变得懒惰(即即使我知道它也不要拼写类型名称)并且不需要修改(例如,仅打印范围内的元素时),我会坚持使用auto const&


auto is in so far different that auto v = {1,2,3}; auto相差甚远,因此auto v = {1,2,3}; will make v an std::initializer_list , whilst f({1,2,3}) will be a deduction failure. 将使v成为std::initializer_list ,而f({1,2,3})将是推论失败。

Consider some type T which has a move constructor, and assume 考虑一些具有移动构造函数的类型T ,并假设

T t( foo() );

uses that move constructor. 使用该move构造函数。

Now, let's use an intermediate reference to capture the return from foo : 现在,让我们使用一个中间引用来捕获foo的返回值:

auto const &ref = foo();

this rules out use of the move constructor, so the return value will have to be copied instead of moved (even if we use std::move here, we can't actually move through a const ref) 这会排除使用move构造函数,因此必须复制返回值,而不是移动它(即使我们在此处使用std::move ,我们也无法实际通过const ref进行移动)

T t(std::move(ref));   // invokes T::T(T const&)

However, if we use 但是,如果我们使用

auto &&rvref = foo();
// ...
T t(std::move(rvref)); // invokes T::T(T &&)

the move constructor is still available. move构造函数仍然可用。


And to address your other questions: 并解决您的其他问题:

... Are there any reasonable situations when you should use auto&& to tell the reader of your code something ... ...在任何合理的情况下,您应该使用auto &&&告诉代码的读者...

The first thing, as Xeo says, is essentially I'm passing X as efficiently as possible , whatever type X is. 正如Xeo所说,第一件事本质上无论X是什么类型, 我都尽可能高效地传递X。 So, seeing code which uses auto&& internally should communicate that it will use move semantics internally where appropriate. 因此,看到内部使用auto&&代码应该传达出在适当的地方内部将使用move语义的信息。

... like you do when you return a unique_ptr<> to tell that you have exclusive ownership ... ...就像您返回unique_ptr <>告诉您拥有专有所有权时一样...

When a function template takes an argument of type T&& , it's saying it may move the object you pass in. Returning unique_ptr explicitly gives ownership to the caller; 当函数模板接受类型为T&&的参数时,就是说它可能会移动您传入的对象。返回unique_ptr显式地赋予调用方所有权; accepting T&& may remove ownership from the caller (if a move ctor exists, etc.). 接受T&&可能会删除呼叫者的所有权(如果存在搬家公司等)。

The auto && syntax uses two new features of C++11: auto &&语法使用C ++ 11的两个新功能:

  1. The auto part lets the compiler deduce the type based on the context (the return value in this case). auto部分允许编译器根据上下文(在这种情况下为返回值)推断类型。 This is without any reference qualifications (allowing you to specify whether you want T , T & or T && for a deduced type T ). 这没有任何参考条件(允许您指定是否要对推导类型T TT &T && )。

  2. The && is the new move semantics. &&是新的动作语义。 A type supporting move semantics implements a constructor T(T && other) that optimally moves the content in the new type. 支持移动语义的类型实现了构造函数T(T && other) ,该构造函数可以最佳地移动新类型中的内容。 This allows an object to swap the internal representation instead of performing a deep copy. 这允许对象交换内部表示,而不是执行深层复制。

This allows you to have something like: 这使您可以像:

std::vector<std::string> foo();

So: 所以:

auto var = foo();

will perform a copy of the returned vector (expensive), but: 将执行返回的向量的副本(昂贵),但是:

auto &&var = foo();

will swap the internal representation of the vector (the vector from foo and the empty vector from var ), so will be faster. 将交换向量的内部表示形式(来自foo的向量和来自var的空向量),因此会更快。

This is used in the new for-loop syntax: 这在新的for循环语法中使用:

for (auto &item : foo())
    std::cout << item << std::endl;

Where the for-loop is holding an auto && to the return value from foo and item is a reference to each value in foo . 其中,for循环将fooauto &&保留为foo的返回值,而item是对foo每个值的引用。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM