Hello again!
In this video, we are going to look at lvalues and rvalues. In C++,
every object is either an lvalue or an rvalue.
This is something that actually goes back to the ancestors of C.
And, originally, it was very simple.
If you have something which can appear on the left of an equal sign, that is an lvalue. "L" for "left".
If you have something which can only appear on the right of an equals sign, then that is an rvalue.
"R" for "right".
So, for example, we can say "x equals 2".
So x is an lvalue. But we cannot say "2 equals something".
So 2 is an rvalue.
And also, with the return value from a function call. We can say that "x equals the return value", but
we cannot say
"the return value is equal to something."
So the function return value is an rvalue.
Unfortunately, this rule is not sufficient for C++.
In C++ we have overloaded operators, and some operators can return things which can be assigned to.
So we need a different rule. In C++, an lvalue represents a named memory location.
So the variable must have a name, and we must be able to take its address.
So to be an lvalue, it must have a name, and we must be able to get the address of this, by using
the address-of operator.
So for example, with x, that has a name, x. And we can take the address of it using the address-of operator.
So X is an lvalue.
Everything else is an rvalue.
So for example, 2 does not have a name, and we cannot take its address the person value.
And the return value from function does not have a name, and we cannot take its address.
So those are both rvalues.
So we have a more complex rule, but it does give the same answers.
The reason why all this is important is that lvalues and rvalues can behave differently when we
pass them as function arguments.
With pass by value,
it does not actually make any difference.
If we have some function which takes an int by value, we can pass an lvalue, a variable x or we can pass
an rvalue: an int literal 2.
However, if we are passing by address, then it does make a difference.
We can pass an lvalue, because we can take the address of an lvalue, but we cannot pass an rvalue,
because we cannot take the address of an rvalue.
And passing by reference.
This is really another way of taking the address.
So again, we have the same rule.
We can pass lvalues by reference.
That is a non-const reference by the way. But we cannot pass rvalues by non-const reference.
So there is some logic to this.
If we are passing by address or by non-const reference, that usually means we want to modify the
argument and modifying an rvalue does not really make much sense.
So that is why it is not allowed.
If we have passing by reference to const, then that is sort-of like passing by value.
So we have the same rules as for pass by value.
So let's actually look at the example in the compiler.
Here, we have a function which returns an int.
So if we call this function, the object we get will be an rvalue.
The return value has no name, and we cannot take its address.
We have another function which takes its argument by reference to const int.
So if we call this function, and we pass the rvalue that we get by calling this function, what will happen?
Is this legal?
And the answer is yes, it is legal. It compiles and runs.
So you can actually pass an rvalue by reference to const, as well as by value.
In C++, we
so far know about pass by value, pass by reference - const and non-const - and pass by address. In
C++11,
we also have pass by move. And this actually uses the same syntax as passing by value.
If you write something like this in older versions of C++, this is always passed by value.
The argument object is copied into the function.
So the function has a local variable, which is a copy of the argument that was passed to it.
If you use the same syntax in C++11, the object may be moved into the function.
So the variable in the function actually has the data that used to be in the object which was passed
to the function.
For this to happen, the object that is passed must be an rvalue and it must have a type which can
be moved.
And I will tell you the rules for that later.
But for now, you can do this for every library type in the C++11 class library. With a few exceptions,
of course, there is always exceptions! And most exceptions are to do with thread synchronization, which we
are not covering in this course.
On the other hand, if the object which is passed is an lvalue, or we pass something which is not movable,
and that includes built in types, then those will be copied instead.
So we get exactly the same behaviour as we did before C++11.
So if you have some classes and they were not written for move semantics and you pass them by value,
then do not worry, that will continue to happen.
So this is all completely compatible with old code.
Okay.
So that is it for this video.
I will see you next time.
But until then, keep coding!