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

      ++out;

    }

  }

  return out;

}


Данная реализация очень похожа на

std::accumulate
, если считать параметр
out
переменной
init
и каким-то образом заменить функцией
f
конструкцию
if-construct
и ее тело!

Мы на самом деле сделали это — создали данную конструкцию

if-construct
и ее тело с помощью объекта бинарной функции, предоставленного в качестве параметра функции
std::accumulate
:


auto copy_and_advance ([](auto it, auto input) {

  *it = input;

  return ++it;

});


Функция

std::accumulate
помещает переменную
init
в параметр бинарной функции
it
. Второй параметр — текущее значение из диапазона, получаемое в цикле. Мы предоставили итератор вывода как параметр
init
функции
std::accumulate
. Таким образом, функция
std::accumulate
не считает сумму, а перемещает элементы, по которым итерирует, в другой диапазон. Это значит следующее: мы повторно реализовали функцию
std::copy
, которая пока что не имеет предикатов и преобразований.

Фильтрацию с помощью предиката добавим путем обертывания функции

copy_and_advance
в другой объект функции, который пользуется функцией-предикатом:


template 

auto filter(T predicate)

{

  return [=] (auto reduce_fn) {

    return [=] (auto accum, auto input) {

      if (predicate(input)) {

        return reduce_fn(accum, input);

      } else {

        return accum;

      }

    };

  };

}


Эта конструкция на первый взгляд выглядит сложной, но посмотрим на конструкцию

if
. Если функция-предикат вернет значение
true
, то параметры будут перенаправлены функции
reduce_fn
, в роли которой в нашем случае выступает функция
copy_and_advance
. Если же предикат вернет значение
false
, то переменная
accum
, выступающая в роли переменной
init
функции
std::accumulate
, будет возвращена без изменений. Так мы реализуем ту часть операции фильтрации, где пропускаем элемент. Конструкция
if
находится внутри лямбда-выражения, которое имеет такую же сигнатуру бинарной функции, что и функция
copy_and_advance;
это делает ее хорошей заменой.

Теперь мы можем отфильтровать элементы, но все еще не выполняем их преобразование. Это делается с помощью вспомогательной функции

map
:


template 

auto map(T fn)

{

  return [=] (auto reduce_fn) {

    return [=] (auto accum, auto input) {

      return reduce_fn(accum, fn(input));

    };

  };

}


Теперь код выглядит гораздо проще. Он тоже содержит внутреннее лямбда-выражение, которое имеет такую же сигнатуру, как и функция

copy_and_advance
, поэтому способен заменить ее. Реализация просто направляет дальше входные значения, но притом преобразует правый параметр вызова бинарной функции с помощью функции
fn
.

Далее, когда мы воспользуемся этими вспомогательными функциями, напишем следующее выражение:


filter(even)(

  map(twice)(

    copy_and_advance

  )

)


Вызов

filter(even)
захватывает предикат
even
и дает функцию, которая принимает бинарную функцию, чтобы обернуть ее в другую бинарную функцию, выполняющую фильтрацию. Функция
map(twice)
делает то же самое с функцией преобразования
twice
, но оборачивает бинарную функцию
copy_and_advance
в другую бинарную функцию, всегда преобразующую правый параметр.

Не выполнив оптимизацию, мы получим сложнейшую конструкцию, состоящую из вложенных функций, которые вызывают другие функции и при этом выполняют совсем немного работы. Однако компилятор может легко оптимизировать подобный код. Полученная бинарная функция так же проста, как и результат более прямолинейной реализации функции

transform_if
. Мы ничего не теряем с точки зрения производительности, приобретая компонуемость, свойственную функциям, поскольку смогли объединить предикат
even
и функцию преобразования
twice
столь же легко, как если бы на их месте были детали «Лего». 

Генерируем декартово произведение на основе любых входных данных во время компиляции

Лямбда-выражения и наборы параметров можно использовать для решения сложных задач. В этом разделе мы реализуем объект функции, который принимает произвольное количество входных параметров и генерирует декартово произведение данного множества, умноженного само на себя.

Декартово произведение — математическая операция. Она обозначается как

A x B
, что означает «декартово произведение множества А на множество В». Результатом будет одно множество, содержащее пары всех комбинаций элементов из множеств А и В. Операция, по сути, означает комбинирование каждого элемента из множества А с каждым элементом множества В. Эта операция показана на рис. 4.3.

Согласно схеме, если

A = (x,y,z)
, а
B = (1,2,3)
, то декартово произведение этих множеств будет равно
(x,1)
,
(x,2)
,
(x,3)
,
(y,1)
,
(y,2)
и т.д.

Если мы решим, что множества A и Bодинаковы, например

(1,2)
, то их декартово произведение будет равно
(1,1)
,
(1,2)
,
(2,1)
и
(2,2)
. В некоторых случаях это может оказаться избыточным, поскольку комбинация элементов с самими собой (например,
(1,1)
) или избыточные комбинации
(1,2)
и
(2,1)
способны стать ненужными. В таких случаях декартово произведение можно отфильтровать с помощью простого правила.

В этом разделе мы реализуем декартово произведение, не используя циклы, но применяя лямбда-выражения и распаковку набора параметров.


Как это делается

В примере мы реализуем объект функции, принимающий функцию

f
и набор параметров. Объект создаст декартово произведение набора параметров, отфильтрует избыточные части и вызовет для каждой пары функцию
f
.

1. Включим только тот заголовочный файл STL, который нужен для печати:


#include


2. Затем определим простую вспомогательную функцию, которая выводит на экран пару значений, и начнем реализовывать функцию

main
:


static void print(int x, int y)

{

  std::cout << "(" << x << ", " << y << ")\n";

}


int main()

{


3. Теперь начинается сложная часть. Сначала реализуем вспомогательную функцию для функции

cartesian
, которую напишем на следующем шаге. Данная функция принимает параметр
f
, являющийся функцией вывода на экран. Другие ее параметры — это