On How to Recognize Rvalue or Lvalue Reference and If-It-Has-A-Name Rule

On how to recognize Rvalue or Lvalue reference and if-it-has-a-name rule

This is one of the most common "rules of thumb" used to explain what is the difference between lvalues and rvalues.

The situation in C++ is much more complex than that so this can't be nothing but a rule of thumb. I'll try to resume a couple of concepts and try to make it clear why this issue is so complex in the C++ world. First let's recap a bit what happened once upon a time

At the beginning there was C

First, what "lvalue" and "rvalue" used to mean originally, in the world of programming languages in general?

In a simpler language like C or Pascal, the terms used to refer to what could be placed at the Left or at the Right of an assignment operator.

In a language like Pascal where the assignment is not an expression but only a statement, the difference is pretty clear and it's defined in grammatical terms. An lvalue is a name of a variable, or a subscript of an array.

That's because only these two things could stand at the left of an assignment:

i := 42; (* ok *)
a[i] := 42; (* ok *)
42 := 42; (* no sense *)

In C, the same difference applies, and it is still pretty much grammatical in the sense that you could look at a line of code and tell if an expression would produce an lvalue or an rvalue.

i = 42; // ok, a variable
*p = 42; // ok, a pointer dereference
a[i] = 42; // ok, a subscript (which is a pointer dereference anyway)
s->var = 42; // ok, a struct member access

So what changed in C++?

Little languages grow up

In C++ things become much more complex and the difference is not grammatical anymore but involves the type checking process, for two reasons:

  • Everything could stay at the left of an assignment, as long as its type has a suitable overload of operator=
  • References

So this means that in C++ you can't say if an expression will produce an lvalue only by looking at its grammatical structure. For example:

f() = g();

is a statement that would have no sense in C but can be perfectly legal in C++ if, for example, f() returns a reference. That's how expressions like v[i] = j work for std::vector: the operator[] returns a reference to the element so you can assign to it.

So what's the point of having a distinction between lvalues and rvalues anymore? The distinction is still relevant for basic types of course, but also to decide what can be bound to a non-const reference.

That's because you don't want to have legal code like:

int &x = 42;
x = 0; // Have we changed the meaning of a natural number??

So the language specifies carefully what is an lvalue and what isn't, and then says that only lvalues can be bound to non-const references. So the above code is not legal because an integer literal is not an lvalue so a non-const reference cannot be bound to it.

Note that const references are different, since they can bind to literals and temporaries (and local references even extend the lifetime of those temporaries):

int const&x = 42; // It's ok

And until now we've only touched what already used to happen in C++98. The rules were already more complex than "if it has a name it's an lvalue", since you have to consider the references. So an expression returning a non-const reference is still considered an lvalue.

Also, other rules of thumb mentioned here already don't work in all cases. For example "if you can take it's address, it's an lvalue". If by "taking the address" you mean "applying operator&", then it might work, but don't trick yourself into thinking that you can't ever come to have the address of a temporary: The this pointer inside a temporary's member function, for example, will point to it.

What changed in C++11

C++11 puts more complexity into the bin by adding the concept of an rvalue reference, that is, a reference that can be bound to an rvalue even if non-const. The fact that it can only be applied to an rvalue make it both safe and useful. I don't think its needed to explain why rvalue reference are useful, so move on.

The point here is that now we have a lot more of cases to consider. So what is an rvalue now? The Standard actually distinguish between different kinds of rvalues to be able to correctly state the behavior of rvalue references and overload resolution and template argument deduction in the presence of rvalue references. So we have terms like xvalue, prvalue and things like that, which make things more complex.

What about our rules of thumb?

So "everything that has a name is an lvalue" can still be true, but for sure it isn't true that every lvalue has a name. A function returning a non-const lvalue reference is an lvalue. A function returning something by value creates a temporary and it is an rvalue, so is a function returning an rvalue reference.

What about "temporaries are rvalues". It's true, but also non-temporaries can be made into rvalues by simply casting the type (as does std::move).

So I think that all these rules are useful if we keep in mind what they are: rules of thumb.
They'll always have some corner case where they don't apply, because to exactly specify what an rvalue is and what isn't, we can't avoid using the exact terms and rules used in the standard. That's why they were written for!

Rvalue Reference is Treated as an Lvalue?

Is bar an rvalue or an lvalue?

The question answers itself. Whatever has a name is an lvalue(1). So bar is an lvalue. Its type is "rvalue reference to string", but it's an lvalue of that type.

If you want to treat it as an rvalue, you need to apply std::move() to it.


If you can perform any operation on an rvalue reference that you can on an lvalue reference what is the point in differentiating between the two with the "&&" instead of just an "&"?

This depends on your definition of "perform an operation." An lvalue reference and a (named) rvalue reference are pretty much identical in how you can use them in expressions, but they differ a lot in what can bind to them. Lvalues can bind to lvalue references, rvalues can bind to rvalue references (and anything can bind to an lvalue reference to const). That is, you cannot bind an rvalue to an lvalue reference, or vice versa.

Let's talk about a function parameter of rvalue reference type (such as your bar). The important point is not what bar is, but what you know about the value to which bar refers. Since bar is an rvalue reference, you know for certain that whatever bound to it was an rvalue. Which means it's bound to be destroyed when the full expression ends, and you can safely treat it as an rvalue (by stealing its resources etc.).

If you're not the one doing this directly to bar, but just want to pass bar on, you have two options: either you're done with bar, and then you should tell the next one who receives it that it's bound to an rvalue—do std::move(bar). Or you'll need to do some more things with bar, and so you do not want anyone inbetween stealing its resources from under you, so just treat it as an lvalue—bar.

To summarise it: The difference is not in what you can do with the reference once you have it. The difference is what can bind to the reference.


(1) A good rule of thumb, with minor exceptions: enumerators have names, but are rvalues. Classes, namespaces and class templates have names, but aren't values.

Why is a named rvalue reference an lvalue expression?

Per [expr.prim.id.unqual] (8.1.4.1 Unqualified names):

[...] The expression is an lvalue if the entity is a function,
variable, or data member and a prvalue otherwise; it is a bit-field if
the identifier designates a bit-field ([dcl.struct.bind]).

Per [basic]/6:

A variable is introduced by the declaration of a reference other
than a non-static data member or of an object. The variable's name, if
any, denotes the reference or object.

The declaration

int&& ref2 = std::move(x);

is a "declaration of a reference other than a non-static data member." Therefore, the entity denoted by ref2 is a variable. So the expression ref2 is an lvalue.

Why rvalue reference pass as lvalue reference?

template <typename T>
void pass(T&& v) {
reference(v);
}

You are using a Forwarding reference here quite alright, but the fact that there is now a name v, it's considered an lvalue to an rvalue reference.

Simply put, anything that has a name is an lvalue. This is why Perfect Forwarding is needed, to get full semantics, use std::forward

template <typename T>
void pass(T&& v) {
reference(std::forward<T>(v));
}

What std::forward<T> does is simply to do something like this

template <typename T>
void pass(T&& v) {
reference(static_cast<T&&>(v));
}

See this;

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.

C How to identify rvalue and lvalue?

lvalue (from left-hand side (LHS) value) in something that refers to a memory (or register) storage and that you can assign values to. *p++ is an lvalue since it is a dereferenced pointer (i.e. refers to the location in memory that ptr points to while the value of ptr itself is the address of that location) and ++*ptr++ actually means: *ptr = *ptr + 1; ptr = ptr + 1; - it increments the value pointed to by ptr and then increments the pointer value itself. i++ is not an lvalue since it is the value of i incremented by 1 and does not refer to a location in memory. You can think of such values as final - they cannot be further modified and can only be used as values to assign to lvalues. That's why they are called rvalues (from right-hand side (RHS) value).

LHS and RHS refer to both sides of the assignment expression A = B;. A is the LHS and B is the RHS.

Understanding rvalue references

I always remember lvalue as a value that has a name or can be addressed. Since x has a name, it is passed as an lvalue. The purpose of reference to rvalue is to allow the function to completely clobber value in any way it sees fit. If we pass x by reference as in your example, then we have no way of knowing if is safe to do this:

void foo(int &&) {}
void bar(int &&x) {
foo(x);
x.DoSomething(); // what could x be?
};

Doing foo(std::move(x)); is explicitly telling the compiler that you are done with x and no longer need it. Without that move, bad things could happen to existing code. The std::move is a safeguard.

std::forward is used for perfect forwarding in templates.

Is an lvalue reference returned from a function actually an rvalue (from the perspective of the caller)?

Keep in mind two properties when thinking about value-ness of an expression, identity and move-ability. An expression has identity if it has a name or an address, and is moveable if it's going to expire once the expression has been evaluated.

  • Lvalue = has identity and is not moveable
  • Xvalue = has identity and
    is moveable
  • Rvalue = does not have identity and is moveable

refToRef returns by lvalue reference, therefore the Obj that refToRef(o) refers to is an object with identity (&refToRef(o) is defined) and will still be around after the expression has been evaluated. refToRef(o) is therefore an lvalue.

Furthermore, be careful not to confuse type with valueness. If I add a function that returns by value and create an rvalue reference from it, the rvalue reference will be an lvalue. For example

class Obj {};
Obj& refToRef(Obj& o) {
return o;
}

Obj refToVal(Obj& o) {
return o;
}
int main() {
Obj o;
Obj& o2 = refToRef(o);
Obj&& o3 = refToVal(o);
}

o3 has type rvalue refernce to Obj, and as an expression is an lvalue.



Related Topics



Leave a reply



Submit