++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
, являющийся функцией вывода на экран. Другие ее параметры — это