Hello again! In this video, we are going to continue looking at lambda expressions and capture.
We saw in the last video that a lambda expression can capture a local variable by a value. That gives it
a copy of the local variable, which is const.
And we can use the mutable word if we want to modify
that. If we want to be able to modify the captured variable, then we need to capture it by reference.
And we can do that by putting an ampersand before the variable name inside the square brackets.
So this is going to capture the variable "n" by reference and then the "n" inside the lambda expression
body is going to be a reference to this local variable.
So if the lambda expression makes any changes to this variable, then these will also affect the variable
in the local scope.
So going back to the exercise in the last video, we were trying to find a word in this vector, which
has more than 5 characters, and we also wanted the index, But we had a problem, because we could
only modify a copy of the index variable.
So that gave the wrong answer. If we now capture it by reference, so we are going to capture this local
variable "idx" by reference.
This is going to be a reference in the lambda body.
So when we increment it here, this is going to also increment the local variable through the reference.
And then, when the find_if() call returns, this should have the correct value.
So let's see if it works.
And there it is. So we do get the correct value. A lambda which does capture it by reference, is going
to be implemented as a functor with state, similar to the capture by value.
So the compiler will generate the definition of a class with a function call operator, and a private
data member, to store the captured value.
This time, the private member is going to be a reference to the captured variable, and the constructor
will take
that argument by reference.
Then the function call operator can modify the captured variable through that reference. And that
is "capture by reference". The functor that the compiler would generate in the last example is going
to look something like this.
So we have two private members, one for the variable "n", which is captured by value, and one for the
"idx" variable, which is captured by reference. And then the [constructor]
will initialize these. And then we have the function call operator.
And then inside the algorithm call, the compiler will add code, which will create an object of this
class. And it will pass the local variables as arguments.
And that does give the same result.
It is also possible for a lambda expression to capture all the variables which are in scope. That is known
as an "implicit capture". And we can do that by value, or by reference.
If we put an equals sign inside the square brackets, that will capture all the variable by value. And an
ampersand will catch them all by reference, which is possibly not the best thing to do.
The reason is, if you capture all the local variables by reference, then the body of the lambda expression
is allowed to modify any variable which is in scope.
And if you have a program which gets modified a lot, that can be a bit of a maintenance headache.
So the best thing to do is to capture variables by reference, only if you actually need to alter them in
the lambda expression.
And you can do that.
For example, if you only want to modify x, then you can put equals, comma, ampersand x.
That will capture x by reference and all the other variables by value.
Alternatively, if you have some variables that you want to protect; you want to stop them from being
altered by the lambda expression.
You can capture those by value, and then implicitly capture everything else by reference.
It is possible to define a lambda expression inside a member function.
So this is different now from the algorithm case.
In the algorithm, the lambda was a callable object, which we were supplying, and the algorithm function
called that directly.
We can just write a lambda expression inside a member function, and that is some local code. And we can
execute that, or we can save it and use it later.
When we have a member function, the member function is always called on some object, and a pointer
to that object is passed as the first argument to the function.
So this is actually an extra argument that you do not see in the signature.
If we call a member function on an object, then the address of the object is passed as the first argument,
and you can access it inside the body of the member function, as a varaible called "this". And "this" will be
a pointer to a Test.
So this means that "this" is, in effect, a local variable, which is in scope in the member function,
and perhaps we can capture that in a lambda expression.
And yes, we can.
A lambda expression is allowed to capture "this", and that means that the expression can access these data
members of the class. All members, including the private ones. And it can also call other member
functions of the class.
The syntax for doing that is to put "this" inside square brackets.
If you do an implicit capture, by reference or by value, that will also capture "this". You are not allowed
to put equals "this" or ampersand "this".
There is a slight peculiarity, in that all these forms will capture the object by reference, even though
it looks as though they should, in some cases, capture it by value.
So let's have an example of this.
So here is our class.
We have a member function, which has a lambda expression defined in it. We will look at this in a
minute.
The class has a private data member.
We initialize this in-place, with the value 10 and then, in the member function, we define our lambda expression.
So this just defines the lambda.
So this will cause the compiler to generate the class definition.
And then when we put brackets after it, this will actually cause the code to be executed.
So this will make the compiler create an object of this class and call the function call operator.
So this will cause the compiler to create an object of the functor class. Not this class, the functor
class! And call the function call the operator in the functor class.
So we have actually got two classes in here: the Test class, which we defined, and the functor, which
the compiler will define.
And then, inside the lambda expression, we capture "this", so we can access the private data of the Test class.
So we can check the value of the time member. And because "this" is actually captured by reference,
even though it looks as though it has been captured by value, we can also modify this private data member.
So this is a bit of a silly example, really,
I could not think of anything more sensible. So we are going to look at this member. If it is greater than
0, we print out the value. If it is equal to 0,
we print "Liftoff!" and then we [decrement] it in either event.
If the time is less than 0, we do not do anything.
We just decrement it. In the main() function.
We create an object of this class, and then we call the countdown member function, 12 times.
So, let's see what happens.
So we get 10, 9, 8, blablabla, lift off and then nothing after that.
So the first time through, "time" is 10.
So it prints out the time and then decrements it. Then the next time,
"time" is 9 and so on. When "time" is 0, lift off, decrement. Then "time" is less than 0,
so just decrement.
You may think that capturing "this" by reference is a little bit dangerous, because the lambda expression
can modify any data member. And in C++ 17, we also have the opportunity to capture the object by value.
So if we put star this inside the square brackets, then that captures the object by value.
So that means the lambda expression only has a copy of the object.
And by default, "this" will be immutable.
So this means that the lambda expression cannot change the object.
So if we have the same code again and we capture "this" by value, then this should not compile.
Guess what?
This is not supported in visual C++. Or at least, not this version.
This is possibly a year old,
but it is not ancient.
So I am going to compile this in C++ 17 mode, which GCC does support. And we get a compiler error. So,
we are not allowed to modify "this".
So the object is read only.
So we have a const copy.
If I change this to mutable.
And then, let's try that again.
So it does compile now.
But if we run it, then we get 10.
And it is the same problem we had with the index in the last video.
So when we call this member function, the member function has a copy of the test object and
it modifies that copy, but not the actual object in main().
Okay, so that is it for this video.
I will see you next time, but until then, keep coding!