Dean Dean - 6 days ago 5
C++ Question

Why both const/nonconst lvalue references bind to a rvalue reference?

This very simple code is allowed:

class s
{
public:
};

const s& tt = std::move(s()); // Also valid without 'const'


But right now I'm wondering why it is allowed..

First we use
std::move
and mark a rvalue (the temporary) as rvalue reference, but why a lvalue reference can bind to a rvalue reference?

Is it because a rvalue reference is also a rvalue?

What's the rationale (or standard quotes) that causes a lvalue reference to bind to a rvalue reference?? That it can be done I got it.

Edit: msvc2015 allows for nonconst lvalue references to bind to rvalue references, the question remains for const lvalue references binding to rvalue references. Sorry, I should have specified the compiler I used.

Answer

Rvalue references are implicitly converted to rvalues (more specifically, to xvalues) as one of the standard conversions (chapter 4 of the C++ standard):

The effect of any implicit conversion is the same as performing the corresponding declaration and initialization and then using the temporary variable as the result of the conversion. The result is an lvalue if T is an lvalue reference type or an rvalue reference to function type (8.3.2), an xvalue if T is an rvalue reference to object type, and a prvalue otherwise

Rvalues (including xvalues) can be bound to const lvalue references so that you can pass a temporary to a function with such a parameter:

void foo(const bar &a);
// ...
foo(bar());

(A temporary is an rvalue; in this case, the result of bar() is an rvalue). There is no reason not to allow this, since the temporary always lives as long as the containing expression - in this case, the function call - and so it will not create a dangling reference inside foo.

This means it is always possible to adjust the signature of a function fun(bar) to fun(const bar &) - and change the implementation accordingly of course! - since temporary arguments will still be accepted, and the semantics should be the same from the perspective of the caller; const implies the object won't be modified, which is also the case if it passed by copy.

Non-const references are not allowed; one practical reason is because they imply that the value should be modified in some meaningful way, and if the value is a temporary, this would be lost. However, you can convert an rvalue to an lvalue if you really want to do so, with some caveats, as described in this answer to another question.

Allowing an rvalue to bind to a const lvalue reference, other than allowing temporary arguments to be passed by reference, is also good for cases where the exact parameter type is not known but you want to allow move semantics if it is possible. Suppose that I am calling a function defined as foo2(const bar &) or foo2(bar) and which may or may not have an overload foo2(bar &&), and I want move semantics to be used if possible; I can safely use std::move to create an rvalue since it will apply in either case. This example might seem a little contrived, but it is the sort of thing that can come up quite a bit when writing templates. In code:

bar bb = bar();
foo2(std::move(bb));
// above is legal if foo2 is declared as:
//    foo2(bar)
//    foo2(const bar &)
// and calls overload foo2(bar &&) if it is defined.

In the case of other rvalue-to-lvalue-reference assignments involving a temporary, the lifetime of the temporary is extended to that of the reference, so that dangling references are not created even in contexts other than parameter passing:

const bar &b = bar(); // temporary lifetime is extended

In the above, the bar object will not be destroyed until the reference b goes out of scope.