void.pointer
void.pointer

Reputation: 26365

Are lvalues really non-temporary objects?

While not the actual standard, I am relying this page on cppreference.com for this specific verbiage:

An lvalue is an expression that identifies a non-temporary object or a non-member function.

The following expressions are lvalues:

  • The name of a variable or function in scope, regardless of type, such as std::cin or std::endl. Even if the variable's type is rvalue reference, the expression consisting of its name is an lvalue expression.

...

My (simplified) comprehension of the quoted section above is that an lvalue:

  1. Is a reference
  2. Must not be a temporary object

I know that references are not objects, so point #2 must mean that in terms of a reference, it must not refer to a temporary object. However, from the expression itself, does that mean a reference to a temporary is not an lvalue? You can have references to temporary and non-temporary objects:

int myvar = 0;
int& ref_myvar = myvar; // Reference to non-temporary

class foo {};
foo const& ref_foo = foo{}; // Reference to temporary

In the above code snippet, usage of ref_foo in a separate expression later would be an lvalue or not? There is a rule for rvalue references that states that rvalue references used by name in an expression are still lvalues (because you refer to the name at that point). Does this rule also apply to lvalue references (since they also have names and using the name itself in an expression would make it an lvalue, as it does for rvalue reference variables)?

I hope I'm making some sort of sense. At least I hope the source of my confusion is evident. Some examples of how the lvalue references above would be used in an expression to prove some points relevant to my question would be a huge help as well.

Upvotes: 2

Views: 296

Answers (1)

I'd say that the cppreference wording is OK for a "general introduction"-level discussion or even for "most everyday uses"-level discussion. However, once you get into the fine technical details, such statements can become somewhat misleading.

The important point is that the value category ("being an lvalue") is the property of an expression, not of an object. You can have a temporary object accessed through an lvalue, and you can have a non-temporary object accessed through an rvalue.

To refer to your examples:

ref_myvar and ref_foo are both lvalues, and always will be, regardless of how you use them. In the following:

foo&& rref = foo{};

rref is, and always will be, an lvalue as well. It is a reference to an rvalue, but the reference itself has a name and so is an lvalue.

If you want to treat an lvalue as an rvalue, you use the standard-provided case operator for that:

rvalue = std::move(lvalue);

Let's analyse this code:

int someint = std::move(ref_myvar);

ref_myvar is an lvalue. std::move(ref_myvar) is an rvalue. someint is an lvalue.


I don't think there is a concise way to define an lvalue without going full standardese, but name (or absence thereof) plays an important part in most definitions. I'll try my hand at such a definition; these are lvalues:

  • An expression which is a name, except for enumerators and member functions.
  • An expression of type "lvalue reference to something."
  • The result of dereferencing a pointer.

Notice that ref_myvar, ref_foo, and rref are all lvalues because they have a name. std::move(ref_myvar) doesn't have a name, and so it's an rvalue.

Upvotes: 3

Related Questions