(upbeat music)
Frank: In the last video,
we learned about stateless Lambda expressions.
That is, Lambda expressions that capture no information
from their environment.
In this video, we'll learn about their close relative,
the stateful Lambda expression.
That is, a Lambda expression that does capture information
from its environment.
It's important to note that stateful Lambda expressions
are just like stateless Lambda expressions
except they have non-empty capture lists.
So everything we do with a stateless Lambda,
we can do with a stateful Lambda and more.
So what exactly do we mean when we refer to a Lambda
capturing information from its environment
and how is it able to do this?
Well, what we're referring to
is the Lambda's ability to use variables
declared within its reaching scope
without having to pass them into the Lambda as parameters.
This is accomplished by simply listing the variables
we would like the Lambda to use within its capture list.
Pretty cool, right?
But how does it work?
Well, to fully understand this,
we first need to take a look under the hood
to see what happens when a Lambda expression is compiled.
Here, we can see a simple
stateless Lambda expression defined.
To represent the Lambda,
the compiler generates what's known as a closure type class
with a default constructor and an operator function
implementing the logic of the Lambda.
The class isn't generated until compile time
so it might now be clear why the auto keyword must be used
as a specifier for the type in Lambdas.
It's because when we define Lambda expressions,
the type hasn't been generated yet.
The type is only generated when the lambda is compiled
and so of course can only be assigned by the compiler
at compile time.
With this in mind, can you see what's happening
when we assign a Lambda expression to a variable?
what we're actually doing is instantiating an object
of the compiler generated closure
by making a call to its constructor.
Hopefully now you'll have a better understanding
of how Lambdas operate behind the scenes,
but that still leaves the question
of how they can capture information from their environment
using the capture list.
As the challenge, pause the video now
and see if you can figure it out
with your new knowledge of how Lambdas work.
When you're done, unpause the video
and we'll go through it in the next slide.
If you thought of storing the captured variables
as member variables of the compiler generated closure,
then you are absolutely right.
When defining a stateful Lambda's capture list,
we're defining the parametrized constructor
of the Lambda's compiler generated closure.
When the stateful Lambda expression is instantiated,
a unique function object is created
using the parameterized constructor
which stores the captured variables as member variables.
We can see this being done in the example.
A simple stateful Lambda expression is defined
which captures the local variable y
and takes a single parameter x.
The compiler generates a closure for the Lambda
with a parameterized constructor
that takes as its parameter the variable y
and assigns it to the closure's member variable y.
You'll notice that the generated closure
has an operator function
implementing the logic of the Lambda as we would expect.
However, this time, it's a constant member function.
This means that no member variable
of the instantiated object can be modified by this function
and so the member variable y cannot be modified.
The reason for this is because by default,
all variables captured by value are captured by const value.
As you can imagine, this can be quite restricting
when we're trying to implement certain functionalities.
So C++ provides us with a multitude of ways
in which we could capture variables.
Let's take a look at them.
As seen in the last example,
the first way in which a Lambda expression
can capture a variable is by value.
This is the default capturing mode,
and so it can be accomplished
by simply listing the variable's name
in the Lambda's capture list.
It's important to remember
that a variable being captured by value
is actually being captured by const value
so it won't be modifiable within the Lambda.
But what if we want to capture a variable by value
and modify it within the Lambda?
In that case, we can use the keyword mutable
to tell the compiler to generate
the Lambda's operator function
as a non-const member function.
By doing so, we can capture variables by value
and modify them within the Lambda.
As you might have guessed,
we can also capture variables by reference.
In this case, any changes made to a variable
captured by reference within the Lambda
will be visible outside the Lambda.
Using both capturing modes,
we can define a Lambda's capture list
any number of ways for any number of variables.
But what if we want to capture a large number of variables,
but we don't want to have to list each variable?
How do we do this?
This is where default captures come in.
A default capture allows a Lambda
to capture all variables referenced within its body
according to the defined capture mode.
An equal sign positioned first in the Lambda's capture list
denotes that all referenced variables
should be captured by value.
An ampersand denotes that they should be captured
by reference.
And if we use the this keyword,
this denotes that the referenced member variables
of the current object Lambda as defined within
should be captured by reference.
When implementing default captures,
we can also explicitly capture variables
according to different capture modes.
It's important to note that when we do so,
we must always place the default capture
first in the capture list.
Also, the explicit capture
cannot be the same as the default.
Otherwise, the Lambda won't compile.
With so many possible combinations of default
and explicit parameters, it's hard to cover them all,
but what we've covered should prepare you
for most situations
in which you'll need to use stateful Lambdas.
Now, let's head over to the IDE and implement some.