Функциональное программирование на C++. Осуществление подпункта f (a) (b) (c)
Я занимаюсь основами функционального программирования на C++. Я пытаюсь сделать функцию f(a)(b)(c)
, что вернет a + b + c
. Я успешно реализовал функцию f(a)(b)
который возвращает a + b. вот код для него:
std::function<double(double)> plus2(double a){
return[a](double b){return a + b; };
}
Я просто не могу понять, как реализовать функцию f(a)(b)(c)
который, как я уже говорил, должен вернуть a + b + c
.
9 ответов:
просто возьмите 2 элемента решения и расширить его, обернув его с другой лямбда.
так как вы хотите вернуть лямбда, которые получают
double
и возвращает adouble
s' addition lambda, все, что вам нужно сделать, это обернуть ваш текущий тип возврата другой функцией и добавить вложенную лямбду в ваш текущий (лямбда, которая возвращает лямбду):std::function<std::function<double(double)>(double)> plus3 (double a){ return [a] (double b) { return [a, b] (double c) { return a + b + c; }; }; }
как @Ðan отметил, вы можете пропустить
std::function<std::function<double(double)>(double)>
и ладить сauto
:auto plus3 (double a){ return [a] (double b) { return [a, b] (double c) { return a + b + c; }; }; }
вы можете расширить эту структуру для каждого числа элементов, используя более глубокие вложенные лямбды. Демонстрация для 4 элементов:
auto plus4 (double a){ return [a] (double b) { return [a, b] (double c) { return [a, b, c] (double d) { return a + b + c + d; }; }; }; }
вы можете сделать это, имея свои функции
f
возвратить функтор, т. е. объект, который реализуетoperator()
. Вот один из способов сделать это:struct sum { double val; sum(double a) : val(a) {} sum operator()(double a) { return val + a; } operator double() const { return val; } }; sum f(double a) { return a; }
пример
int main() { std::cout << f(1)(2)(3)(4) << std::endl; }
версия шаблона
вы даже можете написать шаблонную версию, которая позволит компилятору вывести тип. Попробуйте здесь.
template <class T> struct sum { T val; sum(T a) : val(a) {} template <class T2> auto operator()(T2 a) -> sum<decltype(val + a)> { return val + a; } operator T() const { return val; } }; template <class T> sum<T> f(T a) { return a; }
пример
в этом примере,
T
в конечном итоге решитdouble
:std::cout << f(1)(2.5)(3.1f)(4) << std::endl;
здесь немного другой подход, который возвращает ссылку на
*this
Сoperator()
, Так что у вас нет никаких копий плавающих вокруг. Это очень простая реализация функтора, который хранит состояние и левые складки рекурсивно на себе:#include <iostream> template<typename T> class Sum { T x_{}; public: Sum& operator()(T x) { x_ += x; return *this; } operator T() const { return x_; } }; int main() { Sum<int> s; std::cout << s(1)(2)(3); }
это не
f(a)(b)(c)
аcurry(f)(a)(b)(c)
. Мы оборачиваемf
таким образом, что каждый дополнительный аргумент либо возвращает другойcurry
или фактически вызывает функцию с нетерпением. Это C++17, но может быть реализован в C++11 с кучей дополнительной работы.обратите внимание, что это решение для карринга функции - это впечатление, которое я получил от вопроса, а не решение для сворачивания двоичной функции.
template <class F> auto curry(F f) { return [f](auto... args) -> decltype(auto) { if constexpr(std::is_invocable<F&, decltype(args)...>{}) { return std::invoke(f, args...); } else { return curry([=](auto... new_args) -> decltype(std::invoke(f, args..., new_args...)) { return std::invoke(f, args..., new_args...); }); } }; }
я пропустил переадресацию ссылок для краткости. Пример использования будет:
int add(int a, int b, int c) { return a+b+c; } curry(add)(1,2,2); // 5 curry(add)(1)(2)(2); // also 5 curry(add)(1, 2)(2); // still the 5th curry(add)()()(1,2,2); // FIVE auto f = curry(add)(1,2); f(2); // i plead the 5th
самый простой способ, который я могу придумать, чтобы сделать это, чтобы определить
plus3()
С точки зренияplus2()
.std::function<double(double)> plus2(double a){ return[a](double b){return a + b; }; } auto plus3(double a) { return [a](double b){ return plus2(a + b); }; }
это объединяет первые два списка аргументов в один arglist, который используется для вызова
plus2()
. Это позволяет нам повторно использовать наш ранее существующий код с минимальным повторением, и может быть легко расширен в будущем;plusN()
просто нужно вернуть лямбду, которая вызываетplusN-1()
, который будет передавать вызов до предыдущей функции в свою очередь, пока он не достигнетplus2()
. Оно можно использовать так:int main() { std::cout << plus2(1)(2) << ' ' << plus3(1)(2)(3) << '\n'; } // Output: 3 6
учитывая, что мы просто вызываем вниз в строке, мы можем легко превратить это в шаблон функции, что устраняет необходимость создавать версии для дополнительных аргументов.
template<int N> auto plus(double a); template<int N> auto plus(double a) { return [a](double b){ return plus<N - 1>(a + b); }; } template<> auto plus<1>(double a) { return a; } int main() { std::cout << plus<2>(1)(2) << ' ' << plus<3>(1)(2)(3) << ' ' << plus<4>(1)(2)(3)(4) << ' ' << plus<5>(1)(2)(3)(4)(5) << '\n'; } // Output: 3 6 10 15
посмотрим, как в действии здесь.
Я собираюсь играть.
вы хотите сделать карри сложить над добавлением. Мы могли бы решить эту проблему, или мы могли бы решить класс проблем, которые включают в себя это.
Итак, во-первых, дополнение:
auto add = [](auto lhs, auto rhs){ return std::move(lhs)+std::move(rhs); };
что выражает концепцию сложения довольно хорошо.
теперь, складывая:
template<class F, class T> struct folder_t { F f; T t; folder_t( F fin, T tin ): f(std::move(fin)), t(std::move(tin)) {} template<class Lhs, class Rhs> folder_t( F fin, Lhs&&lhs, Rhs&&rhs): f(std::move(fin)), t( f(std::forward<Lhs>(lhs), std::forward<Rhs>(rhs)) ) {} template<class U> folder_t<F, std::result_of_t<F&(T, U)>> operator()( U&& u )&&{ return {std::move(f), std::move(t), std::forward<U>(u)}; } template<class U> folder_t<F, std::result_of_t<F&(T const&, U)>> operator()( U&& u )const&{ return {f, t, std::forward<U>(u)}; } operator T()&&{ return std::move(t); } operator T() const&{ return t; } };
он принимает начальное значение и T, а затем разрешает цепочку.
template<class F, class T> folder_t<F, T> folder( F fin, T tin ) { return {std::move(fin), std::move(tin)}; }
теперь мы соединяем их.
auto adder = folder(add, 0); std::cout << adder(2)(3)(4) << "\n";
мы также можем использовать
folder
для других операций:auto append = [](auto vec, auto element){ vec.push_back(std::move(element)); return vec; };
использование:
auto appender = folder(append, std::vector<int>{}); for (int x : appender(1)(2)(3).get()) std::cout << x << "\n";
видео.
нужно позвонить
.get()
вот такfor(:)
петли не понимают нашу папкуoperator T()
. Мы можем исправить это с небольшим количеством работы, но.get()
легче.
Если вы открыты для использования библиотек, это действительно легко в Boost в Гане:
double plus4_impl(double a, double b, double c, double d) { return a + b + c + d; } constexpr auto plus4 = boost::hana::curry<4>(plus4_impl);
и затем использовать его так же, как вы хотите:
int main() { std::cout << plus4(1)(1.0)(3)(4.3f) << '\n'; std::cout << plus4(1, 1.0)(3)(4.3f) << '\n'; // you can also do up to 4 args at a time }
все эти ответы кажутся ужасно сложными.
auto f = [] (double a) { return [=] (double b) { return [=] (double c) { return a + b + c; }; }; };
делает именно то, что вы хотите, и он работает в C++11, в отличие от многих или, возможно, большинства других ответов здесь.
обратите внимание, что он не использует
std::function
что влечет за собой штраф за производительность, и действительно, он, вероятно, может быть встроен во многих случаях.
вот шаблон состояния синглтон вдохновил подход с использованием
operator()
изменить состояние.Edit: обменял ненужное назначение на инициализацию.
#include<iostream> class adder{ private: adder(double a)val(a){} double val = 0.0; static adder* mInstance; public: adder operator()(double a){ val += a; return *this;} static adder add(double a){ if(mInstance) delete mInstance; mInstance = new adder(a); return *mInstance;} double get(){return val;} }; adder* adder::mInstance = 0; int main(){ adder a = adder::add(1.0)(2.0)(1.0); std::cout<<a.get()<<std::endl; std::cout<<adder::add(1.0)(2.0)(3.0).get()<<std::endl; return 0; }