Book Image

Learning C++ Functional Programming

By : Wisnu Anggoro
5 (1)
Book Image

Learning C++ Functional Programming

5 (1)
By: Wisnu Anggoro

Overview of this book

Functional programming allows developers to divide programs into smaller, reusable components that ease the creation, testing, and maintenance of software as a whole. Combined with the power of C++, you can develop robust and scalable applications that fulfill modern day software requirements. This book will help you discover all the C++ 17 features that can be applied to build software in a functional way. The book is divided into three modules—the first introduces the fundamentals of functional programming and how it is supported by modern C++. The second module explains how to efficiently implement C++ features such as pure functions and immutable states to build robust applications. The last module describes how to achieve concurrency and apply design patterns to enhance your application’s performance. Here, you will also learn to optimize code using metaprogramming in a functional way. By the end of the book, you will be familiar with the functional approach of programming and will be able to use these techniques on a daily basis.
Table of Contents (15 chapters)
Title Page
Credits
About the Author
About the Reviewer
www.PacktPub.com
Customer Feedback
Preface

Getting closer with several new features in modern C++


So, what is new in modern C++ in comparison to the old one? There are so many changes in modern C++ compared to the old one, and the book pages will dramatically increase if we discuss all of them. However, we will discuss the new features in modern C++, which we should know about, to make us more productive in coding activities. We will discuss several new keywords, such as auto, decltype, and nullptr. We will also discuss the enhancement of the begin() and end() function that has now become a non-member class function. We will also discuss the augmented support for the for-eachtechnique to iterate over collections using the range-based for looptechniques.

The next few subsections in this chapter will also discuss the new features of modern C++, namely Lambda expressions, smart pointers, and tuples, which were just added in the C++11 release.

Defining the data type automatically using the auto keyword

Prior to the modern C++, the C++ language has a keyword named auto that is used to explicitly specify that the variable should have automatic duration. The automatic duration that adheres to the variable will create the variable at the point of definition (and initialized, if relevant) and destroy the variable when the block they are defined in is exited. For instance, the local variable will be created when it is defined at the beginning of the function and destroyed when the program exits the function where the local variable is there.

Since C++11, the auto keyword is used to tell the compiler to deduce the actual type of a variable that is being declared from its initializer. And since C++14, the keyword can also be applied to a function to specify the return type of the function that is a trailing return type. Now, in modern C++, the use of the auto keyword to specify the automatic duration is abolished since all variables are set to automatic duration by default.

The following is an auto.cpp code demonstrating the use of the auto keyword in the variables. We will define four variables with the auto keyword, and then find out the data type for each variable using the typeid() function. Let's take a look:

    /* auto.cpp */

    #include <iostream>
    #include <typeinfo>

    int main()
    {
      std::cout << "[auto.cpp]" << std::endl;

      // Creating several auto-type variables
      auto a = 1;
      auto b = 1.0;
      auto c = a + b;
      auto d = {b, c};

      // Displaying the preceding variables' type
      std::cout << "type of a: " << typeid(a).name() << std::endl;
      std::cout << "type of b: " << typeid(b).name() << std::endl;
      std::cout << "type of c: " << typeid(c).name() << std::endl;
      std::cout << "type of d: " << typeid(d).name() << std::endl;
      return 0;
    }

As we can see in the preceding code, we have an a variable that will store the integer value and have a b variable that will store the double value. We calculate the addition of a and b and store the result in variable c. Here, we expect that c will store the double object since we add the integer and double object. The last is the d variable that will store the initializer_list<double> data type. When we run the preceding code, we will see the following output on the console:

As can be seen in the preceding snapshot, we are just given the first character of the data type, such as i for integer, d for double, and St16initializer_listIdE for initializer_list<double>, that is the last lowercase d character that stands for double.

Note

We may have to enable the Run-Time Type Information (RTTI) feature in our compiler options to retrieve the data type object. However, GCC has enabled the feature by default. Also, the output of the use of the typeid() function depends on the compiler. We may get the raw type name or just a symbol as we did in the preceding example.

Besides, for variable, as we discussed earlier, the auto keyword can also be applied to a function to deduce a function's return type automatically. Suppose we have the following trivial function named add() to calculate the addition of two parameters:

    int add(int i, int j)
    {
      return i + j;
    }

We can refactor the preceding method to use the auto keyword, as we can see in the following lines of code:

    auto add(int i, int j)
    {
      return i + j;
    }

Similar to the auto-type variable, the compiler can decide the correct return type based on the returned value of the function. And, as shown in the preceding code, the function will indeed return the integer value since we just add two integer values.

Another feature that uses the auto keyword in modern C++ is trailing the return type syntax. By using this feature, we can specify the return type, the rest of the function prototype, or function signature. From the preceding code, we can refactor it to use the feature as follows:

    auto add(int i, int j) -> int
    {
      return i + j;
    }

You might ask me why we have to specify the data type again after the arrow symbol (->), even though we have used the auto keyword. We will find the answer when we cover the decltype keyword in the next section. Also, by using this feature, we can now refactor the preceding auto.cpp code a little bit by modifying the syntax of the main() method, instead of the following syntax of main() function signature:

    int main()
    {
      // The body of the function
    }

We can change the signature syntax into the following line of code:

    auto main -> int
    {
      // The body of the function
    }

Now, we will see all of our code in this book using this trailing return type feature to apply the modern C++ syntax.

Querying the type of an expression using the decltype keyword

We discussed in the preceding section that the auto keyword can automatically deduce the type of the variable based on the type of values it stores. The keyword can also deduce the function's return type based on the type of its return value. Now, let's combine the auto keyword and the decltype keyword to gain the power of modern C++.

Before we combine the two keywords, we will find out what the decltype keyword is used for--it is used for asking the type of an object or an expression. Let's take a look at the following several lines of trivial variable declaration:

    const int func1();
    const int& func2();
    int i;

    struct X { double d; };
    const X* x = new X();

Now, based on the preceding code, we can declare other variables using the decltype keyword as follows:

    // Declaring const int variable
    // using func1() type
    decltype(func1()) f1;

    // Declaring const int& variable
    // using func2() type
    decltype(func2()) f2;

    // Declaring int variable
    // using i type
    decltype(i) i1;

    // Declaring double variable
    // using struct X type
    decltype(x->d) d1; // type is double
    decltype((x->d)) d2; // type is const double&

As we can see in the preceding code, we can specify the type of an object based on another object's type. Now, let's suppose we need to refactor the preceding add() method to become a template. Without the auto and decltype keyword, we will have the following template implementation:

    template<typename I, typename J, typename K>
    K add(I i, J j)
    {
      return i + j;
    }

Fortunately, since the auto keyword can specify the return type of the function, which is a trailing return type, and the decltype keyword can deduce the type based on the expression, we can refactor the preceding template as follows:

    template<typename I, typename J>
    auto add(I i, J j) -> decltype(i + j)
    {
      return i + j;
    }

To prove, let's compile and run the following decltype.cpp code. We will use the following template to calculate the addition of two different value types--integer and double:

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

    // Creating template
    template<typename I, typename J>
    auto add(I i, J j) -> decltype(i + j)
    {
      return i + j;
    }

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

      // Consuming the template
      auto d = add<int, double>(2, 2.5);

      // Displaying the preceding variables' type
      std::cout << "result of 2 + 2.5: " << d << std::endl;

      return 0;
    }

The compilation process should run smoothly without error. We will see the following output on the screen if we run the preceding code:

As we can see, we have successfully combined the auto and decltype keyword to create a template simpler than we usually do before the modern C++ is announced.

Pointing to a null pointer

Another new feature in modern C++ is a keyword named nullptr that replaces the NULL macro to represent a null pointer. Now, there's no ambiguity in the use of the NULL macro for zero numeric or a null pointer. Let's suppose we have the following two method's signature in our declaration:

    void funct(const char *);
    void funct(int)

The former function will pass a pointer as the argument and the latter will pass the integer number as its argument. Then, we invoke the funct() method and pass the NULL macro as the parameter, as shown here:

    funct(NULL);

What we intend to call is the former function. However, since we pass the NULL parameters, which is basically defined as 0, the latter function will be invoked. In modern C++, we can use the nullptr keyword to ensure that we will pass a null pointer to the argument. The invocation of the funct() method should be as follows:

    funct(nullptr);

Now the compiler will invoke the former function since it passes a null pointer to the argument, and this is what we expect. There will be no ambiguity anymore, and it will avoid unnecessary future problems.

Returning an iterator using non-member begin() and end() function

Prior to modern C++, to iterate a sequence, we call the begin() and end() member method of each container. For array, we can iterate its element by iterating the index. Since C++11, the language has a non-member function--begin() and end()--to retrieve the iterator of the sequence. Let's suppose we have an array of the following elements:

    int arr[] = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 };

When the language doesn't have the begin() and end() function, we need to iterate the elements of the array using the index we can see in the following lines of code:

    for (unsigned int i = 0; i < sizeof(arr)/sizeof(arr[0]); ++i)
    // Do something to the array

Fortunately, using the begin() and end() function, we can refactor the preceding for loop to become as follows:

    for (auto i = std::begin(arr); i != std::end(arr); ++i)
    // Do something to the array

As we can see, the use of the begin() and end() function creates a compact code since we don't need to worry about the length of the array because the iterator pointer of begin() and end() will do it for us. For comparison, let's take a look at the following begin_end.cpp code:

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

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

      // Declaring an array
      int arr[] = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 };

      // Displaying the array elements
      // using conventional for-loop
      std::cout << "Displaying array element using conventional for-
       loop";
      std::cout << std::endl;
      for (unsigned int i = 0; i < sizeof(arr)/sizeof(arr[0]); ++i)
      std::cout << arr[i] << " ";
      std::cout << std::endl;

      // Displaying the array elements
      // using non-member begin() and end()
      std::cout << "Displaying array element using non-member begin()
       and end()";
      std::cout << std::endl;
      for (auto i = std::begin(arr); i != std::end(arr); ++i)
       std::cout << *i << " ";
      std::cout << std::endl;

      return 0;
    }

To prove the preceding code, we can compile the code, and, when we run it, the following output should be displayed on the console screen:

As we can see in the screenshot, we've got the exact same output when we use the conventional for-loop or begin() and end() functions.

Iterating over collections using range-based for loops

In the modern C++, there is a new feature that is augmented to support the for-each technique to iterate over collections. This feature is useful if you want to do something to the elements of a collection or array without caring about the number of elements or the indexes. The syntax of the feature is also simple. Suppose we have an array named arr and we want to iterate each element using the range-based for loop technique; we can use the following syntax:

    for (auto a : arr)
    // Do something with a

So, we can refactor our preceding begin_end.cpp code to use range-based for loop as we can see in the following code:

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

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

      // Declaring an array
      int arr[] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9};

      // Displaying the array elements
      // using non-member begin() and end()
      std::cout << "Displaying array element using range-based for
        loop";
      std::cout << std::endl;
for (auto a : arr) std::cout << a << " ";
      std::cout << std::endl;

      return 0;
    }

The syntax we see in the preceding code is simpler now. If we compile the preceding code, we should find no error and, if we run the code, we should see the following output on the console screen:

We now have a new technique to iterate over the collection without caring about the indexes of the collection. We will keep using it in this book.