C++17 STL Стандартная библиотека шаблонов — страница 32 из 119

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 он несколько изменился. В этом разделе мы увидим, как сейчас выглядят лямбда-выражения и что они означают.