#include
2. Сначала реализуем функцию multicall
, которая является основной для этого примера. Она принимает произвольное количество функций в качестве параметров и возвращает лямбда-выражение, принимающее один параметр. Она перенаправляет данный параметр всем функциям, предоставленным ранее. Таким образом, можно определить функцию auto call_all(multicall(f,g,h))
, а затем вызов call_all(123)
приведет к серии вызовов f(123); g(123); h(123);
. Эта функция уже выглядит сложной, поскольку требуется распаковать набор параметров, в котором находятся функции, в набор вызовов с помощью конструктора std::initializer_list
.
static auto multicall (auto ...functions)
{
return [=](auto x) {
(void)std::initializer_list
((void)functions(x), 0)...
};
};
}
3. Следующая вспомогательная функция принимает функцию f
и набор параметров xs
. После этого она вызывает функцию f
для каждого из параметров. Таким образом, вызов for_each(f,1,2,3)
приводит к серии вызовов: f(1); f(2); f(3);
. Функция, по сути, использует такой же синтаксический прием для распаковки набора параметров xs
в набор вызовов функций, как и функция, показанная ранее.
static auto for_each (auto f, auto ...xs) {
(void)std::initializer_list
((void)f(xs), 0)...
};
}
4. Функция brace_print
принимает два символа и возвращает новый объект функции, принимающий один параметр x
. Она выводит его на экран, окружив двумя символами, которые мы только что захватили.
static auto brace_print (char a, char b) {
return [=] (auto x) {
std::cout << a << x << b << ", ";
};
}
5. Теперь наконец можно использовать все эти функции в функции main
. Сначала определим функции f
, g
и h
. Они представляют функции print
, которые принимают значения и выводят их на экран, окружив разными скобками. Функция nl
принимает любой параметр и просто выводит на экран символ переноса строки.
int main()
{
auto f (brace_print('(', ')'));
auto g (brace_print('[', ']'));
auto h (brace_print('{', '}'));
auto nl ([](auto) { std::cout << '\n'; });
6. Объединим все эти функции с помощью вспомогательной функции multicall
:
auto call_fgh (multicall(f, g, h, nl));
7. Мы хотим, чтобы каждое предоставленное нами число было выведено на экран трижды в разных скобках. Таким образом, нужно выполнить один вызов функции, который приведет к пяти вызовам нашей мультифункции, а та, в свою очередь, выполнит четыре вызова для функций f
, g
, h
и nl
:
for_each(call_fgh, 1, 2, 3, 4, 5);
}
8. Перед компиляцией и запуском программы подумаем, какой результат должны получить:
$ ./multicaller
(1), [1], {1},
(2), [2], {2},
(3), [3], {3},
(4), [4], {4},
(5), [5], {5},
Как это работает
Вспомогательные функции, которые мы только что реализовали, выглядят очень сложными. Так произошло из-за распаковки набора параметров с помощью std::initializer_list
. Почему мы вообще использовали эту структуру данных? Еще раз взглянем на for_each
:
auto for_each ([](auto f, auto ...xs) {
(void)std::initializer_list
((void)f(xs), 0)...
};
});
Сердцем данной функции является выражение f(xs).xs
— набор параметров, и нужно f
. К сожалению, мы не можем просто написать конструкцию f(xs)...
с помощью нотации ...
, с которой уже знакомы.
Вместо этого можно создать список значений с помощью std::initializer_list
, имеющего конструктор с переменным числом параметров. Выражение наподобие return std::initializer_list
решает задачу, но имеет for_each
, которая тоже работает и при этом выглядит проще нашего варианта:
auto for_each ([](auto f, auto ...xs) {
return std::initializer_list
});
Она более проста для понимания, но имеет следующие недостатки.
1. Создает список инициализаторов для возвращаемых значений на основе вызовов функции f
. К этому моменту нас не волнуют возвращаемые значения.