Value Category (lvalue and rvalue)
Learned in CS247. This is used for the Move Assignment Operator.
Every expression in C++ has both a type and a Value Category.
lvalue
An lvalue is any expression that you can take the address of.
For example:
int x = 5;
f(x);
- In the above, this expression
x
- we can take its address, therefore it is an lvalue.
rvalue
An rvalue is a temporary value, it will be destroyed “soon”.
f(5)
5
is an rvalue, as we cannot take the address of5
.
Another example
string f() {
return "Hello World";
}
string S = f();
- this expression
f()
is an rvalue. The returned result off
only exists until the end of the line
We cannot run &f()
- the string isn’t stored anywhere permanently, just a temporary until it is saved into s
The references we have seen so far are lvalue references. These can only bind to expressions that are lvalues.
For example:
int x = 5;
int& y = x; // âś“ this is good
However,
int& y = 5; // âś• this doesn't compile. 5 is an rvalue.
An exception: We can bind rvalues to const lvalue references.
f(int& x) {
...
}
f(5) // âś• prohibited
g(const int& x) {
...
}
g(5); // âś“ this is allowed, we won't modify x, the compiler creates a temporary memory location to store the 5 in.
rvalue reference
We can create rvalue references. This extends the lifetime of the rvalue to the lifetime of the reference.
String f() {
return "Hello World";
}
String&& s = f(); // String&& is an rvalue reference
- We can use the temporary value returned by
f
for as long ass
exists.
Most commonly used for overloading functions based on the value category of the expression.
void f(const string& s) { // lvalue or rvalue are both accepted
cout << "1" << endl;
}
void f(string&& s) { // only accepts rvalue
cout << "2" << endl;
}
string s{"cs247"};
f(s); // "1"
f(string{"CS247"}); // "2"
Why is this useful? We’ll see shortly.
Finally, note that type and value categories are independent properties.
void f(string&& s) {
cout << s << endl;
}
- Here, although
s
references an rvalue, we can take s’s addresss
is an lvalue.
Food for thought
What about the
const
qualifier for rvalue reference?
- It’s not very useful, because rvalues are meant to be modified. They will disappear soon.
More reading
You can do something like this
void test2(int&& x) {
cout << "3" << endl;
}
void test(int&& x) {
cout << "2" << endl;
// test2(x); // this doesn't work :( x is an lvalue
// test2(std::move(x)); // this works again!
}
- Use std::move to convert back to rvalue :)
xvalue?
-
a glvalue (“generalized” lvalue) is an expression whose evaluation determines the identity of an object or function;
-
a prvalue (“pure” rvalue) is an expression whose evaluation
- computes the value of an operand of a built-in operator (such prvalue has no result object), or
- initializes an object (such prvalue is said to have a result object).
- The result object may be a variable, an object created by new-expression, a temporary created by temporary materialization, or a member thereof. Note that non-void discarded expressions have a result object (the materialized temporary). Also, every class and array prvalue has a result object except when it is the operand of decltype;
-
an xvalue (an “eXpiring” value) is a glvalue that denotes an object whose resources can be reused;
-
an lvalue (so-called, historically, because lvalues could appear on the left-hand side of an assignment expression) is a glvalue that is not an xvalue;
-
an rvalue (so-called, historically, because rvalues could appear on the right-hand side of an assignment expression) is a prvalue or an xvalue.