This blog post should be a relative simple explanation of what rvalue and lvalues are. It will not go in depth but it should be enough for a easy understanding, and enough if it every comes up.
First and easiest explanation is, that a Lvalue is always on the left side of an assignment and and Rvalue always on the right side of an assignment. In the example bellow we see the most simple explanation of Lvalue and Rvalue. I created a define statement for the rvalue.
#define rvalue 10
int lvalue = rvalue
int otherLvalue = 42 // 42 is the rvalue
int thirdLvalue = lvalue + otherLvalue // The addition is also an rvalue
So now we know where an lvalue and rvalue can be, but what other differences to they have.
Lvalue
An Lvalue has always an address so it always lives in some specific address in memory.
Rvalue
An Rvalue has no specific address and usualy only lives for a short amount of time.
We can not take the address of an rvalue
So with this new information we can also show some different examples.
int lvalue = 10;
int* pLvalue = &lvalue;
lvalue is in our example a variable which gets a specific address in memory. The value 10(literal constant) is our rvalue it has no specific address in memory. We can get the address of lvalue with the & operator which takes an lvalue as argument and returns a rvalue. So in this case the expression '&lvalue' is a rvalue.
We can also try the other way as shown in the example bellow
int x;
123 = x;
The example obviously does not work. Because 123 is a constant literal or rvalue and has no specific address so we can not assign a value to something which has no place to live. With clang++ we would get such an error error: expression is not assignable
We can also try to grab the address of an rvalue
int* x = &123;
Clang++ would tell us: error: cannot take the address of an rvalue of type 'int'
. Which was to be expected because I mentioned earlier, that rvalues have no specific address and the & operator expects an lvalue.
So far I only talked about simple examples with variables, but what happens in the context of functions?
int val()
{
return 123;
}
val() = 1; // Error
As mentioned already on the left side of an assignment we have always an lvalue. clang++ would say the following: error: expression is not assignable
as we can see we get the same error as above. Because the the value returned by the function has no address and is itself only a rvalue.
Is it even possible to make the example above work? The answer is yes, we could use the help of references to be able to not return a rvalue from the function but to reference an actual variable which has an address in memory.
int global = 123;
int& val() // Changed signature to return a reference
{
return global;
}
val() = 1;
The example above works because we return a reference which points to an address in memory in our case the variable global.
In C++ there are often situations where lvalue to rvalue conversions happen under the hood.
int z = x + y;
In this addition we have an implicite conversion from the lvalues x,y to rvalues. Because the + operator expects two rvalues as we can see in the specification: T operator+(const T &a, const T2 &b);
. From what I understand the compiler would fetch the values of the address of x and y and then pass it to the + operator which in turn also returns an rvalue.
For fundamental types such as int's the conversion is done automagical, but for custom types we would have to implement such a conversion ourselfs. One example would be:
class MyType {
public:
MyType(int value) : data(value) {}
operator int() const {
return data;
}
private:
int data;
};
int main() {
MyType obj(10); // Create an object of MyType.
int value = obj; // 'obj' is implicitly converted to an 'int' using the conversion operator.
// The value of 'obj' (10) is extracted and assigned to 'value'.
return 0;
}
As we can see we implemented the conversion to int explicitly with int() which returns an rvalue. But in general those conversions are done implicitly and we do not have to care to much.
Another implicite conversion is:
int arr[5] = {1, 2, 3, 4, 5};
int element = arr[2]; // 'arr[2]' is an lvalue, but it's converted to an rvalue for assignment to 'element'.
We are able to reference lvalues with:
int x = 10;
int& y = x;
++y;
In this case we reference the variable x via y (With the operator int& a reference to x). We ensure that y is referencing a. A reference has always to reference an existing object on a specific address. We would not be able to do something like this:
int& y = 123;
clang++ would greet us with: error: non-const lvalue reference to type 'int' cannot bind to a temporary of type 'int'
(When we read the error message we should see that clang++ is mentioning something with 'const' this will be important later)
The following example will also not work we can not pass const literals to functions which expect references:
int refPar(int& x)
{
}
intPar(123);
In this case we would get following compiler message: note: candidate function not viable: expects an lvalue for 1st argument
with an additional error message mentioning that we have no matching function call. As we can see it is not possible to pass a const literal to a function which expects a reference.
As I mentioned before, clang was telling us something about const. So how can we use that to make the example from above work? Is is allowed to bind const lvalue to rvalues. Fixed example from above:
int refPar(const int& x)
{
}
intPar(123);
Now the example works as we hoped. But how? The answer to that is that if we use the example without the const then we reference something that only exists for a short moment of time and it makes no sense to reference that. But if we use a const then we tell the compiler, that we will not modify that value. Based on that the compiler will create a hidden temporary variable for us:
To be able to see what happens under the hood we can try following example:
int main()
{
const int& ref = 420;
return 0;
}
This would give us following assembly output:
_main: ; @main
.cfi_startproc
; %bb.0:
sub sp, sp, #32
.cfi_def_cfa_offset 32
mov w0, #0
str wzr, [sp, #28]
add x8, sp, #12
mov w9, #420 <----- Loading the value 420 into register w9
str w9, [sp, #12] <----- Storing value of w9 on the stack, this is used later for the reference
str x8, [sp, #16]
ldr x8, [sp, #16]
ldr w8, [x8]
str w8, [sp, #8]
add sp, sp, #32
ret
.cfi_endproc
; -- End function
In other words the compiler would generate something like his:
int __internal_unique_name = 10;
const int& ref = __internal_unique_name;