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

Chapter 7 Functional Programming

Listing 7-9.  Filling a Vector with 100 Random Numbers

#include "RandomGenerator.h" #include <algorithm> #include <functional> #include <iostream>

#include <vector>

using Numbers = std::vector<short>;

const std::size_t AMOUNT_OF_NUMBERS { 100 };

Numbers createVectorFilledWithRandomNumbers() { RandomNumberGenerator<short> randomNumberGenerator { }; Numbers randomNumbers(AMOUNT_OF_NUMBERS);

std::generate(begin(randomNumbers), end(randomNumbers), std::ref(randomNu mberGenerator));

return randomNumbers;

}

void printNumbersOnStdOut(const Numbers& numbers) { for (const auto& number : numbers) {

std::cout << number << std::endl;

}

}

int main() {

auto randomNumbers = createVectorFilledWithRandomNumbers(); printNumbersOnStdOut(randomNumbers);

return 0;

}

Unary Function

Next, let’s look at an example of a unary function-like object, which is a functor whose parenthesis operator has one parameter. See Listing 7-10.

306

Chapter 7 Functional Programming

Listing 7-10.  An Example of a Unary Functor

class ToSquare { public:

[[nodiscard]] constexpr int operator()(const int value) const noexcept { return value * value; }

};

As its name suggests, this functor squares the values passed to it in the parenthesis operator. This does not necessarily always have to be the case, because, a unary functor can also have private member variables, and thus a mutable state. Read or write access to global variables is also possible (...although this should not be the normal case nowadays).

With the ToSquare functor, we can now extend the previous example and apply it to the vector with the ascending integer sequence. See Listing 7-11.

Listing 7-11.  All 100 Numbers in a Vector Are Squared

#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::generate(begin(numbers), end(numbers), IncreasingNumberGenerator()); std::transform(begin(numbers), end(numbers), begin(numbers), ToSquare()); // ...to be continued...

return 0;

}

The used algorithm std::transform (defined in the <algorithm> header) applies the given function or function object to a range (defined by the first two parameters) and stores the result in another range (defined by the third parameter). In our case, these ranges are the same.

307

Chapter 7 Functional Programming

Predicate

Predicates are a special kind of functor. A unary functor is called a unary predicate if it has one parameter and a Boolean return value indicating a true or false result of some test, such as shown in Listing 7-12.

Listing 7-12.  An Example of a Predicate

class IsAnOddNumber { public:

[[nodiscard]] constexpr bool operator()(const int value) const noexcept { return (value % 2) != 0;

}

};

This predicate can now be applied to our number sequence using the std::erase_ if algorithm to get rid of all the odd numbers. See Listing 7-13.

Listing 7-13.  All Odd Numbers From the Vector Are Deleted Using std::erase_if

#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::generate(begin(numbers), end(numbers), IncreasingNumberGenerator()); std::transform(begin(numbers), end(numbers), begin(numbers), ToSquare()); std::erase_if(numbers, IsAnOddNumber());

// ...

return 0;

}

308

Chapter 7 Functional Programming

Note  Unless you are using the C++20 language standard, you will need to apply the Erase-Remove idiom to remove the odd numbers from the vector, which is explained in a sidebar in the section entitled “Building Abstractions Is Sometimes Hard” in Chapter 3.

In order to use a functor in a more flexible and generic way, it is usually implemented as a class template. Therefore, we can refactor our unary functor IsAnOddNumber into a class template so that it can be used with all integral types, such as short, int, unsigned int, uint64_t, etc. This can easily be done with the new C++20 concepts, as shown in Listing 7-14.

Listing 7-14.  Ensuring That the Template Parameter Is an Integral Data Type

#include <concepts>

template <std::integral T> class IsAnOddNumber { public:

[[nodiscard]] constexpr bool operator()(const T value) const noexcept { return (value % 2) != 0;

}

};

The location within the body of the main() function, where our predicate is used (the call of the std::erase_if function), must now be adjusted a little bit:

// ...

std::erase_if(numbers, IsAnOddNumber<Numbers::value_type>());

// ...

If we inadvertently use the IsAnOddNumber template with a non-integral data type, such as a double, we would get a meaningful error message from the compiler.

Listing 7-15 shows the entire example, completed with an output of the contents of the vector on stdout, using std::for_each and the PrintOnStdOut functor.

309

Chapter 7 Functional Programming

Listing 7-15.  The Whole Code Example with All Three Types of Functors

#include <algorithm> #include <concepts> #include <iostream>

#include <vector>

class IncreasingNumberGenerator { public:

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

private:

int number { 0 };

};

class ToSquare { public:

[[nodiscard]] constexpr int operator()(const int value) const noexcept { return value * value;

}

};

template <std::integral T> class IsAnOddNumber { public:

[[nodiscard]] constexpr bool operator()(const T value) const noexcept { return (value % 2) != 0;

}

};

class PrintOnStdOut { public:

void operator()(const auto& printable) const { std::cout << printable << '\n';

}

};

using Numbers = std::vector<int>;

310