0.0
для переменной-аккумулятора, которая, в конечном счете, будет содержать сумму произведений:
const auto dot_product (accumulate(
begin(zipped), end(zipped), 0.0, add_product));
17. Выведем на экран полученный результат:
cout << dot_product << '\n';
}
18. Компиляция и запуск программы дадут правильный результат:
32
Дополнительная информация
Да, нам пришлось написать много строк, чтобы добавить в код немного синтаксического сахара, и он все еще не так элегантен, как версия кода на Haskell. Большим недостатком является тот факт, что наш итератор-упаковщик жестко закодирован — он работает только для векторов, содержащих переменные типа
double
.С помощью шаблонов и типажей типов такой итератор может стать более обобщенным. Таким образом, он сможет работать со списками, векторами, двусторонними очередями и ассоциативными массивами, даже если они специализированы для совершенно других типов элементов контейнера.
Нельзя недооценивать объем работы, которую нужно выполнить, чтобы сделать эти классы обобщенными. К счастью, такие библиотеки уже существуют. Одной из популярных библиотек, не входящих в STL, является Boost (
zip_iterator
). Ее легко использовать для самых разных классов.Кстати, если хотите узнать о наиболее элегантном способе определения скалярного произведения в C++ и вам не особо нужна концепция итераторов-упаковщиков, то обратите внимание на
std::valarray
. Взгляните сами:
#include
#include
int main()
{
std::valarray a {1.0, 2.0, 3.0};
std::valarray b {4.0, 5.0, 6.0};
std::cout << (a * b).sum() << '\n';
}
Библиотека ranges. Это очень интересная библиотека для языка C++, которая поддерживает упаковщики и все прочие виды волшебных адаптеров итераторов, фильтров и т.д. Ее создатели вдохновлялись библиотекой
Boost ranges
, и какое-то время казалось, что она может попасть в C++17, но, к сожалению, придется ждать следующего стандарта. Библиотека значительно улучшит возможности написания выразительного и быстрого кода на C++ путем создания сложных механизмов из универсальных и простых блоков кода. В документации к ней можно найти очень простые примеры.1. Определение суммы квадратов всех чисел от
1
до 10
:
const int sum = accumulate(view::ints(1)
| view::transform([](int i){return i*i;})
| view::take(10), 0);
2. Фильтрация всех четных чисел из вектора чисел и преобразование остальных чисел в строки:
std::vector v {1,2,3,4,5,6,7,8,9,10};
auto rng = v | view::remove_if([](int i){return i % 2 == 1;})
| view::transform([](int i){return std::to_string(i);});
// rng == {"2"s,"4"s,"6"s,"8"s,"10"s};
Если вы заинтересовались и не можете дождаться выхода следующего стандарта С++, то обратитесь к документации для ranges, которая находится по адресу https://ericniebler.github.io/range-v3/.
Глава 4Лямбда-выражения
В этой главе:
□ динамическое определение функций с помощью лямбда-выражений;
□ добавление полиморфизма путем оборачивания лямбда-выражений в конструкцию
std::function
;□ создание функций с помощью конкатенации;
□ создание сложных предикатов с помощью логической конъюнкции;
□ вызов нескольких функций с одними и теми же входными данными;
□ реализация
transform_if
с применением std::accumulate
и лямбда-выражений;□ генерация декартова произведения на основе любых входных данных во время компиляции.
Введение
Одной из важных новых функций C++11 были лямбда-выражения. В C++14 и C++17 они получили новые возможности, и это сделало их еще мощнее. Но что же такое лямбда-выражение?
Лямбда-выражения или лямбда-функции создают замыкания. Замыкание — очень обобщенный термин для безымянных объектов, которые можно вызывать как функции. Чтобы предоставить подобную возможность в С++, такой объект должен реализовывать оператор вызова функции
()
, с параметрами или без. Создание аналогичного объекта без лямбда-выражений до появления С++11 выглядело бы так:
#include
#include
int main() {
struct name_greeter {
std::string name; void operator()() {
std::cout << "Hello, " << name << '\n';
}
};
name_greeter greet_john_doe {"John Doe"};
greet_john_doe();
}
Экземпляры структуры
name_greeter
, очевидно, содержат строку. Обратите внимание: тип этой структуры и объект не являются безымянными, в отличие от лямбда-выражений. С точки зрения замыканий можно утверждать, что они захватывают строку. Когда экземпляр-пример вызывается как функция без параметров, на экран выводится строка "Hello, John Doe
", поскольку мы указали строку с таким именем.Начиная с С++11 создавать подобные замыкания стало проще:
#include
int main() {
auto greet_john_doe ([] {
std::cout << "Hello, John Doe\n";
});
greet_john_doe();
}
На этом все. Целая структура
name_greeter
заменяется небольшой конструкцией [] { /* сделать что-то */ }
, которая на первый взгляд выглядит странно, но уже в следующем разделе мы рассмотрим все возможные случаи ее применения.Лямбда-выражения помогают поддерживать код обобщенным и чистым. Они могут применяться как параметры для обобщенных алгоритмов, чтобы уточнить их при обработке конкретных типов, определенных пользователем. Они также могут служить для оборачивания рабочих пакетов и данных, чтобы их можно было запускать в потоках или просто сохранять работу и откладывать само выполнение пакетов. После появления С++11 было создано множество библиотек, работающих с лямбда-выражениями, поскольку они стали естественной частью языка С++. Еще одним вариантом использования лямбда-выражений является метапрограммирование, поскольку они могут быть оценены во время выполнения программы. Однако мы не будем рассматривать этот вопрос, так как он не относится к теме данной книги.
В текущей главе мы в значительной мере будем опираться на отдельные шаблоны функционального программирования, которые могут показаться странными для новичков и даже опытных программистов, еще не работавших с такими шаблонами. Если в последующих примерах вы увидите лямбда-выражения, возвращающие лямбда-выражения, которые опять-таки возвращают лямбда-выражения, то, пожалуйста, не теряйтесь. Мы несколько выходим за рамки привычного стиля программирования, чтобы подготовиться к работе с современным языком С++, в котором шаблоны функционального программирования встречаются все чаще и чаще. Если код какого-то примера выглядит слишком сложным, то уделите время тому, чтобы подробнее разобрать его. Как только вы с этим разберетесь, сложные лямбда-выражения в реальных проектах больше не будут вас смущать.
Динамическое определение функций с помощью лямбда-выражений
Применяя лямбда-выражения, можно инкапсулировать код, чтобы вызвать его позже или даже в другом месте, поскольку его разрешено копировать. Кроме того, можно инкапсулировать код несколько раз с несколько различающимися параметрами, при этом не нужно будет реализовывать новый класс функции для этой задачи.
Синтаксис лямбда-выражений выглядел новым в С++11, и к С++17 он несколько изменился. В этом разделе мы увидим, как сейчас выглядят лямбда-выражения и что они означают.