10

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?

Haitham Gad
  • 1,529
  • 2
  • 13
  • 23
  • 1
    What if `getWidget()` returns an rvalue reference that you don't want to immediately move from? – Barry Mar 21 '16 at 14:41
  • @Barry that would be so subtle I'd ban the very idea from the language. – akappa Mar 21 '16 at 14:45
  • 1
    @Barry Does any of these definitions prevent moving the return value to the local variable? – Haitham Gad Mar 21 '16 at 14:47
  • 1
    Also, `Widget&& w1 = ...` and `auto&& w2 = ...` mean rather different things. The latter can bind to lvalues. – Barry Mar 21 '16 at 14:49
  • @Barry I understand, but you can get the same effect using auto&. Why use auto&&? – Haitham Gad Mar 21 '16 at 14:50
  • Because `auto&` can't bind to rvalues and `auto&&` can. `auto&&` works regardless of whether `getWidget()` returns a `Widget`, `Widget&`, `Widget const&`, or `Widget&&`. `auto&` works only in the 2nd and 3rd cases. – Barry Mar 21 '16 at 14:51
  • 3
    It is used in the mechanics to for a [ranged based for loop](http://en.cppreference.com/w/cpp/language/range-for) – NathanOliver Mar 21 '16 at 14:55
  • Also consider http://stackoverflow.com/a/33904463/3537677 for the whole naming controversy – Superlokkus Mar 21 '16 at 14:58
  • I also see auto&& used this way by Eric Niebler in his ranges library, but I have no clue what is it good for: https://github.com/ericniebler/range-v3/blob/master/include/range/v3/algorithm/copy_if.hpp – Haitham Gad Mar 21 '16 at 15:00
  • 1
    Some iterators like those of `std::vector` return proxy objects rather than references to the elements. `auto&` fails for these but `auto&&` works. – Simple Mar 21 '16 at 15:18

4 Answers4

10

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();
}
Yakk - Adam Nevraumont
  • 262,606
  • 27
  • 330
  • 524
  • Looks like unmovable classes cannot be returned from functions, even if they have a copy constructor (tried it). – Haitham Gad Mar 21 '16 at 18:30
  • @HaithamGad Sure they can, via direct construction. I'll add an example. – Yakk - Adam Nevraumont Mar 21 '16 at 18:53
  • 3
    Interesting! Why does it fail if you change `test()` to `return Foo(3);` or `return 3;`? – Haitham Gad Mar 21 '16 at 19:43
  • 5
    @HaithamGad Because that does not directly construct the return value. In both cases, it logically creates a temporary `Foo`, then copies it into the return value (well, moves). This copy is elided, but *it must be legal* in order for the elision to occur. Meanwhile, `return {3};` simply directly constructs the return value. – Yakk - Adam Nevraumont Mar 21 '16 at 19:51
2

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.

Superlokkus
  • 4,731
  • 1
  • 25
  • 57
1

One example could be an array

template<class T, int N> using raw_array = T[N];

then

auto && nums = raw_array<int,4>{101, 102, 103, 104};

this allows the temporary to be used as if it was an ordinary array.

Jarod42
  • 203,559
  • 14
  • 181
  • 302
QuentinUK
  • 2,997
  • 21
  • 20
1

A variable declared auto&& will follow perfect forwarding rules. Try it for yourself. This is even how perfect forwarding is done with lambdas in c++14.

const Type& fun1();
Type&& fun2();

auto&& t1 = fun1(); // works
auto&& t2 = fun2(); // works too
Guillaume Racicot
  • 39,621
  • 9
  • 77
  • 141