auto concat(T t, Ts ...ts)
{
3. Теперь задача усложняется. Когда пользователь предоставит функции f
, g
и h
, мы оценим это выражение как f(concat(g, h))
, которое будет распаковано в f(g(concat(h)))
, на чем рекурсия остановится, и мы получим выражение f(g(h(...)))
. Данная цепочка вызовов функций, представляющая конкатенацию пользовательских функций, захватывается лямбда-выражением, которое затем может принять какие-то параметры p
и передать в вызов f(g(h(p)))
. Мы будем возвращать это лямбда-выражение. Конструкция if constexpr
проверяет, находимся ли мы на шаге рекурсии, требующем сконкатенировать более чем одну функцию:
if constexpr (sizeof...(ts) > 0) {
return [=](auto ...parameters) {
return t(concat(ts...)(parameters...));
};
}
4. Еще одна ветвь конструкции if constexpr
будет выбрана компилятором в том случае, если достигнут t
, поскольку она является единственным оставшимся параметром:
else {
return t;
}
}
5. Теперь применим нашу новую функцию конкатенации, передав в нее несколько функций. Начнем с функции main
, где определим два дешевых объекта функций:
int main()
{
auto twice ([] (int i) { return i * 2; });
auto thrice ([] (int i) { return i * 3; });
6. Выполним конкатенацию. Объединим два объекта функций умножения с помощью функции STL std::plus
, которая принимает два параметра и возвращает их сумму. Таким образом, получим функцию, выполняющую вызов twice(thrice(plus(a, b )))
.
auto combined (
concat(twice, thrice, std::plus
);
7. Воспользуемся тем, что получилось. Функция combined
теперь выглядит как обычная, и компилятор может объединять эти функции без особых задержек:
std::cout << combined(2, 3) << '\n';
}
8. Компиляция и запуск программы дадут следующий результат, и он не будет неожиданным, поскольку 2*3*(2+3)
равно 30
:
$ ./concatenation
30
Как это работает
Самой сложной частью этого раздела является функция concat
. Она выглядит очень мудреной, поскольку разворачивает набор параметров ts
в другое лямбда-выражение, которое рекурсивно снова вызывает функцию concat
, теперь уже с меньшим количеством параметров:
template
auto concat(T t, Ts ts)
{
if constexpr (sizeof...(ts) > 0) {
return [=](auto ...parameters) {
return t(concat(ts...)(parameters...));
};
} else {
return [=](auto ...parameters) {
return t(parameters...);
};
}
}
Напишем более простую версию этой функции, которая объединяет ровно три функции:
template
auto concat(F f, G g, H h)
{
return [=](auto ... params) {
return f(g(h(params...)));
};
}
Эта функция выглядит аналогично, но уже не так сложна. Мы возвращаем лямбда-выражение, которое захватывает f
, g
и h
. Оно принимает произвольно большое количество параметров и просто перенаправляет их по цепочке вызовов f
, g
и h
. Если мы пользуемся конструкцией auto combined(concat(f,g,h))
, а затем вызываем данный объект функции с двумя параметрами, например combined(2,3)
, то 2
, 3
представлены набором параметров из предыдущей функции concat
.
Повторный взгляд на гораздо более сложную обобщенную функцию concat
позволяет увидеть следующее: единственное, что мы действительно делаем по-другому, — выполняем конкатенацию f(g(h(params...)))
. Вместо этого мы пишем выражение f(concat(g,h))(params...)
, которое будет преобразовано в конструкцию f(g(concat(h)))(params...)
при следующем рекурсивном вызове, а затем — в конструкцию f(g(h(params...)))
.
Создаем сложные предикаты с помощью логической конъюнкции
При фильтрации данных с помощью обобщенного кода мы определяем
При фильтрации строк, например, можно реализовать предикат, который возвращает значение true
, если входная строка foo
". Еще один предикат должен возвращать значение true
, если входная строка bar
".