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

Chapter 7 Functional Programming

Listing 7-21.  Putting Every Single Word in a List in Angle Brackets

#include <algorithm> #include <iostream>

#include <string> #include <vector>

int main() {

std::vector<std::string> quote { "That's", "one", "small", "step", "for", "a", "man,", "one", "giant", "leap", "for", "mankind." }; std::vector<std::string> result;

std::transform(begin(quote), end(quote), back_inserter(result), [](const std::string& word) { return "<" + word + ">"; });

std::for_each(begin(result), end(result),

[](const std::string& word) { std::cout << word << " "; });

return 0;

}

The output of this small program is as follows:

<That's> <one> <small> <step> <for> <a> <man,> <one> <giant> <leap> <for> <mankind.>

Generic Lambda Expressions (C++14)

With C++14, lambda expressions experienced additional improvements. Since C++14, it is okay to use auto (see the section about automatic type deduction in Chapter 5) as the return type of a function or a lambda. In other words, the compiler will deduce the type. Such lambda expressions are called generic lambda expressions.

Listing 7-22 shows an example.

318

Chapter 7 Functional Programming

Listing 7-22.  Applying a Generic Lambda Expression on Values of Different Data Type

#include <complex> #include <iostream>

int main() {

auto square = [](const auto& value) noexcept { return value * value; };

const auto result1 = square(12.56); const auto result2 = square(25u); const auto result3 = square(-6);

const auto result4 = square(std::complex<double>(4.0, 2.5));

std::cout << "result1 is " << result1 << "\n"; std::cout << "result2 is " << result2 << "\n"; std::cout << "result3 is " << result3 << "\n"; std::cout << "result4 is " << result4 << std::endl;

return 0;

}

The parameter type as well as the result type are derived automatically depending on the type of the concrete parameter (literal) when the function is compiled (in

the previous example, double, unsigned int, int, and a complex number of type std::complex<T>). Generalized lambdas are extremely useful in interaction with Standard Library algorithms, because they are universally applicable.

Lambda Templates (C++20)

The C++17 language standard that followed C++14 extended the capabilities of C++ lambdas. For instance, with C++17, it became possible to evaluate lambdas at compile-­ time, so-called constexpr lambdas. The new C++20 standard also offers further, mostly smaller improvements regarding more convenient uses of lambdas and to allow some advanced use cases.

However, one new C++20 add-on regarding lambda expressions is explicitly noteworthy: lambda templates!

319

Chapter 7 Functional Programming

Maybe you are a little surprised now and ask yourself, wait a minute! We have had generic lambdas since C++14. That is actually something like templates. For what purpose do we still need lambda templates now?

Let’s compare two lambda expressions for a multiplication, one implemented as a generic lambda (C++14) and one as a template lambda (C++20):

auto multiply1 = [](const auto multiplicand, const auto multiplier) { return multiplicand * multiplier;

};

auto multiply2 = []<typename T>(const T multiplicand, const T multiplier) { return multiplicand * multiplier;

};

If you would call them with identical parameters for both arguments, you will not notice a difference:

auto result1 = multiply1(10, 20); auto result2 = multiply2(10, 20);

In both cases, the value 200 can be found in the variables that receive the results. But what will happen if we call both variants with parameters of different types, e.g., an int for the multiplicand and a bool for the multiplier?

auto result3 = multiply1(10, true); auto result4 = multiply2(10, true);

Also in this case the compiler manages to translate the generic template multiply1 without complaint. (By the way, the result in result3 is 10 because the compiler expands the true to an int [integral promotion] and has the value 1.) However, we get a compiler error for the instantiation of the lambda template multiply2; for instance something like this:

error: deduced conflicting types for parameter 'T' ('int' and 'bool')

With lambda templates, developers cannot be prevented from accidental wrong instantiations or usages of lambdas. Furthermore, C++20 concepts (see the section entitled “Concepts: Requirements for Template Arguments” in Chapter 5) can of course be used to perform compile-time validations of a lambda’s template arguments. See Listing 7-23.

320

Chapter 7 Functional Programming

Listing 7-23.  Lambda Templates Can Also Be Equipped with Constraints Using C++20 Concepts

#include <concepts> #include <iostream>

#include <string>

template <typename T>

concept Number = std::integral<T> || std::floating_point<T>;

int main() {

auto add = [] <Number T> (const T addend1, const T addend2) { return addend1 + addend2;

};

const std::string string1 { "Hello" }; const std::string string2 { "World" };

auto result1

= add(10, 20);

//

OK

auto result2

= add('x', 'y');

 

// OK

auto

result3

=

add(10.0, 20.0);

// OK

 

auto

result4

=

add(string1, string2); // Compiler-error: constraints not

 

 

 

 

satisfied!

std::cout << result1 << ", " << result2 << ", " << result3 << std::endl;

return 0;

}

In this example, the lambda template is allowed only for use with numeric data types (int, float, double, ...). Although there is an operator called std::string::operator+ that allows two strings to be concatenated, an instantiation of the lambda template is prohibited by the Number<T> concept.

321