Lvalue to Rvalue Reference Binding

Lvalue to rvalue reference binding

Insert(key, Value()); // Compiler error here

key here is Key&& key - this is an lvalue! It has a name, and you can take its address. It's just that type of that lvalue is "rvalue reference to Key".

You need to pass in an rvalue, and for that you need to use std::move:

Insert(std::move(key), Value()); // No compiler error any more

I can see why this is counter-intuitive! But once you distinguish between and rvalue reference (which is a reference bound to an rvalue) and an actual rvalue, it becomes clearer.

Edit: the real problem here is using rvalue references at all. It makes sense to use them in a function template where the type of the argument is deduced, because this allows the argument to bind to either an lvalue reference or an rvalue reference, due to reference collapsing rules. See this article and video for why: http://isocpp.org/blog/2012/11/universal-references-in-c11-scott-meyers

However, in this case the type of Key is not deduced when the function is called, as it has already been determined by the class when you instantiated FastHash<std::string, ... >. Thus you really are prescribing the use of rvalue references, and thus using std::move fixes the code.

I would change your code to that the parameters are take by value:

template <typename Key, typename Value, typename HashFunction, typename Equals>
Value& FastHash<Key, Value, HashFunction, Equals>::operator[](Key key)
{
// Some code here...

Insert(std::move(key), Value());

// More code here.
}

template <typename Key, typename Value, typename HashFunction, typename Equals>
void FastHash<Key, Value, HashFunction, Equals>::Insert(Key key, Value value)
{
// ...
}

Don't worry too much about extra copies due to use of value arguments - these are frequently optimised out by the compiler.

Understanding the warning: binding r-value to l-value reference

You are taking a reference to a temporary object. The only legal way to do this is either :

const object& (const l-value reference), or

object&& (mutable r-value reference)

This is a (deliberate) language limitation.

further discussion:

Assigning a temporary to a reference extends the lifetime of the temporary so that it matches the lifetime of the reference. Therefore, surprisingly to many beginners, this is legal:

{
const string& s = foo();
cout << s << endl; // the temporary to which s refers is still alive
}
// but now it's destroyed

However, it would normally be a logic error to take a mutable reference to a temporary so this is disallowed in the language:

{
string& s = foo(); // this is not possible
s += "bar"; // therefore neither is this
// the implication is that since you modified s, you probably want to
// preserve it
}
// ... but now it's destroyed and you did nothing with it.

here's a more realistic reason why it's probably a logic error, given:

string foo();         // function returning a string
void bar(string& s); // this function is asserting that it intends to *modify*
// the string you sent it

// therefore:

bar(foo()); // makes no sense. bar is modifying a string that will be discarded.
// therefore assumed to be a logic error

you would have to replace the above with:

  string s = foo();
s += "bar";
// do something here with s

Note that there is no overhead whatsoever for capturing the temporary in a named variable (l-value).

r-value references are designed to be the subject of a move-constructor or move-assignment. Therefore it makes sense that they are mutable. Their very nature implies that the object is transient.

thus, this is legal:

string&& s = foo();    // extends lifetime as before
s += "bar";
baz(std::move(s)); // move the temporary into the baz function.

It might help you to remember that specifying && is you asserting that you know that the variable is a mutable temporary.

But the real reason it's allowed is so that this will work:

string foo();   // function that returns a string
void bar(string&& s); // function that takes ownership of s

bar(foo()); // get a string from foo and move it into bar

// or more verbosely:

string s = foo();
bar(move(s));

prior to c++11, bar would have to have been written one of these ways:

void bar(string s);   // copy a string

// resulting in:

const string& s = foo();
bar(s); // extra redundant copy made here

void bar(const string& s); // const l-value reference - we *may* copy it
// resulting in:

const string& s = foo();
bar(s); // maybe an extra redundant copy made here, it's up to bar().

Can't bind lvalue to rvalue reference

Quote from WIKI

For safety reasons, some restrictions are imposed. A named variable will never be considered to be an rvalue even if it is declared as such. To get an rvalue, the function template std::move() should be used. Rvalue references can also be modified only under certain circumstances, being intended to be used primarily with move constructors.

Bind rvalue reference to (auto generated) lvalue

name1 is an lvalue... but it's not a std::string, it's a char const*. Constructing a StrHolder from it involves making a temporary std::string using its implicit constructor from char const*, then invoking StrHolder::StrHolder() with an rvalue reference to that temporary. name1 gets left alone, and is never moved from.

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

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 is 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 that could be defined as foo2(const bar &) or foo2(bar) and which in the former case may or may not have an overload foo2(bar &&), and I want to allow move semantics to be used if possible (assuming that a foo2(bar &&) overload will use move semantics in its implementation); 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 either:
// foo2(bar)
// foo2(const bar &)
// and in latter case 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.

Template allows lvalue to become bound with rvalue reference

When T is a template argument T&& is a forwarding reference, not a r-value one. T gets deduced as T for r-values and T& for l-values. Such a reference binds to anything.

For what reason would it be necessary to bind a const lvalue reference to an rvalue?

constexpr int& v and const int& v are not the same thing. constexpr int& v means create a reference to an int and that reference is a constexpr. You can't bind that to a temporary because lvalue references to mutable object are not allowed to bind to a temporary.

const int& v on the other hand means create a reference to a const int. This is allowed to bind to a temporary since you cannot modify the state of the temporary.

In what case would it be necessary to bind a const lvalue reference to an rvalue?

Pre C++11, this was the only way to capture a reference to a temporary. For example

std::vector<int> make_vec(int size) { return std::vector<int>(size); }

const std::vector<int>& foo = make_vec(1000);

In the above code, ignoring any sort of optimization, this is the only way to access the vector that make_vec returns without making a copy of it. This was/is very important as it helps to prevent a lot of copies that C++'s value semantics naturally creates.

Post C++11 it's not needed as much as we now have rvalue references that allow you to bind a reference to a temporary, and you can modify it since they are typically not const.

C++11: Why rvalue reference parameter implicitly converted to lvalue

One, the x argument to fn isn't an r-value reference, it's a "universal reference" (yes, this is rather confusing).

Two, the moment you give an object a name, that name is not an r-value unless explicitly "fixed", either with std::move (to make it an r-value reference, always), or with std::forward (to convert it back to its original type in the case of universal references). If you want to avoid the complaint, use std::forward to forward as the original type:

template <class T>
void fn (T&& x) {
overloaded(std::forward<T>(x));
}


Related Topics



Leave a reply



Submit