Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Rvalue reference and auto&& local variables

Tags:

c++

c++11

What is the point of defining a local variable to be an rvalue reference or a forwarding (universal) reference? As far as I understand, any variable that has a name is an lvalue and will be treated as such moving forward.

Example:

Widget&& w1 = getWidget();
auto&& w2 = getWidget();

w1 and w2 are both lvalues, and will be treated as such if they're passed as arguments later on. Their decltype is probably not, but what difference does this make? Why would anyone need to define variables that way?

like image 506
Haitham Gad Avatar asked Mar 21 '16 14:03

Haitham Gad


2 Answers

If you have a function returning a temporary which cannot be moved.

Foo some_function();

auto&& f = some_function();

This is legal. auto f = some_function(); will either copy (which could be expensive), or fail to compile (if the class also cannot be copied).

In general, auto&& deduces down to either an r or lvalue reference depending on what it is initialized with, and if initialized with a temporary extends its lifetime while giving you access to it as an lvalue.

A classic use is in the 'just-loop' pattern:

for( auto&& x : some_range )

where there is actually a auto&& x = *it; in the code generated.

You cannot bind a non-constant lvalue reference to a temporary, so your other choice is Widget const&, which doesn't let you modify the temporary during its lifetime.

This technique is also useful to decompose a complex expression and see what is going on. So long as you aren't working with extremely fragile expression templates, you can take the expression a+b+c*d and turn it into

auto&& c_times_d = d*d;
auto&& b_plus_c_times_d = b + decltype(c_times_d)c_times_d;
auto&& c_plus_b_plus_c_times_d = c + decltype(b_plus_c_times_d)b_plus_c_times_d;

and now you have access to the temporary objects whose lifetime is extended, and you can easily step through the code, or introduce extra steps between steps in a complex expression: and this happens mechanically.

The concern about fragile expression templates only holds if you fail to bind every sub-expression. (Note that using -> can generate a myriad of sub-expressions you might not notice.)

I use auto&& when I want to say "I'm storing the return value of some function, as is, without making a copy", and the type of the expression is not important. auto is when I want to make a local copy.

In generic code it can be highly useful.

Foo const& a(Foo*);
Bar a(Bar*);

template<class T>
auto do_stuff( T*ptr ) {
  auto&& x = a(ptr);
}

here if you pass a Bar* it stores the temporary, but if you pass a Foo* to do_stuff it stores the const&.

It does the least it can.

Here is an example of a function returning a non-movable non-copyable object, and how auto&& lets you store it. It is otherwise useless, but it shows how it works:

struct Foo {
  Foo(&&)=delete;
  Foo(int x) { std::cout << "Foo " << x << " constructed\n";
};
Foo test() {
  return {3};
}

int main() {
  auto&& f = test();
}
like image 190
Yakk - Adam Nevraumont Avatar answered Sep 23 '22 07:09

Yakk - Adam Nevraumont


As far as I know, there is no real, aka widely used purpose to define a local rvalue reference, since their nature, to not to bind to lvalues, is only helpful in overloading and deduction, so to define them as parameters of a function.

One could use them, to bind them to temporary values like int &&rref = 5*2; but since almost all compilers are optimizing the expression int i = 5*2; there is no real need, in therm of performance or avoid copying.

like image 34
Superlokkus Avatar answered Sep 24 '22 07:09

Superlokkus