Search icon CANCEL
Arrow left icon
Explore Products
Best Sellers
New Releases
Books
Videos
Audiobooks
Learning Hub
Conferences
Free Learning
Arrow right icon
Arrow up icon
GO TO TOP
Learning C++ Functional Programming

You're reading from   Learning C++ Functional Programming Explore functional C++ with concepts like currying, metaprogramming and more

Arrow left icon
Product type Paperback
Published in Aug 2017
Publisher
ISBN-13 9781787281974
Length 304 pages
Edition 1st Edition
Languages
Arrow right icon
Author (1):
Arrow left icon
Wisnu Anggoro Wisnu Anggoro
Author Profile Icon Wisnu Anggoro
Wisnu Anggoro
Arrow right icon
View More author details
Toc

Table of Contents (9) Chapters Close

Preface 1. Diving into Modern C++ 2. Manipulating Functions in Functional Programming FREE CHAPTER 3. Applying Immutable State to the Function 4. Repeating Method Invocation Using Recursive Algorithm 5. Procrastinating the Execution Process Using Lazy Evaluation 6. Optimizing Code with Metaprogramming 7. Running Parallel Execution Using Concurrency 8. Creating and Debugging Application in Functional Approach

Simplifying the function notation using a Lambda expression

The Lambda expression is an anonymous notation that represents something that performs an operation or calculation. In functional programming, the Lambda expression is useful to produce the first class and pure function, which we will discuss in separate chapters in this book. For now, let's familiarize ourselves with this new feature introduced in C++11 by investigating three basic parts of the Lambda expression:

  • capturing list: []
  • parameter list: ()
  • body: {}

The order of these three basic parts is as follows:

    [](){} 

The capturing list part is also used as a mark to identify the Lambda expression. It is a placeholder to value to be involved in the expression. The only capture defaults are the ampersand symbol (&), which will implicitly capture the automatic variables by reference, and the equal sign (=), which will implicitly capture the automatic variables by copy (we will discuss it further in the upcoming section). The parameter list is similar to the capturing list in every function where we can pass the value to it. The body is the implementation of the function itself.

Using the Lambda expression for a tiny function

Imagine we have a tiny one-line function that we invoke only once. It's better if we write the operation of that function directly when we need it. We actually had this function in our previous example when discussing the C++ Standard Library. Just go back to the for_each.cpp file and we will find the PrintOut() function that is only invoked once by for_each(). We can make this for_each loop more readable if we use Lambda. Let's take a look at the following code snippet to examine how we refactor the for_each.cpp file:

    /* lambda_tiny_func.cpp */
#include <vector>
#include <algorithm>
#include <iostream>
#include "../vehicle/vehicle.h"

using namespace std;

auto main() -> int
{
cout << "[lambda_tiny_func.cpp]" << endl;

// Initializing several Vehicle instances
Vehicle car("car", 4);
Vehicle motorcycle("motorcycle", 2);
Vehicle bicycle("bicycle", 2);
Vehicle bus("bus", 6);

// Assigning the preceding Vehicle instances to a vector
vector<Vehicle> vehicles = { car, motorcycle, bicycle, bus };

// Displaying the elements of the vector
// using Lambda expression
cout << "All vehicles:" << endl;
for_each(
begin(vehicles),
end(vehicles),
[](const Vehicle &vehicle){
cout << vehicle.GetType() << endl;
});

return 0;
}

As we can see, we have transformed the PrintOut() function that we used in the for_each.cpp file into a Lambda expression and passed it to the for_each loop. It will indeed give the same output as the for_each.cpp file does. However, now our code becomes more concise and readable.

Using the Lambda expression for multiline functions

The Lambda expression can also be used for multiline functions, so we can put the body of the function on it. This will make our code more readable as well. Let's make a new code. In that code, we will have an integer collection and an intent to inspect whether the selected element is the prime number or not. We can make a separate function, for instance, PrintPrime(), then invoke it. However, since the prime number checking operation is called only once, it's more readable if we transform it into a Lambda expression. The code should look like this:

    /* lambda_multiline_func.cpp */
#include <vector>
#include <algorithm>
#include <iostream>

using namespace std;

auto main() -> int
{
cout << "[lambda_multiline_func.cpp]" << endl;

// Initializing a vector containing integer element
vector<int> vect;
for (int i = 0; i < 10; ++i)
vect.push_back(i);

// Displaying whether or not the element is prime number
for_each(
begin(vect),
end(vect),
[](int n) {
cout << n << " is";
if(n < 2)
{
if(n == 0)
cout << " not";
}
else
{
for (int j = 2; j < n; ++j)
{
if (n % j == 0)
{
cout << " not";
break;
}
}
}

cout << " prime number" << endl;
});

return 0;
}

The output we should see on the screen is as follows:

As we can see in the preceding screenshot, we have successfully identified the prime number by using the Lambda expression.

Returning a value from the Lambda expression

Our two preceding samples of the Lambda expression are just for the purpose to print on console. It means the function does not need to return any value. However, we can ask the Lambda expression to return a value for an instance if we do the calculation inside the function and return the calculation result. Let's take a look at the following code to examine the use of this Lambda:

    /* lambda_returning_value.cpp */
#include <vector>
#include <algorithm>
#include <iostream>

using namespace std;

auto main() -> int
{
cout << "[lambda_returning_value.cpp]" << endl;

// Initializing a vector containing integer element
vector<int> vect;
for (int i = 0; i < 10; ++i)
vect.push_back(i);

// Displaying the elements of vect
cout << "Original Data:" << endl;
for_each(
begin(vect),
end(vect),
[](int n){
cout << n << " ";
});
cout << endl;

// Creating another vect2 vector
vector<int> vect2;
// Resize the size of vect2 exactly same with vect
vect2.resize(vect.size());
// Doubling the elements of vect and store to vect2
transform(
begin(vect),
end(vect),
begin(vect2),
[](int n) {
return n * n;
});

// Displaying the elements of vect2
cout << "Squared Data:" << endl;
for_each(
begin(vect2),
end(vect2),
[](int n) {
cout << n << " ";
});
cout << endl;

// Creating another vect3 vector
vector<double> vect3;
// Resize the size of vect3 exactly same with vect
vect3.resize(vect.size());
// Finding the average of the elements of vect
// and store to vect2
transform(
begin(vect2),
end(vect2),
begin(vect3),
[](int n) -> double {
return n / 2.0;
});

// Displaying the elements of vect3
cout << "Average Data:" << endl;
for_each(
begin(vect3),
end(vect3),
[](double d) {
cout << d << " ";
});
cout << endl;

return 0;
}

When we use the transform() method in the preceding code, we have a Lambda expression that returns a value from the calculation of n * n. However, there's no return type stated in the expression. This is because we can omit the statement of the return type since the compiler has understood that the expression will return an integer value. So, after we have another vector, vect2, which has the same size as vect, we can invoke the transform() method along with the Lambda expression, and the value of vect will be doubled and stored in vect2.

We can, if we want to, specify the return type to the Lambda expression. As we can see in the preceding code, we transformed the vect3 vector based on all values of the vect vector, but now we specify the return type to double using the arrow symbol (->). The result of the preceding code should be like the following screenshot:

As we can see from the preceding screenshot, we have successfully found the doubled and average result using the Lambda expression.

Capturing a value to the Lambda expression

In our previous Lambda expression examples, we keep the capturing part and the square bracket ([]) empty since the Lambda doesn't capture anything and doesn't have any extra member variable in the anonymous object generated by the compiler. We can also specify the object we want to capture in the Lambda expression by specifying it in this square bracket. Let's take a look at the following piece of code to go through the discussion:

    /* lambda_capturing_by_value.cpp */
#include <vector>
#include <algorithm>
#include <iostream>

using namespace std;

auto main() -> int
{
cout << "[lambda_capturing_by_value.cpp]" << endl;

// Initializing a vector containing integer element
vector<int> vect;
for (int i = 0; i < 10; ++i)
vect.push_back(i);

// Displaying the elements of vect
cout << "Original Data:" << endl;
for_each(
begin(vect),
end(vect),
[](int n){
cout << n << " ";
});
cout << endl;

// Initializing two variables
int a = 2;
int b = 8;

// Capturing value explicitly from the two variables
cout << "Printing elements between " << a;
cout << " and " << b << " explicitly [a,b]:" << endl;
for_each(
begin(vect),
end(vect),
[a,b](int n){
if (n >= a && n <= b)
cout << n << " ";
});
cout << endl;

// Modifying variable a and b
a = 3;
b = 7;

// Capturing value implicitly from the two variables
cout << "printing elements between " << a;
cout << " and " << b << " implicitly[=]:" << endl;
for_each(
begin(vect),
end(vect),
[=](int n){
if (n >= a && n <= b)
cout << n << " ";
});
cout << endl;

return 0;
}

In the preceding code, we will try to capture the value in the Lambda expression, explicitly and implicitly. Let's suppose we have two variables, a and b, and we want to explicitly capture the values, we can specify them in the Lambda expression using the [a,b] statement, and then using the values inside the function body. Moreover, if we wish to capture the value implicitly, just use [=] for the capturing part and then the expression will know which variable we intend to use when we specify them in the function body. If we run the preceding code, we will get the following output on the screen:

We can also mutate the state of the values we capture without modifying the value outside the Lambda expression function body. For this purpose, we can use the same techniques as used previously, and add the mutable keyword as shown in the following block of code:

    /* lambda_capturing_by_value_mutable.cpp */
#include <vector>
#include <algorithm>
#include <iostream>

using namespace std;

auto main() -> int
{
cout << "[lambda_capturing_by_value_mutable.cpp]" << endl;

// Initializing a vector containing integer element
vector<int> vect;
for (int i = 0; i < 10; ++i)
vect.push_back(i);

// Displaying the elements of vect
cout << "Original Data:" << endl;
for_each(
begin(vect),
end(vect),
[](int n){
cout << n << " ";
});
cout << endl;

// Initializing two variables
int a = 1;
int b = 1;

// Capturing value from the two variables
// without mutate them
for_each(
begin(vect),
end(vect),
[=](int& x) mutable {
const int old = x;
x *= 2;
a = b;
b = old;
});

// Displaying the elements of vect
cout << "Squared Data:" << endl;
for_each(
begin(vect),
end(vect),
[](int n) {
cout << n << " ";
});
cout << endl << endl;

// Displaying value of variable a and b
cout << "a = " << a << endl;
cout << "b = " << b << endl;

return 0;
}

The preceding code will double the element of the vect vector. It uses capturing by value in the Lambda expression and also the mutable keyword. As we can see, we passed the vector element by reference (int& x) and multiplied it by two, then changed the value of a and b. However, since we use the mutable keyword, the final result of a and b will remain the same, although, we have passed the vector by reference. The output on the console looks like the following screenshot:

If we want to change the value of the a and b variables, we have to use the Lambda expression to capture by reference. We can do this by passing the reference to the angle bracket in the Lambda expression, for instance, [&a, &b]. For more detail, let's take a look at the following piece of code:

    /* lambda_capturing_by_reference.cpp */
#include <vector>
#include <algorithm>
#include <iostream>

using namespace std;

auto main() -> int
{
cout << "[lambda_capturing_by_reference.cpp]" << endl;

// Initializing a vector containing integer element
vector<int> vect;
for (int i = 0; i < 10; ++i)
vect.push_back(i);

// Displaying the elements of vect
cout << "Original Data:" << endl;
for_each(
begin(vect),
end(vect),
[](int n){
cout << n << " ";
});
cout << endl;

// Initializing two variables
int a = 1;
int b = 1;

// Capturing value from the two variables
// and mutate them
for_each(
begin(vect),
end(vect),
[&a, &b](int& x){
const int old = x;
x *= 2;
a = b;
b = old;
});

// Displaying the elements of vect
cout << "Squared Data:" << endl;
for_each(
begin(vect),
end(vect),
[](int n) {
cout << n << " ";
});
cout << endl << endl;

// Displaying value of variable a and b
cout << "a = " << a << endl;
cout << "b = " << b << endl;

return 0;
}

The preceding code has the same behavior with the lambda_capturing_by_value_mutable.cpp file that will double the element of the vect vector. However, by capturing by reference, it now also modifies the value of a and b when they are processed in the for_each loop. The a and b values will be changed at the end of the code, as we can see in the following screenshot:

Preparing the value using initialization captures

Another great feature of the Lambda expression coming up in C++14 is its initialization captures. The expression can capture a value of the variable and assign it to the expression's variable. Let's take a look at the following piece of code implementing the initialization captures:

    /* lambda_initialization_captures.cpp */
#include <iostream>

using namespace std;

auto main() -> int
{
cout << "[lambda_initialization_captures.cpp]" << endl;

// Initializing a variable
int a = 5;
cout << "Initial a = " << a << endl;

// Initializing value to lambda using the variable
auto myLambda = [&x = a]() { x += 2; };

// Executing the Lambda
myLambda();

// Displaying a new value of the variable
cout << "New a = " << a << endl;

return 0;
}

As we can see in the preceding code, we have an int variable named a with the value 5. The Lambda expression, myLambda, then captures the a value and executes it in the code. The result is that now the a value will be 7 since it is added by 2. The following output screenshot should appear in our console window when we run the preceding code:

From the preceding snapshot, we see that we can prepare the value to be included in the calculation inside the Lambda expression.

Writing a generic Lambda expression to be used many times with many different data types

Before C++14, we have to specifically state the type of the parameter list. Fortunately, now in C++14, Lambda expressions accept auto as a valid parameter type. Therefore, we can now build a generic Lambda expression as demonstrated in the following code. In that code, we have only one Lambda expression to find out which is the greatest value between two numbers passed to the expression. We will use the auto keyword in parameter declaration so it can be passed by any data type. Therefore, the findMax() function parameters can be passed by both the int and float data type. The code should be as follows:

    /* lambda_expression_generic.cpp */
#include <iostream>

using namespace std;

auto main() -> int
{
cout << "[lambda_expression_generic.cpp]" << endl;

// Creating a generic lambda expression
auto findMax = [](auto &x, auto &y){
return x > y ? x : y; };

// Initializing various variables
int i1 = 5, i2 = 3;
float f1 = 2.5f, f2 = 2.05f;

// Consuming generic lambda expression
// using integer data type
cout << "i1 = 5, i2 = 3" << endl;
cout << "Max: " << findMax(i1, i2) << endl << endl;

// Consuming generic lambda expression
// using double data type
cout << "f1 = 2.5f, f2 = 2.05f" << endl;
cout << "Max: " << findMax(f1, f2) << endl << endl;

return 0;
}

The output we will see on the console should be as follows:

The C++17 language plans to introduce two new features for the Lambda expression--they are capturing *this, which allows the expression to capture the enclosing object by copy, and the constexpr Lambda expressions, which allows us to use the result of the Lambda expressions and generate constexpr objects at compile time. However, since C++17 has not been released yet, we cannot try it for now.
lock icon The rest of the chapter is locked
Register for a free Packt account to unlock a world of extra content!
A free Packt account unlocks extra newsletters, articles, discounted offers, and much more. Start advancing your knowledge today.
Unlock this book and the full library FREE for 7 days
Get unlimited access to 7000+ expert-authored eBooks and videos courses covering every tech area you can think of
Renews at €18.99/month. Cancel anytime