Hello again!
In this video, we are going to look at perfect forwarding.
forwarding means that a function passes one or more of its arguments to another function.
Here we have a function which takes an argument x, and it calls some function g, and it passes x to that
function.
So we say that f is forwarding the argument x to the function g.
We can have so-called "perfect" forwarding, in which x has the same properties in g that it has in f.
If x is modifiable in the body of f, then it can be modified by g.
If x cannot be modified by f, then it cannot be modified by g.
And if we moved an object into f's argument, then the same object gets moved into g's argument.
Perfect forwarding is useful for writing functions which call constructors.
An example of this is make_pair() from the standard library.
A pair object has two members and the make_pair function will initialize those by perfectly forwarding
the arguments
that make_pair was given.
If we pass an rvalue to make_pair, then the corresponding member will be initialized using the
move constructor, and that is the most efficient way to do it.
Another use is in templates with a variable number of arguments.
We will look at those when we do compile-time programming, but usually you dispatch the arguments off to
some other function. And, again, this will allow move operations, where they are available.
So let's say we wants to implement this function f and function g.
We have three functions g, which take the three possible alternatives: modifiable argument, unmodifiable
argument and an argument which is moved into. And then we need some function f which can call these.
The obvious way to do this is to write three overloads of f, with the same arguments and then call g.
And obviously for the move version, we need to cast x to an rvalue.
So we have three almost identical functions, so we could think about templates. And also, we know about
forwarding references, so perhaps we can do something clever with those.
So here is some code.
We have a class, Test, and it does not really matter what it does.
We have three overloaded functions g, which will take an object of this class by lvalue reference, const lvalue reference
and rvalue reference, and it will tell us which version was called.
Then we have our function f, which is a template function with a forwarding reference. And the compiler
is going to instantiate different overloads of f(), depending on how we call it.
If we pass an lvalue object, then T will be deduced as an lvalue reference to test.
Then we have reference collapsing, and this will instantiate a function which takes lvalue reference
to Test.
So when we pass x to g(), that is going to call the version which takes lvalue reference to Test.
If we pass a const lvalue to this function, T will be deduced as const lvalue reference.
Then we have reference collapsing again, and x will be of type const lvalue reference.
So when we call g, it calls the version which takes const lvalue reference.
And if we pass an rvalue, then the compiler will instantiate this with an rvalue reference argument.
So we should get the move version of g.
So let's see what happens.
So the lvalue argument gives us the modifiable version of g.
The const lvalue argument gives us the immutable version of g, and the rvalue argument - ah, that calls
the modifiable version of g.
So that has not worked.
So what has gone wrong?
And if you have already worked it out, then give yourself a bonus point!
When you are looking at template code and trying to make sense of it, it is often useful to try and
imagine what the instantiated code looks like.
So for the rvalue case, we would have something like this.
The function will be instantiated with an rvalue reference.
And then we call it by passing this argument.
If we look at this argument x, it has a name. x, of course!
And we could take its address if we wanted to.
So this is actually an lvalue.
So when we call g here, we are passing an lvalue.
And that is why the compiler will put in a call to the lvalue version.
So we really want to call the rvalue version of g, and we know how to do that.
We can just cast the argument to an rvalue by calling move().
So let's try that.
So let's see if that works.
And yes, the rvalue argument calls the move version of g.
But if we look up here, we see that the lvalue argument calls the move version of g as well.
And that is not good.
When we call move here, we are saying, in effect, that we do not care what happens to x after
this function returns.
On the other hand, when we just do a normal function call, then we do care.
Or we may care.
We can reasonably assume the techs will still be in a usable state, when this function call returns.
And if, in fact, the function call causes this variable thatto be moved into something else.
So we no longer have access to the data, then that is rather undesirable.
So this is what the code would look like for the lvalue version.
We have an instantiation that takes the argument by lvalue reference and then, when we call g, we put
the argument inside a call to move.
So that is always going to call the rvalue version.
We have got a bit of a problem here.
We need to have something that will cast to an rvalue, but not if the argument is an lvalue reference.
And in fact, the library provides something which does this.
This is a function called forward().
All it does is cast its argument to an an rvalue reference.
And this all works because of reference collapsing.
If the arguments is an lvalue reference, we have rvalue reference to lvalue reference, which collapses
down to an lvalue reference.
So that is unchanged.
If we have an lvalue, then casting to rvalue reference gives rvalue reference.
If we have an rvalue reference, then casting that gives rvalue reference to rvalue reference,
which collapses down to an rvalue reference to the argument.
So we always gets an rvalue reference, unless the argument is an lvalue reference.
So forward does not cast it argument
if the argument is an lvalue reference. And, by the way, the forward requires a template parameter.
So we need to give the template type, which is not the case for move.
So let's see if this helps.
And I will try it without the template parameter.
Okay.
So that does not work.
Now, what have we got? Lvalue argument calls modifiable version, const lvalue argument calls
immutable version, and rvalue argument calls the move version. (Ah, at last!)
Okay, so this may all seem a bit elaborate. And if you only right applications, then you may never
actually need to use this.
But it is a useful technique if you are writing library code, because it is a template.
So you do not need to actually write very much code, and it will automatically perform move operations
if they are available.
So that is always the most efficient form.
So it is efficient, both in development time and run time.
Okay, so that is it for this video.
I will see you next time.
But until then, keep coding!