(gentle music)
(keyboard clacking)
Frank: In this video, we'll look at what it means
for a lambda expression to be stateless.
You might be surprised to hear that you're already familiar
with stateless lambda expressions.
In the last video,
we looked at the structure and syntax of a lambda
and said that a lambda's capture list allows it to capture
the context or closure in which it executes.
But what if the capture list is empty?
An empty capture list means that the expression captures
no information from its environment
and only knows about the information passed to it
via the function parameter list.
This is what's known as a stateless lambda expression.
Now, let's take a look at some examples.
In this example, we have two stateless lambda expressions.
We can tell that they're stateless
from their empty capture lists.
The first expression takes no parameters
and simply displays Hi when executed.
Everything it needs to execute is contained
within its function body.
The second expression
takes a single integer x as a parameter
and displays a value of x when it's called.
We can see that this lambda is instantiating a call
with the value of 100 as its x parameter.
But we can also see that another integer x
has been defined above.
Can you tell which x value lambda will display?
Remember this lambda expression is stateless,
and so it captures no information from its environment.
This means that the lambda has no knowledge
of the integer x defined above
and only knows about the value
that was assigned to its parameter x
when it was instantiated and called.
Since the parameter x was assigned a value of 100,
the lambda will display 100.
In this example, the lambda expression called sum
takes an integer array and its length as parameters
and returns the sum
of the integers contained within the array.
We can see that the lambda's parameter names are the same
as the integer array and length defined above.
But, again, we know from the empty capture list
that this lambda expression is stateless,
so it has no knowledge
of the array defined above or its length.
The only way for the lambda to compute the sum
of the integers contained within the array
is if the array and its length are passed
to the lambda as parameters.
We can see this being done when the lambda is called,
and so the lambda will compute and return the sum 60.
So far, we've only seen lambda expressions
that use value parameters,
but you're probably wondering,
"Could they also be reference parameters?"
Absolutely.
Remember that by passing a parameter
by reference to a function or lambda expression,
you're passing the location of the actual parameter.
So the form of parameter in the function body
is an alias to the actual parameter.
Unlike passing by value, no copy will be made.
When you change the form of parameter,
you're changing the actual parameter.
Let's take a look at an example.
In this example, we have the lambda expression called bonus
that takes as its parameters two test scores
and a number of points to increase them by.
You'll notice that both score1 and score2
are reference parameters,
so any changes made to them within the lambda body
will change the parameters they're referencing.
This means that there's no need
to return the increased scores
since the parameters they're referencing
will already have been updated.
We can see that bonus is called
and passed test_score1, test_score2, and 5
as the number of bonus points
to be awarded to each test score.
As the lambda body executes,
score1 and score2 are increased by five points,
but these are just aliases
for the parameters test_score1 and test_score2.
So it's actually test_score1 and test_score2
that are being increased by five points.
Once the lambda has finished executing,
test_score1 and test_score2
are displayed with their updated values.
You may remember that in C++, it's possible
to use pointer parameters to achieve pass by reference.
So naturally it follows
that lambda expressions can also use pointers as parameters.
To use pointer parameters properly,
we need to remember that the pointer parameter is pointing
to the address of the parameter that's being passed.
So when calling the lambda,
we need to use the referencing or address of operator
to pass the address of the parameter.
Additionally, because the lambda pointer parameter
is pointing to the address of the actual parameter,
if we want to access or modify
the parameter stored at that address,
we need to use the dereferencing operator.
All of this was covered in detail
in Section 12 of the course.
But I think it's important that you know
it can also be implemented with lambda expressions.
Now, let's quickly take a look at that example
with the bonus lambda expression
but this time using pointers.
Notice the differences from the last example.
The parameters score1 and score2 are now pointer parameters.
So when bonus is called,
the referencing or address of operator is used
to pass the addresses of test_score1 and test_score2.
We can also see that the dereferencing operator is used
to modify the values that score1 and score2 are pointing to
within the lambda body.
Now that we've seen how lambda expressions can use
values, references, and pointers
as parameters for different data types,
you may be asking yourself,
"Can the same be applied to data structures and objects?"
And the answer is of course.
Let's take a look at how we can use vectors
as reference parameters to pass multiple test scores
to the lambda expression bonus.
Here, we see that the lambda expression bonus takes
as its parameters a reference to an integer vector
containing the scores to be awarded bonus points
and an integer number of bonus points to be awarded.
Within the lambda body,
a range base for loop is used to iterate
over each individual score contained within the vector.
You'll notice that the referencing operator is used
in front of score in the range declaration of the for loop.
This is because we want to modify
the scores contained within the vector.
If we didn't use the referencing operator,
no changes made within the body of the lambda
would carry outside of it.
We can see that bonus is called
and passed the integer vector test_scores and 5
as the number of bonus points
to be awarded to each test score.
Once the lambda has finished executing,
the updated test scores are displayed.
Pretty simple, right?
But what if the vector test scores contain
floating-point values instead of integers?
Could we modify this lambda expression so that it could take
both integer and floating-point vectors
as its scores parameter?
Moreover, is there a way to write any lambda expression
to accept any data type for a single parameter?
Let's find out.
Here we have two variables, num1 and num2,
which are of type integer and float, respectively.
The lambda expression l takes a parameter x
and displays its value.
What type specifier would we need to use for parameter x
so that the lambda could display
both num1 and num2 when called?
Just like we use the keyword auto to tell the compiler
to deduce the type of the lambda expression,
we can also use it to tell the compiler
to deduce the type of the parameter.
This is a very powerful concept in C++,
and it's commonly used with lambda expressions
to increase robustness, performance, and usability.
Let's see how we can implement this concept
with our last example.
We can see now that by changing the type specifier
of the scores parameter to auto,
the lambda expression can be passed
both integer and floating-point vectors.
In fact, it can be passed
in any container that can be iterated over
so long as the logic of the lambda body can be applied
to the contents of the container.
You might also notice
that the type specifier for the for loop range declaration
has changed to auto as well.
This is necessary if we want the for loop to be able
to iterate over whatever data type is contained
within the scores container.
You can see from this example how useful the auto keyword is
when implementing lambda expressions.
So going forward, we'll be using them regularly
as lambda parameter type specifiers.
Just remember auto is not an actual type.
It's an instruction telling the compiler
to deduce the actual type.
One of the best things about lambda expressions
is their ability to be passed into functions as parameters.
In C++ 14, there are two ways in which we can do this.
The first is by passing a lambda expression
as a function object
using the standard library's functional header.
We can see this done in the first example
as the function foo takes as its parameter
the function object l.
The void type specifier represents
the return type of the function object,
and the integer type specifier represents
the function object's parameter type.
In this case,
the function l takes as its parameter an integer,
for example, 10, and returns nothing.
The second way of passing a lambda expression to a function
is as a function pointer.
In the second example, the function foo
takes as its parameter a pointer to the function l.
Again, the void type specifier represents
the return type of the function,
and the integer type specifier represents
the function's parameter type.
In C++ 20, we can do away with the restrictiveness
of having to declare the return and parameter types.
We can simply use the auto keyword to tell the compiler
to deduce both the return type and the parameter type
of the passed lambda expression.
In addition to using lambda expressions
as function parameters,
we can also have functions return lambda expressions.
Similar to how we can pass lambda expressions to functions,
we can return them
as either function objects, function pointers,
or by using the auto keyword
to instruct the compiler to deduce the return type.
You'll notice that the syntax of the second example
which returns a function pointer's a little strange.
This is old-style C syntax
that has survived through generations
for backward compatibility,
but you shouldn't give it too much attention.
In modern C++, if you want to return
a lambda expression from a function,
you'll most likely return them
as either a function object or by using the auto keyword.
So here you see three different ways
to achieve the same result.
What would it look like
to actually call and use these functions?
It's simple. All three versions use the exact same way.
First, we call foo
and assign its return value to a variable.
Then we use the variable to execute the function
and pass any parameters into it that it needs.
In this case, we're passing a 10,
and the 10 is displayed as a result.
Examples of why you might want
to return a lambda from a function
are best explained using stateful lambda expressions,
so we'll wait and cover that.
In the meantime, let's go back
to looking at lambda expressions as function parameters
and see how we can call a function with a lambda expression
as the pass parameter.
In the first example, we can see that the lambda expression
is defined within the function call to foo.
This is a common way
of passing lambda expressions to functions
since in most cases they're only ever passed once.
If a lambda will be used more than once,
it may be beneficial to assign it to a variable
so that it can be passed to multiple functions
and called independently
without having to define the lambda each time.
In this case, the lambda can be passed to a function
by passing the variable it was assigned to,
as seen in the second example.
Now, let's take a look at why we might want
to pass a lambda expression to a function
with a simple but powerful example using a predicate lambda.
Before we get started,
let me explain what a predicate lambda is.
A predicate is a C++ function
that takes any number of arguments
and returns a boolean value.
So naturally it follows that a predicate lambda is a lambda
that implements this functionality.
This may seem like a simple concept, and it is,
but this is where the true power of lambdas lies.
In this example, we have a function called print_if
that takes as its parameters an integer vector
and a predicate lambda that's passed as a function pointer.
In this case, the predicate lambda is used to determine
which elements of the integer vector to display.
Within the main function,
print_if is called with the integer vector nums
and two different predicate lambdas.
The first predicate lambda
returns true if the parameter x is even and false otherwise.
So when it's passed to the print_if,
the even value of nums will be displayed.
The second predicate lambda
returns true if the parameter x is odd and false otherwise.
So when it's passed into the print_if,
the odd values of nums will be displayed.
You can see that the same print_if function can be used
to display different elements of the vector
depending on the predicate lambda that's passed into it.
This exemplifies the predominant use of lambda expressions
and why they're so powerful.
As we'll see later,
predicate lambdas are extremely important
when using the standard template library functions
and algorithms such as sort or for each
which require a predicate parameter.
In the next video, we'll go over to the IDE
and we'll run some of these examples and live code.
I'll mark them up so you can see exactly what's happening.
I'll see you there.