Hello again!
In this video, we are going to look at lvalue and rvalue references. So we know about lvalues
and rvalues.
And now we are going to find out about lvalue references and rvalue references.
Lvalue references are actually not very scary!
They are just normal references. If you think about how these are implemented,
they are done through pointers.
If we have some reference to a variable, then that is actually implemented as a pointer to that variable.
And when we go through the reference, we actually dereference the pointer. And the compiler will put
in code to do all, this behind the scenes.
Obviously, this requires that x has a name, and we can take its address.
So that means that x must be an lvalue.
So that is why these are called "lvalue references".
As we saw in the last video, we cannot use an rvalue with these lvalue references, unless the reference
is const. And again, the compiler will adds some extra code to make this work.
In C++11, we now have the concept of a so-called "rvalue reference", which is not actually a reference
at all.
It is just a bit of syntax.
The actual syntax we use, it looks a bit peculiar.
We have these double ampersands. So this is not a reference to a reference.
It is actually some special instruction to the compiler.
So the compiler will check that
the argument is a moveable value.
For example, if we pass the value 2. Literals are rvalues, and built-in types are regarded as moveable,
even though they actually get copied!
So this will be accepted.
On the other hand, if we have a variable y, this has a name, and we can take its address.
So it is an lvalue, not an rvalue, and this will not compile.
Let's check that.
So here is a function which takes one of these "rvalue references".
We can call it with 2 as argument, and that should work.
Yep.
If I pass an lvalue as argument, then the compiler does not like that.
"You cannot bind an lvalue to an rvalue reference."
So function arguments can be an rvalue reference.
The object which is passed will be moved into the argument, if it is an rvalue and the type is moveable.
Otherwise, the call does not compile. And this means we can actually overload on whether the
argument is an rvalue reference or some type which takes an lvalue. So we can get to different behaviour
depending on whether we pass an lvalue or an rvalue.
And later on in this section, we will see that that can be useful sometimes.
What happens if you have a function that takes an rvalue reference, and you want to pass an lvalue to
it?
Well, this must mean that you do not actually want to keep the data in the lvalue.
If you remember the example we had, with the vector of 1 million strings, that vector was just created
so it could be passed to the function.
And after it was passed to the function, we we did not care.
We do not want to know any more.
So that would be a good candidate for moving from an lvalue into a function argument.
So how can we do that?
The C++ library provides a function for this, which is rather badly named.
It is called move().
This does not actually move anything.
All it does is cast its argument.
So if we go back to the function on the last slide, which takes an int as an rvalue reference.
If we pass "y", and wrap it in a call to move(), then the return value from this will be "y" cast to an
rvalue.
And then we have a moveable rvalue. So the code
would then compile.
This is going to move the data from the argument "y" for into the function "x".
So if we have our vector with 1 million strings, then doing something like this will move the strings
from the local variable, into the function argument.
So that means the original variable
no longer has all those strings in it.
Obviously, you should only do that if you do not care about that data.
Once you call the function, then the data may be empty, or the vehicle may be unusable.
If we want to use that variable again, it is not safe to do that, unless we reset it.
So we need to reassign some data to it.
Right.
Let's look at some code again.
Here is a function called test(), which takes a string by const reference to an lvalue. An lvalue reference
to const.
We have an overload, which takes one of these "rvalue references".
These two functions are going to print out, so we know which one was called.
And then we call them, with various arguments.
So, with a temporary object; with a local variable; a reference to a local variable; and the result
of calling move.
So, what
output do you expect to see?
So the temporary object is an rvalue.
So that calls the rvalue reference version.
It is a moveable rvalue.
The lvalue and the lvalue reference both call the lvalue reference version.
And if we have the return value from move(), that calls the rvalue reference version as well.
So that converts the lvalue into an rvalue.
For the purposes of this function call.
I am going to go on now, and declare this variable as an rvalue reference. And you are allowed to do that.
So let's see what difference that makes.
So we call the test function directly.
And let's try calling it with the return value from move().
So, any guesses what will happen here?
Well, how many of you said that this is going to kill the lvalue version,
I wonder.
If you look at this, it is a variable which has a name, and it has an address.
So we could do "&r", and that would be legal.
So this is actually an lvalue, even though it is declared as an rvalue reference.
So when we pass it to this function,
it calls the version which takes an lvalue.
If we want the rvalue version, then we have to cast it, by calling move().
Well, there is quite a lot to think about here, so let's try and collect our thoughts a bit.
If we have an argument type, which is just an lvalue.
So there is no ampersand anywhere. We can pass an lvalue to this argument, and then the function will
have a copy of the passed object's data.
So that is just pass by value.
If we pass a moveable rvalue, the object will be moved into the function, and the function now owns
the data that used to belong to the object that was passed to it.
If we have an an lvalue reference, we can pass an lvalue, but not an rvalue.
The function has a reference to the passed object's data and it can use the reference to modify it.
If we have a constable lvalue reference, we can pass either an lvalue or an rvalue.
The function will have a reference to the original object's data, but it cannot modify it.
And then finally, if the function argument is an rvalue reference, then we can pass a moveable
value, but not an lvalue.
The passed object will be moved into the function argument, and the function now owns the data that used
to belong to that object.
Okay, so that is it for this video.
I will see you next time.
Until then, keep coding!