C++ lvalue and rvalue
More than the left side or right side…
Value Categories and References
They are totally different. Don’t get confused with these two different things.
Value Categories
- lvalue / rvalue
References
- lvalue reference
- rvalue reference
- const reference
1. Value Categories
Value categories properties of expressions
- lvalue
- rvalue
Identity
- does the expression have a name?
- does the expression have a memory location?
- can you take the address of the expression?
Definition
lvalue
- has a name
must be able to
take its address of memory location via&
Foo *pfoo = new Foo;
pfoo is a lvalue
- the data type is “pointer to Foo”
- It has a name
- It can take the address of the pfoo
pfoo is a lvalue
- even if it doesn't have a name, we can still take the address of *pfoo
int i = 1; // i is lvalue
i = 0; // lvalue can be assigned with a value
int* p = &i; // lvalue can take its address of memory location via &
// function returning a reference is lvalue
int& foo();
foo() = 9; // foo() is an lvalue hence it can get assigned
int* p = &foo(); // foo() is lvalue hence it can take it's address
rvalue
- does NOT have a name
- can’t take its address
- literals (such as 2, “hello world”, true, or nullptr)
- can’t be assigned with a value
- temporary objects returned from functions.
- lifetime usually ends with the current statement
int j = 0;
j = 4; // j is lvalue// a function returning a value is rvalue
int boo();
j = boo(); // np, j is lvalue, boo() is an rvalue
int* p = &boo(); // error, cannot take the address of an rvalue
2. Pass by value vs Pass by reference
Pass by Value
class Foo{};
void func(Foo f); // f is passed by value
Foo foo; // foo is lvalue
func(foo); // call with an lvalue - valid
func(Foo{}); // call with an rvalue - valid
Pass by Reference
class Foo{};
void func(Foo& f); // f is passed by lvalue reference
Foo foo; // foo is lvalue
func(foo); // call with an lvalue - valid
func(Foo{}); // call with an rvalue - error
to fix the above,
void func(const Foo& f); // f is passed by const reference
or
class Foo{};
void func(Foo&& f); // f is passed by rvalue reference
Foo foo; // foo is lvalue
// func(foo); // call with an lvalue - error
func(std::move(foo)) // need to cast lvalue foo to rvalue using move
func(Foo{}); // call with an rvalue - valid
3. References
lvalue reference
- called method or function can/will modify the data
- the caller will observe any modifications made
const reference
- called method or function can NOT modify the data
rvalue reference
- called method or function can/will modify the data
- the caller will not and should not observe any modification made
- declared using &&
C++ 11 extended the notion of rvalues by letting you bind an rvalue (category) to an rvalue reference.
Examples
Examples from codespice. (see the end of the article for link)
4. Code Practice
below code will end up with an error.
This is invalid. Since the callee can change but the caller can’t see the change made by the callee. So, this is wrong.
error message
It says, “cannot bind non-const lvalue reference of ….”
Solutions
Solution 1
Add const
Solution 2
Provide a function that takes rvalue reference for a parameter.
Caveat:
The second function with rvalue reference as its parameter will take the only rvalue as an argument, such as literal or temporary value (such as “hello world”)
Code Practice 2
I guess nobody will code like below, but if you do, it will be disastrous and will end up crashing because you are returning a reference to a temporary variable in the stack that will go away.
Fix
Still, I discourage doing this but below can fix it.
Disclaimer
I studied lvalue and rvalue concept with copperspice c++ education materials and this page includes some summary notes from it.