Hello again! In this video, we are going to look at lambda expressions and capture. We can pass
data to a lambda expression through the arguments.
If we have an algorithm call which uses a lambda expression, then we can only pass elements of the container
that the algorithm is processing.
We cannot add more arguments because the signature of the lambda expression has to match the expectations
of the algorithm.
So what can we do?
A lambda expression has access to global or non-local variables, just like a regular function.
It can also access static variables in the same scope.
There is some very limited access to local variables.
So this is in the scope where the algorithm is being called.
If we have a local variable, which is a reference and was initialized with a const expression, then
the lambda expression can read and write those variables.
If we have a local variable, which is an integer or enumerated type, and was initialized with a constant
expression, then the lambda expression can read those, but not modify them.
So that is pretty limited and also not very well supported.
So we have a lambda expression, which is just going to print out some variables in its body.
We have a global variable (Move myself out of the way. Going down.)
So we have a global variable, which it can access.
We have a static variable in the same scope, which it can access.
We have a constant integer which it should be able to access, but this does not compile with Visual
C++.
"Cannot be implicitly captured"
And the other possibility is a reference initialized with a constant.
But this is not supported by any compiler that I have.
Same error, again.
So what can we do?
Clearly, we need to do something special.
And fortunately, the language does provide something special.
We can "capture" the local variables. And, to do that, we just put the name of the local variable inside the
square brackets.
So when we define a lambda function, we are now defining a lambda function, which will capture this
local variable, "n".
And then the variable is available inside the function body, so we can just use it for whatever purposes
we want.
If we have more than one, we can just capture both, and put commas between them.
As an example of this, the find_if() algorithm we have seen before. We pass the iterator range and a lambda
expression.
And this one will find the first element which has more than 5 characters in the string.
So here is that code. We have a vector of strings, and we are going to find the first one which has more
than 5 characters.
And the first one is, "collection".
But what happens if we want some different value from 5, or we want to be able to change this?
We can use a lambda with capture. So we can have a local variable, "n", which has the number of elements we
want to look for.
And then we can capture this local variable "n" and use that in the lambda expression body.
So this is going to find the first element in the vector which has more than "n" characters.
That is, "collection".
So if we change "n" to... actually we need to move - again, that is not a very good choice of data.
So if we make - so let's try that again.
So it is still collection.
If we make this, 3.
Then we get "words".
So how is this implemented?
(Just get myself back in the picture!)
So how is this implemented?
A lambda, which has capture, is implemented as a functor with state.
So this means the compiler will generate a class which has a private data member, and that data member
will store the captured variable.
This member will be initialized in the constructor of the class. And then that private data member is
available in the body of the member function.
The function call operator.
By default, this is all done by value.
So the captured variable is passed to the function's constructor by value, and the data member is going
to be a value type.
So this means that the functor object will have its own copy of the captured variable. And that is known
as capture by value.
By default, this statement member will also be const.
So this means that the object has a const copy of the variable, and the function call operator cannot modify
it.
So this is what the equivalent code with a functor would look like.
So this is the code that the compiler will generate for the previous example.
So we have a class which has a private member with the captured variable. A const copy of the variable.
This is initialized in the constructor. And then, in the function call operator,
we have the code from the lambda expression body.
And then when we call the algorithm, the compiler will put in some code, which creates an object of
this class, and passes the variable "n" to the constructor.
And there we are.
And you can experiment with changing the words around if you like.
Let's say now that we want to be able to find the index of this variable that matches. So we have another
local variable.
And then every time the algorithm processes an element, we increment this variable.
So if we set this to minus one, that will be equal to the number of processed elements minus one,
which will give us the index.
OK, so what do you think will happen?
Well, we get a compiler error, and the reason for that is that, this will generate a functor which
has a const member to represent the index.
So in the member function, this will increment a const private data member and that is not allowed.
What we can do is add the "mutable" the keyword after the lambda expression signature.
So that means that the data member is not going to be const. It is going to be a modifiable variable.
And then this will compile and then we get the value minus one.
And the reason for that is that the functor has a copy of the captured variable.
So when this was being modified in the functor's member function, that was only modifying the copy.
It was not doing anything to change this local variable.
And then when we print this out, this is going to be the local variable and not the copy which the
functor made, which disappears when this call returns.
OK, so we need to find out now how to be able to modify captured variables, and we will do that in the
next video. But until then, keep coding!