beginpluses
beginpluses

Reputation: 537

Why is a named rvalue reference an lvalue expression?

I know that a named reference is an lvalue:

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

I've read the explanation — that is because we can take the address of those ref1 and ref2.

But when we take the address of a reference we actually take the address of the referenced object, don't we? So this explanation doesn't seem to be correct.

So why a named reference is an lvalue?

Upvotes: 7

Views: 1622

Answers (3)

Lightness Races in Orbit
Lightness Races in Orbit

Reputation: 385114

That explanation is just a simplification. lvalues aren't defined by being "something you can take the address of", but by a specific set of rules about the value category of expressions. Those rules are carefully constructed so as to result in a self-consistent language in which everything fits together reasonably neatly.

That being said, the explanation does rather fit here, if you consider that by writing ref1, you're not really naming "the reference" but the thing being referred to. That's the magic of references: you're supposed to consider them name aliases rather than entities in their own right.

There are some abstraction leaks surrounding this (particularly, member references), but that's the gist.

You ought to forget about notions like "the reference is an lvalue" and instead think about expressions. Objects have types; expressions have value categories.

Upvotes: 3

Bulat
Bulat

Reputation: 716

Here is an explanation from Scott Meyers's book "Effective Modern C++":

In fact, T&& has two different meanings. One is rvalue reference, of course. Such references behave exactly the way you expect: they bind only to rvalues, and their primary raison d’être is to identify objects that may be moved from.

void f(Widget&& param); // rvalue reference

Widget&& var1 = Widget(); // rvalue reference

auto&& var2 = var1; // not rvalue reference

template<typename T>
void f(std::vector<T>&& param); // rvalue reference

template<typename T>
void f(T&& param); // not rvalue reference

The other meaning for T&& is either rvalue reference or lvalue reference. Such references look like rvalue references in the source code (i.e., T&&), but they can behave as if they were lvalue references (i.e., T&). Their dual nature permits them to bind to rvalues (like rvalue references) as well as lvalues (like lvalue references). Furthermore, they can bind to const or non-const objects, to volatile or non-volatile objects, even to objects that are both const and volatile. They can bind to virtually anything. Such unprecedentedly flexible references deserve a name of their own. I call them universal references.

Upvotes: -1

L. F.
L. F.

Reputation: 20579

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.

Upvotes: 5

Related Questions