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

Chapter 7 Functional Programming

Listing 7-4.  A GCD Function Using Recursion That Can Be Evaluated at Compile Time

constexpr unsigned int greatestCommonDivisor(const unsigned int x,

const unsigned int y) noexcept

{

return y == 0 ? x : greatestCommonDivisor(y, x % y);

}

By the way, the mathematical algorithm behind this is called Euclidean algorithm, or Euclid’s algorithm, named after the ancient Greek mathematician Euclid.

And since C++17, the numeric algorithm std::gcd() has become part of the C++ Standard Library (defined in the <numeric> header), so it is not necessary to implement it on your own. See Listing 7-5.

Listing 7-5.  Using the std::gcd Function from the <numeric> Header

#include <iostream> #include <numeric>

int main() {

constexpr auto result = std::gcd(40, 10);

std::cout << "The GCD of 40 and 10 is: " << result << std::endl; return 0;

}

Function-Like Objects (Functors)

What was also always possible in C++ from the very beginning was the definition and use of so-called function-like objects, also known as functors (another synonym is functionals) in short. Technically speaking, a functor is more or less just a class that defines the parenthesis operator, that is, the operator(). After the instantiation of these classes, they can pretty much be used like functions.

Depending on whether the operator() has none, one, or two parameters, the functor is called a generator, unary function, or binary function. Let’s look at a generator first.

302

Chapter 7 Functional Programming

Generator

As the name “generator” reveals, this type of functor is used to produce something. See Listing 7-6.

Listing 7-6.  An Example of a Generator, a Functor That Is Called With No Argument

class IncreasingNumberGenerator { public:

[[nodiscard]] int operator()() noexcept { return number++; }

private:

int number { 0 };

};

The working principle is quite simple: every time IncreasingNumberGenerator: :operator() is called, the actual value of the member variable number is returned to the caller, and the value of this member variable is increased by 1. The following usage example prints a sequence of the numbers 0 to 2 on standard output:

int main() {

IncreasingNumberGenerator numberGenerator { }; std::cout << numberGenerator() << std::endl; std::cout << numberGenerator() << std::endl; std::cout << numberGenerator() << std::endl; return 0;

}

Remember the quote from Sean Parent that I presented in the section on algorithms in Chapter 5: no raw loops! To fill a std::vector<T> with a certain amount of increasing values, we should not implement a handcrafted loop. Instead, we can use std::generate or std::ranges::generate (since C++20), both defined in the <algorithm> header. Both are function templates that assign each element in a certain range a value generated by a given generator object. Hence, we can write the simple and well-readable code shown in Listing 7-7 to fill a vector with an increasing number sequence using IncreasingNumberGenerator.

303

Chapter 7 Functional Programming

Listing 7-7.  Filling a Vector with an Increasing Number Sequence Using std::ranges::generate

#include <algorithm>

#include <vector>

using Numbers = std::vector<int>;

int main() {

const std::size_t AMOUNT_OF_NUMBERS { 100 }; Numbers numbers(AMOUNT_OF_NUMBERS);

std::ranges::generate(numbers, IncreasingNumberGenerator()); // ...now 'numbers' contains values from 0 to 99...

return 0;

}

As one can easily imagine, these kinds of functors do not fulfill the strict requirements for pure functions. Generators do commonly have a mutable state, that is, when operator() is called, these functors usually have some side effect. In our case, the mutable state is represented by the private member variable called IncreasingNumberGe nerator::number, which is incremented after each call of the parenthesis operator.

STD::IOTA (SINCE C++11) AND STD::RANGES::IOTA_VIEW (SINCE C++20)

Since C++11, the <numeric> header has contained a function template called std::iota(), named after the functional symbol (Iota) from the programming language APL. It’s not a generator functor, but it can be used to fill a container with an ascending sequence of values in an elegant way. Since C++20, this function template is also specified as constexpr and thus usable in compile-time computations.

Thus, the line from the previous code example where the vector is filled can also be written as follows:

std::iota(begin(numbers), end(numbers), 0);

304

Chapter 7 Functional Programming

With the introduction of the Ranges library since C++20, there is another way to generate a sequence of elements by repeatedly incrementing an initial value: the Range factory std::ranges::iota_view (defined in the <ranges> header):

auto view = std::ranges::iota_view { 0, 100 }; std::vector<int> numbers(std::begin(view), std::end(view)); // ...now 'numbers' contains values from 0 to 99...

Another example of a function-like object of type generator is the random number generator functor class template shown in Listing 7-8. This functor encapsulates all the stuff that is necessary for the initialization and usage of a pseudorandom number generator (PRNG) based on the so-called Mersenne Twister algorithm (defined in the

<random> header).

Listing 7-8.  A Generator Functor Class Template Encapsulating a Pseudorandom Number Generator

#include <random>

template <typename NUMTYPE> class RandomNumberGenerator { public:

RandomNumberGenerator() { mersenneTwisterEngine.seed(randomDevice());

}

[[nodiscard]] NUMTYPE operator()() {

return distribution(mersenneTwisterEngine);

}

private:

std::random_device randomDevice; std::uniform_int_distribution<NUMTYPE> distribution; std::mt19937_64 mersenneTwisterEngine;

};

Listing 7-9 shows how the RandomNumberGenerator functor could then be used.

305