In this video, we'll look at what it means for 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 capture list
allows it to capture the context or closure in which it executes.
But what if the captured list is empty?
An empty capsule 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 high one 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 the value of X when it's
called.
We can see that this lambda is instantiated and called with a 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 the 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 parameter names are the same as the integer array and length defined above.
But again, we know from the empty capsule 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 formal parameter and the function body is an alias to the actual parameter.
Unlike passing by value, no copy will be made.
When you change the formal 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 score one and score two 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 score one, test score two and five as the number of bonus points to be awarded to each test score.
As the Lambda Body executes, score one and score two are increased by five points.
But these are just aliases for the parameters test score one and test score two.
So it's actually test score one and test score two that are being increased by five points.
Once the Lambda has finished executing test score one and test score two are displayed with their updated
values.
You may remember that in C++ it's possible to use pointer parameters to achieve past 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 DX referencing
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 score one and score two are now pointer parameters.
So when bonus is called, the referencing or address of operator is used to pass the addresses of test
score one and test score two.
We can also see that the DX referencing operator is used to modify the values that score one and score
to a point 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 based 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 change is made within the body of the lambda would carry
outside of it.
We can see that bonus is called and pass the integer vector test scores and five is 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, number one and number two, which are of type integer and float respectively.
The lambda expression L takes a parameter X and displays its value.
What type specifiers would we need to use for parameter X so that the lambda could display both number
one and number two when called?
Just like we use the key word 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 specified 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 specified 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 will be using the 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.
Is 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 libraries functional
header.
We could see this done in the first example as the function foo takes as its parameter the function
object l the void type specify a represents the return type of the function object and the integer type.
Specify R represents the function object's parameter type.
In this case, the function L takes as its parameter an integer, for example ten 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 specified represents the return type of the function and the integer type specify
a represents the functions parameter type.
In C++ 20, we could do away with the restrictiveness of having to declare the return and parameter
types, and we can simply use the auto keyword to tell the compiler to deduce both the return type and
the parameter type of the pass 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 is a little strange.
This is old style.
See 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 it 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 ten and the ten is displayed as a result.
Examples of why you might want to return a lambda from a function or best explained using stateful lambda
expressions.
So we'll wait until 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 the 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 the 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 call it independently without having to define the lambda.
Each time, in this case, the lander that 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 Valley.
So naturally it follows that a predicate lambda is the lambda that implements this functionality.
This may seem like a simple concept, and it is, but this is where the true power of Lambda 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 ID and we'll run some of these examples in live code.
I'll mark them up so you can see exactly what's happening.
I'll see you there.