Добавил:
Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
roth_stephan_clean_c20_sustainable_software_development_patt.pdf
Скачиваний:
29
Добавлен:
27.03.2023
Размер:
7.26 Mб
Скачать

Chapter 7 Functional Programming

In this case, every call of the member function with its suspicious name Clazz:: functionWithSideEffect() will alter an internal state of the instance of class Clazz. As a consequence, every call of this member function returns a different result, although the given parameter for the function’s argument is always the same. You can have similar effects in procedural programming with global variables that are manipulated by procedures. Functions that can produce different outputs even if they are always called with the same arguments are called impure functions. Another clear indicator that a function is an impure function is when it makes sense to call it without using its return value. If you can do that, this function must have any kind of side effect.

In a single-threaded execution environment, global states may cause a few problems and pain. But now imagine that you have a multithreaded execution environment, where several threads are running, calling functions in a non-deterministic order. In such an environment, global states, or object-wide states of instances, are often problematic and can cause unpredictable behavior or subtle errors.

Functional Programming in Modern C++

Believe it or not, functional programming has always been a part of C++! With this multiparadigm language, you were always able to program in a functional style, even with C++98. The reason that I can claim this is because of the existence of the known template metaprogramming (TMP) since the beginning of C++.

Functional Programming with C++ Templates

Many C++ developers know that template metaprogramming is a technique in which so-­ called templates are used by a compiler to generate C++ source code in a step before the compiler translates the source code to object code. What many programmers may not be aware of is the fact that template metaprogramming is functional programming, and that it is Turing Complete.

299

Chapter 7 Functional Programming

TURING COMPLETENESS

The term Turing Complete, named after the well-known English computer scientist, mathematician, logician, and cryptanalyst Alan Turing (1912 – 1954), is often used to define what makes a language a “real” programming language. A programming language is characterized as Turing Complete if you can solve any possible problem with it that can be theoretically computed by a Turing Machine. A Turing Machine is an abstract and theoretical machine invented by Alan Turing that serves as an idealized model for computations.

In practice, no computer system is really Turing Complete. The reason is that ideal Turing Completeness requires unlimited memory and unbounded recursions, what today’s computer systems cannot offer. Hence, some systems approximate Turing Completeness by modeling unbounded memory, but they are restricted by a physical limitation in the underlying hardware.

As a proof, we will calculate the greatest common divisor (GCD) of two integers using TMP only. The GCD of two integers, which are both not zero, is the largest positive integer that divides both of the given integers. See Listing 7-3.

Listing 7-3.  Calculating the Greatest Common Divisor Using Template

Metaprogramming

01 #include <iostream>

02

03 template< unsigned int x, unsigned int y > 04 struct GreatestCommonDivisor {

05static const unsigned int result = GreatestCommonDivisor< y, x % y >::result;

06 };

07

08 template< unsigned int x >

09struct GreatestCommonDivisor< x, 0 > {

10static const unsigned int result = x;

11};

12

13 int main() {

300

Chapter 7 Functional Programming

14std::cout << "The GCD of 40 and 10 is: " << GreatestCommonDivisor <40u, 10u>::result

15<< std::endl;

16std::cout << "The GCD of 366 and 60 is: " << GreatestCommonDivisor <366u, 60u>::result <<

17std::endl;

18return 0;

19}

This is the output that the program generates:

The GCD of 40 and 10 is: 10

The GCD of 366 and 60 is: 6

What is remarkable about this style of calculating the GCD at compile time using templates is that it is real functional programming. The two class templates used are completely free of states. There are no mutable variables, meaning that no variable can change its value once it has been initialized. During template instantiation, a recursive process is initiated that stops when the specialized class template on Lines 9-11 come into play. And, as mentioned, we have Turing Completeness in template

metaprogramming, meaning that any conceivable computation can be done at compile time using this technique.

Well, template metaprogramming is undoubtedly a powerful tool, but also has some disadvantages. Particularly the readability and understandability of the code can suffer drastically if a great deal of template metaprogramming is used. The syntax and idioms of TMP are sometimes not easy to understand. Users can be confronted with extensive and often cryptic error messages when something goes wrong, even if this can now be greatly reduced by using C++20 concepts (see Chapter 5). And, of course, the compile time also increases with an extensive use of template metaprogramming. Therefore, TMP is certainly a proper way of designing and developing generic multi-purpose libraries (an outstanding example is undoubtedly the C++ Standard Library), but should only be used in modern and well-crafted application code if this kind of generic programming is required (e.g., to minimize code duplication).

Since C++11, it is no longer necessary to use only template metaprogramming for compile-time computations. With the help of constant expressions (constexpr; see the section about computations during compile time in Chapter 5), the GCD can easily be implemented as a usual recursive function, as shown in Listing 7-4.

301