It declares an rvalue reference (standards proposal doc).
Here's an introduction to rvalue references.
Here's a fantastic in-depth look at rvalue references by one of Microsoft's standard library developers.
CAUTION: the linked article on MSDN ("Rvalue References: C++0x Features in VC10, Part 2") is a very clear introduction to Rvalue references, but makes statements about Rvalue references that were once true in the draft C++11 standard, but are not true for the final one! Specifically, it says at various points that rvalue references can bind to lvalues, which was once true, but was changed.(e.g. int x; int &&rrx = x; no longer compiles in GCC) – drewbarbs Jul 13 '14 at 16:12
The biggest difference between a C++03 reference (now called an lvalue reference in C++11) is that it can bind to an rvalue like a temporary without having to be const. Thus, this syntax is now legal:
T&& r = T();
rvalue references primarily provide for the following:
Move semantics. A move constructor and move assignment operator can now be defined that takes an rvalue reference instead of the usual const-lvalue reference. A move functions like a copy, except it is not obliged to keep the source unchanged; in fact, it usually modifies the source such that it no longer owns the moved resources. This is great for eliminating extraneous copies, especially in standard library implementations.
For example, a copy constructor might look like this:
foo(foo const& other)
{
this->length = other.length;
this->ptr = new int[other.length];
copy(other.ptr, other.ptr + other.length, this->ptr);
}
If this constructor were passed a temporary, the copy would be unnecessary because we know the temporary will just be destroyed; why not make use of the resources the temporary already allocated? In C++03, there's no way to prevent the copy as we cannot determine whether we were passed a temporary. In C++11, we can overload a move constructor:
foo(foo&& other)
{
this->length = other.length;
this->ptr = other.ptr;
other.length = 0;
other.ptr = nullptr;
}
Notice the big difference here: the move constructor actually modifies its argument. This would effectively "move" the temporary into the object being constructed, thereby eliminating the unnecessary copy.
The move constructor would be used for temporaries and for non-const lvalue references that are explicitly converted to rvalue references using the std::move
function (it just performs the conversion). The following code both invoke the move constructor for f1
and f2
:
foo f1((foo())); // Move a temporary into f1; temporary becomes "empty"
foo f2 = std::move(f1); // Move f1 into f2; f1 is now "empty"
Perfect forwarding. rvalue references allow us to properly forward arguments for templated functions. Take for example this factory function:
template <typename T, typename A1>
std::unique_ptr<T> factory(A1& a1)
{
return std::unique_ptr<T>(new T(a1));
}
If we called factory<foo>(5)
, the argument will be deduced to be int&
, which will not bind to a literal 5, even if foo
's constructor takes an int
. Well, we could instead use A1 const&
, but what if foo
takes the constructor argument by non-const reference? To make a truly generic factory function, we would have to overload factory on A1&
and on A1 const&
. That might be fine if factory takes 1 parameter type, but each additional parameter type would multiply the necessary overload set by 2. That's very quickly unmaintainable.
rvalue references fix this problem by allowing the standard library to define a std::forward
function that can properly forward lvalue/rvalue references. For more information about how std::forward
works, see this excellent answer.
This enables us to define the factory function like this:
template <typename T, typename A1>
std::unique_ptr<T> factory(A1&& a1)
{
return std::unique_ptr<T>(new T(std::forward<A1>(a1)));
}
Now the argument's rvalue/lvalue-ness is preserved when passed to T
's constructor. That means that if factory is called with an rvalue, T
's constructor is called with an rvalue. If factory is called with an lvalue, T
's constructor is called with an lvalue. The improved factory function works because of one special rule:
When the function parameter type is of
the form T&&
where T
is a template
parameter, and the function argument
is an lvalue of type A
, the type A&
is
used for template argument deduction.
Thus, we can use factory like so:
auto p1 = factory<foo>(foo()); // calls foo(foo&&)
auto p2 = factory<foo>(*p1); // calls foo(foo const&)
Important rvalue reference properties:
- For overload resolution, lvalues prefer binding to lvalue references and rvalues prefer binding to rvalue references. Hence why temporaries prefer invoking a move constructor / move assignment operator over a copy constructor / assignment operator.
- rvalue references will implicitly bind to rvalues and to temporaries that are the result of an implicit conversion. i.e.
float f = 0f; int&& i = f;
is well formed because float is implicitly convertible to int; the reference would be to a temporary that is the result of the conversion.
- Named rvalue references are lvalues. Unnamed rvalue references are rvalues. This is important to understand why the
std::move
call is necessary in: foo&& r = foo(); foo f = std::move(r);
It's only ever problematic in the case where the initializer is a function call that returns a short-lived rvalue reference. With less words and more code:
// Fine; lifetime extension applies!
auto&& ref = 42;
auto id = [](int&& i) -> int&& { return std::move(i); };
auto&& uhoh = id(42);
// uhoh is now a stale reference; can't touch it!
In contrast, auto uhoh = id(42);
would have worked fine.
In your case, because std::make_tuple
returns a value and not an rvalue reference there is no problem.
I'm of the opinion that the real danger is from those functions and function templates with rvalue reference parameters and that return an rvalue reference to either those of some subobjects which lifetimes depend on those. (That being said, something as simple as auto&& ref = std::move(42);
exhibits the problem!)
The situation is not entirely new from C++11, consider: T const& ref = bar(T_factory());
.
Best Answer
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 withT&&
). The reason this works is because a forwarding reference,auto&&
orT&&
, 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 aconst
reference is that it'sconst
! You won't be able to later bind it to any non-const references or invoke any member functions that are not markedconst
.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:This code will compile just fine regardless of the initializer expression. The alternatives to
auto&&
fail in the following ways:So for this,
auto&&
works perfectly! An example of usingauto&&
like this is in a range-basedfor
loop. See my other question for more details.If you then use
std::forward
on yourauto&&
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: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 theauto&&
will bind to anything, we cannot possibly try to rip outvar
s guts ourselves - it may very well be an lvalue or even const. We can howeverstd::forward
it to other functions that may totally ravage its insides. As soon as we do this, we should considervar
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 aT
by value. In this case we know for sure that the type ofvar
will be deduced asT&&
. Since we know for certain that it's an rvalue, we don't needstd::forward
's permission to steal its resources. In this specific case, knowing thatfoo
returns by value, the reader should just read it as: I'm taking an rvalue reference to the temporary returned fromfoo
, 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:Here,
get_vector<T>()
is that lovely expression that could be either an lvalue or rvalue depending on the generic typeT
. We essentially change the return type ofget_vector
through the template parameter offoo
.When we call
foo<std::vector<int>>
,get_vector
will returnglobal_vec
by value, which gives an rvalue expression. Alternatively, when we callfoo<std::vector<int>&>
,get_vector
will returnglobal_vec
by reference, resulting in an lvalue expression.If we do:
We get the following output, as expected:
If you were to change the
auto&&
in the code to any ofauto
,auto&
,const auto&
, orconst 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: