C ++ 11 — Функциональное программирование на C ++. Реализация f (a) (b) (c)

Я начал изучать основы функционального программирования на C ++. Я пытаюсь сделать функцию f(a)(b)(c) что вернется a + b + c, Я успешно реализовал функцию f(a)(b) который возвращает + B. Вот код для этого:

std::function<double(double)> plus2(double a){
return[a](double b){return a + b; };
}

Я просто не могу понять, как реализовать функцию f(a)(b)(c) который, как я уже говорил, должен вернуться a + b + c,

63

Решение

Просто возьмите раствор из двух элементов и разверните его, обернув его другой лямбдой.

Так как вы хотите вернуть лямбду, которая получает double и возвращает doubles ‘lambda, все, что вам нужно сделать, это обернуть ваш текущий тип возвращаемого значения другой функцией и добавить вложенную лямбду в вашу текущую (лямбда, которая возвращает лямбду):

std::function<std::function<double(double)>(double)> plus3 (double a){
return [a] (double b) {
return [a, b] (double c) {
return a + b + c;
};
};
}

  • Как @ Ðаn отметил, что вы можете пропустить 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;
    };
    };
    };
    }
    
58

Другие решения

Вы можете сделать это, имея свою функцию 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;
115

Здесь немного другой подход, который возвращает ссылку на *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);
}

Жить на Колиру

30

Это не 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
15

Самый простой способ сделать это — определить 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

Смотрите оба в действии Вот.

12

Я собираюсь играть.

Вы хотите сделать карри сгиб над сложением. Мы могли бы решить эту проблему, или мы могли бы решить класс проблем, которые включают это.

Итак, во-первых, дополнение:

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() это легче.

11

Если вы открыты для использования библиотек, это действительно легко Boost’s Hana:

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
}
10

Все эти ответы кажутся ужасно сложными.

auto f = [] (double a) {
return [=] (double b) {
return [=] (double c) {
return a + b + c;
};
};
};

делает именно то, что вы хотите, и это работает в C ++ 11, в отличие от многих или, возможно, большинство других ответов здесь.

Обратите внимание, что он не использует std::function что влечет за собой снижение производительности, и, действительно, во многих случаях оно может быть встроено.

4