Кстати, это действительно ваш код?Вы не расширяете params
, поэтому он не должен компилироваться.
I.То, как вы определяете состав, неотличимо от простого вызова: ваш fComposition(f, g, arg)
совпадает с f(g(arg))
за исключением набора дополнительных символов.Реальной композицией обычно является комбинатор, который принимает две функции и возвращает замыкание, которое при вызове реальных аргументов применяет их последовательно.Примерно так:
template<class F, class G> auto comp(F f, G g) {
return [f, g](auto &&... args) {
return f(g(std::forward<decltype(args)>(args)...));
};
}
(Обратите внимание на привязки побочных значений. В C ++ 17 они более продвинуты, чем двадцать лет назад. :) Вы можете добавить std::move
s и std::forward
s по вкусу.)
Таким образом, вы создаете две функции:
auto fg = comp(f, g);
и позже вызываете результат с аргументами:
auto x = fg(arg1, arg2);
II.Но на самом деле, зачем ограничиваться двумя операндами?В Haskell (.)
- это одна двоичная функция.В C ++ у нас может быть целое дерево перегрузок:
template<class Root, class... Branches> auto comp(Root &&root, Branches &&... branches) {
return [root, branches...](auto &&...args) {
return root(branches(std::forward<decltype(args)>(args)...)...);
};
}
Теперь вы можете инкапсулировать любой AST в один вызываемый объект:
int f(int x, int y) { return x + y; }
int g(int x) { return x * 19; }
int h(int x) { return x + 2; }
#include <iostream>
int main() {
auto fgh = comp(f, g, h);
std::cout << fgh(2) << '\n';
}
Подобный метод был единственным известным способоммне иметь анонимные замыкания в C ++ до 11 стандарта.
III.Но подождите, есть ли решение для библиотеки?На самом деле да.Из описания std::bind
Если сохраненный аргумент имеет тип T, для которого std::is_bind_expression<T>::value == true
(например, другое выражение привязки было передано непосредственно в начальный вызовдля связывания), затем связывание выполняет композицию функций: вместо передачи объекта функции, который должен возвращать подвыражение связывания, подвыражение вызывается с нетерпением, и его возвращаемое значение передается внешнему вызываемому объекту.Если подвыражение связывания имеет какие-либо аргументы-заполнители, они используются совместно с внешним связыванием (выбрано из u1, u2, ...
).В частности, аргумент vn
в приведенном выше вызове std::invoke
равен arg(std::forward<Uj>(uj)...)
, а тип Vn
в том же вызове равен std::result_of_t<T cv &(Uj&&...)>&&
(квалификация cv такая же, как у g).
Извините, здесь нет примеров.> _ <</p>
PS И да, std::round
- перегруженная функция, поэтому вы должны ввести ее, чтобы указать, какую именно перегрузку вам необходимо составить.