Summary
In this post, I will introduce lvalues and rvalues in C/C++. But I leave rvalue references and move semantics to the next post.
Details
Definition
- Lvalues: (locator value) represents an object that occupies some unique location in memory (has an address in some scope).
- Rvalues: not lvalues.
Important points and examples
1. Rvalues can’t be assigned
An assignment expects an lvalue as its left operand. See the codes below.
// lvalue in left operand
int var;
var = 4;
// rvalue in left operand
4 = var; // ERROR!
(var + 1) = 4; // ERROR!
2. Function can return lvalue or rvalue
We can assign value returned by a function. See the codes below.
// error: lvalue required as left operand of assignment
int foo() {return 2;}
int main()
{
foo() = 2;
return 0;
}
// good
int globalvar = 20;
int& foo()
{
return globalvar;
}
int main()
{
foo() = 10;
return 0;
}
Actually []
in map
works by using this.
std::map<int, float> mymap;
// [] returns a reference that can be assigned to
mymap[10] = 5.6;
3. Unmodifiable lvalues
const int a = 10; // 'a' is an lvalue
a = 10; // but it can't be assigned!
4. Conversions between lvalues and rvalues
Implicit lvalues-to-rvalues
Language constructs operating on object values require rvalues as arguments. All lvalues that aren’t arrays or functions can be converted to rvalues.
int a = 1; // a is an lvalue
int b = 2; // b is an lvalue
int c = a + b; // + needs rvalues, so a and b are converted to rvalues
// and an rvalue is returned
Explicit lvalues-to-rvalues
The unary address-of operator &
takes an lvalue argument and produces an rvalue.
int var = 10;
int* bad_addr = &(var + 1); // ERROR: lvalue required as unary '&' operand
int* addr = &var; // OK: var is an lvalue
&var = 40; // ERROR: lvalue required as left operand
// of assignment.
Explicit rvalues-to-lvalues
Implicit rvalues-to-lvalues is not possible. But we can use things like unary operator *
(dereference), which takes an rvalue argument but produces an lvalue as a result.
int arr[] = {1, 2};
int* p = &arr[0];
// good: p + 1 is an rvalue, but *(p + 1) is an lvalue
*(p + 1) = 10;
5. Lvalue references
&
can produce “lvalue references”. It takes a lvalue and return a lvalue reference. Non-const lvalue references cannot be assigned rvalues, since that would require an invalid rvalue-to-lvalue conversion.
std::string& sref1 = "Hello world!"; // ERROR: invalid initialization of non-const reference of type 'std::string&' from an rvalue of type 'std::string'
const std::string& sref2 = "Hello world!"; // good
std::string s("Hello world!");
std::string& sref3 = s; // good