Are lvalues really non-temporary objects? - c++

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:
Is a reference
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.

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.

Related

Move assignment operator not invoked [duplicate]

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?
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.
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.
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.

Rvalue reference or lvalue?

I've got a question of similiar nature like this one posted 5 years ago:
Why are rvalues references variables not rvalue?
My major concern is why can I do this:
int&& k = 3;
k++;
but I cannot do this:
(static_cast<int&&>(3))++;
I've always interpreted rvalue references as lvalues since rvalue reference variables are lvalues. But apparently that is not the case. Can someone explain to me why the (static_cast<int&&>(3))++; results in using rvalue as lvalue ?
The confusion is probably arising from the difference between r-value and r-value reference. The former is a value-category which only applies to expressions, while the latter is a type which applies to variables (technically it would need to be an r-value reference of some type, e.g. r-value reference to int).
So the difference between the snippets you've shown is not actually related to the type of the variable, but the value-category of the expression. Postfix operator++ requires the value-category of the operand to be an l-value, regardless of the type of the operand.
In k++, the expression k is an l-value (roughly speaking, it has a name), which is its value-category. The type of the variable k is an r-value reference, but that's fine.
In (static_cast<int&&>(3))++, the expression static_cast<int&&>(3) is an r-value (it doesn't have a name), which is its value-category. Regardless of the type of static_cast<int&&> (which is int), the value-category is wrong, and so you get an error.
Note that the error message using rvalue as lvalue is referring to the value-category of the expression being used. It has nothing to do with the types of the variables.

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?
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.
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.
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.

binding a lvalue expression of type T&&

In the last few days I've been trying to grasp an apparently trivial principle behind lvalue/rvalue references. Let us define a new rvalue reference:
int&& x = 12;
x is therefore an lvalue expression of type int&&. Since x is a lvalue, it can be bound to a lvalue reference of the same type, i.e., a lvalue reference of type int&&. Such a lvalue reference would be defined as:
int&& & ref_x = x; // non-working code, just for the sake of explanation
Of course, it is not possible to explicitly define a reference to a reference, and the correct way to perform the binding is as follows:
int& ref_x = x;
C++ Primer reports the following about using references as initializers:
when we use a reference as an initializer, we are really using the
object to which the reference is bound
On the other hand, the lvalue reference must match the type of the lvalue expression. What am I missing? Is reference collapsing involved in this case?
Thanks.
No, x (as an expression) is an expression of type int. The type of the value of an expression is never a reference. In fact, x is also an lvalue, since it is a named thing.
Also, there are no references to references, for the same reason: References bind to values, and values are never references.
If you're ever confused, just keep telling yourself: The value of an expression is always an object type. Whether the value category of an expression is l or r only determines what sort of things the value can bind to; it has no effect on its type.

Rvalues vs temporaries

Somebody generalized the statement "Temporaries are rvalues". I said "no" and gave him the following example
double k=3;
double& foo()
{
return k;
}
int main()
{
foo()=3; //foo() creates a temporary which is an lvalue
}
Is my interpretation correct?
Temporaries and rvalues are different (but related) concepts. Being temporary is a property of an object. Examples of objects that aren't tempory are local objects, global objects and dynamically created objects.
Being an rvalue is a property of an expression. The opposite of rvalues are lvalues such as names or dereferenced pointers. The statement "Temporaries are rvalues" is meaningless. Here is the relationsip between rvalues and temporary objects:
An rvalue is an expression whose evaluation creates a temporary object which is destroyed at the end of the full-expression that lexically contains the rvalue.
Note that lvalues can also denote temporary objects!
void blah(const std::string& s);
blah(std::string("test"));
Inside the function blah, the lvalue s denotes the temporary object created by evaluating the expression std::string("test").
Your comment "references are lvalues" is also meaningless. A reference is not an expression and thus cannot be an lvalue. What you really mean is:
The expression function() is an lvalue if the function returns a reference.
No. You are returning a reference to an global double, not a temporary.
The same test with a real temporary would be:
double foo() { return 3.0; }
int main() {
foo() = 2.0; // error: lvalue required as left operand of assignment
}
EDIT:
The answer was meant just to identify that the example was wrong, and I did not really want to get into the deeper discussion of whether temporaries are or not rvalues... As others have said, lvalue-ness or rvalue-ness are properties of an expression and not of the object (in the most general sense, not only class instances). Then again, the standard says that:
§3.10/5 The result of calling a function that does not return a reference is an rvalue. User defined operators are functions, and whether such operators expect or yield lvalues is determined by their parameter and return types.
§3.10/6 An expression which holds a temporary object resulting from a cast to a nonreference type is an rvalue (this includes the explicit creation of an object using functional notation (5.2.3)).
Which AFAIK are the circumstances under which temporaries are created. Now, it is also true that you can bind a constant reference to a temporary, in which case you will get a new variable (the reference) that can be used as an lvalue that effectively refers to the temporary object.
The fine line is that expressions that create temporaries are rvalue expressions. You can bind a constant reference to the result of that expression to obtain a variable that can be used as an const-qualified lvalue expression.
Temporaries were so consistently protected from becoming lvalues, that they are now called rvalues. But C++0x will allow temporaries to become lvalues thanks to move semantics. Like in this dumb snippet
void blah(ICanBeTemporary && temp)
{
temp.data = 2; //here temporary becomes lvalue
}
//somewhere
blah(ICanBeTemporary("yes I can"));
Now we have terminology mess. People used to call temporaries rvalues and this is called rvalue reference. Named objects are now considered to be non-rvalue referenced.