Как можно каррирование в C++?

Что такое карри?

Как можно каррирование в C++?

Объясните, пожалуйста, связующие в STL контейнере?

Стоит ли изучать PHP в 2026-2027 годах?
Стоит ли изучать PHP в 2026-2027 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
50
0
26 889
10
Перейти к ответу Данный вопрос помечен как решенный

Ответы 10

Ответ принят как подходящий

Короче говоря, каррирование принимает функцию f(x, y) и, учитывая фиксированный Y, дает новую функцию g(x), где

g(x) == f(x, Y)

Эта новая функция может быть вызвана в ситуациях, когда предоставляется только один аргумент, и она передает вызов исходной функции f с фиксированным аргументом Y.

Биндеры в STL позволяют делать это для функций C++. Например:

#include <functional>
#include <iostream>
#include <vector>

using namespace std;

// declare a binary function object
class adder: public binary_function<int, int, int> {
public:
    int operator()(int x, int y) const
    {
        return x + y;
    }
};

int main()
{
    // initialise some sample data
    vector<int> a, b;
    a.push_back(1);
    a.push_back(2);
    a.push_back(3);

    // here we declare a function object f and try it out
    adder f;
    cout << "f(2, 3) = " << f(2, 3) << endl;

    // transform() expects a function with one argument, so we use
    // bind2nd to make a new function based on f, that takes one
    // argument and adds 5 to it
    transform(a.begin(), a.end(), back_inserter(b), bind2nd(f, 5));

    // output b to see what we got
    cout << "b = [" << endl;
    for (vector<int>::iterator i = b.begin(); i != b.end(); ++i) {
        cout << "  " << *i << endl;
    }
    cout << "]" << endl;

    return 0;
}

Marcin: raj запросил пример C++, прежде чем я его добавил. :)

Greg Hewgill 02.10.2008 04:16

Хотел бы я проголосовать за комментарий. Эта цепочка комментариев забавна. :)

pestophagous 02.10.2008 20:39

На самом деле, я думаю, что это неправильный ответ; в этом ответе описывается «частичное приложение», которое выполняют функции bind. «Каррирование» - это процесс преобразования функции, принимающей N аргументов, в функцию, принимающую один аргумент и возвращающую функцию с N-1 аргументами, например void (*)(int, short, bool) становится X (*)(int), а X - void (*)(short, bool). Смотрите в haskell.org/haskellwiki/Currying все, что вы когда-либо хотели знать о каррировании и частичном применении функций.

Frerich Raabe 20.09.2011 21:34

@FrerichRaabe Вы совершенно правы; но цель каррирования - добиться (легкого) частичного применения.

Marcin 19.03.2012 20:20

@Marcin: Я нашел этот вопрос, потому что мне нужен карри, а не какое-либо частичное приложение. Разница в том, что когда у вас есть функция с 6 аргументами, тщательно написанными так, что вам нужно исправить первый аргумент, вы не хотите помещать туда все заполнители!

Jan Hudec 23.11.2012 17:00

Каррирование - это способ сокращения функции, которая принимает несколько аргументов, до последовательности вложенных функций с одним аргументом каждая:

full = (lambda a, b, c: (a + b + c))
print full (1, 2, 3) # print 6

# Curried style
curried = (lambda a: (lambda b: (lambda c: (a + b + c))))
print curried (1)(2)(3) # print 6

Каррирование - это хорошо, потому что вы можете определять функции, которые являются просто оболочками для других функций с предопределенными значениями, а затем передавать упрощенные функции. Связывания C++ STL обеспечивают реализацию этого на C++.

> Компоновщики C++ STL обеспечивают реализацию этого на C++. да, но только для унарных и бинарных функций ...

ugasoft 30.09.2008 12:59

а сломанные связыватели C++ 03 (текущий C++) не работают с функциями, имеющими ссылочные параметры. Они просто не справляются с проблемой ссылки на ссылку. буст-связующие намного умнее

Johannes Schaub - litb 04.09.2009 06:34

@ugasoft B-но выполнение унарной операции похоже на разбавление вина в сброженном виноградном соке смущенный

Ludovic Zenohate Lagouardette 28.01.2016 18:32

Взгляните на Boost.Bind, который делает процесс, показанный Грегом, более универсальным:

transform(a.begin(), a.end(), back_inserter(b), bind(f, _1, 5));

Это связывает 5 со вторым аргументом f.

Стоит отметить, что это каррирование нет (а не частичное применение). Однако в C++ сложно использовать каррирование в общих чертах (на самом деле, это стало возможным совсем недавно), и вместо этого часто используется частичное приложение.

Конрад, поскольку вы используете форму std :: transform с одним источником, не следует ли вам использовать _1 вместо _2? Тот факт, что вы написали bind (f, 5, _1) вместо bind (f, _1, 5), говорит о том, что переменная подключается к f в качестве второго аргумента.

paxos1977 30.09.2008 20:31

Я не могу рекомендовать Boost.Bind достаточно - изучите его, используйте, полюбите !!

fbrereto 04.09.2009 00:35

Это не карри. Это частичное приложение.

Thomas Eding 26.02.2015 07:39

@Thomas Вы, конечно, совершенно правы. На самом деле, меня очень раздражает, что R-сообщество использует неправильную терминологию для него. Мой ответ на самом деле не был попыткой продать это как каррирование, а просто показал лучший способ реализации принятого ответа. при этом я обновил свой ответ, чтобы прояснить это.

Konrad Rudolph 26.02.2015 12:15

Разница между каррированием и частичным применением: частичное приложение принимает такую ​​функцию, как f (x, y), и связывает некоторые аргументы с определенными значениями, возвращая функцию с меньшим количеством аргументов. Например. мы могли бы привязать y к конкретному значению 2, а затем вернуть функцию h (x) = f (x, 2): h - f, частично примененная. h (x) не обозначает функцию; он обозначает значение f (x, 2). Каррирование не связывает определенные ценности. Если мы карри f (x, y), удалив параметр y, мы получим h (x), который возвращает функцию. h (x) обозначает функцию g, такую ​​что g (y) обозначает f (x, y).

Kaz 08.01.2019 04:34

Таким образом, каррирование дает нам «частичную фабрику приложений». Мы можем выбрать какое-нибудь значение, например 2, а затем написать h (2). Это даст нам функцию g (y), которая вызывает f (2, y): параметр x был частично применен. Мы каррировали ж, чтобы получить час, а затем вызвали час с определенным значением, чтобы вызвать частичное применение ж.

Kaz 08.01.2019 04:38

Упрощая пример Грегга, используя tr1:

#include <functional> 
using namespace std;
using namespace std::tr1;
using namespace std::tr1::placeholders;

int f(int, int);
..
int main(){
    function<int(int)> g     = bind(f, _1, 5); // g(x) == f(x, 5)
    function<int(int)> h     = bind(f, 2, _1); // h(x) == f(2, x)
    function<int(int,int)> j = bind(g, _2);    // j(x,y) == g(y)
}

Функциональные компоненты Tr1 позволяют писать код в функциональном стиле на C++. Кроме того, C++ 0x позволит встроенным лямбда-функциям делать то же самое:

int f(int, int);
..
int main(){
    auto g = [](int x){ return f(x,5); };      // g(x) == f(x, 5)
    auto h = [](int x){ return f(2,x); };      // h(x) == f(2, x)
    auto j = [](int x, int y){ return g(y); }; // j(x,y) == g(y)
}

И хотя C++ не обеспечивает обширного анализа побочных эффектов, который выполняют некоторые функционально-ориентированные языки программирования, анализ констант и лямбда-синтаксис C++ 0x могут помочь:

struct foo{
    int x;
    int operator()(int y) const {
        x = 42; // error!  const function can't modify members
    }
};
..
int main(){
    int x;
    auto f = [](int y){ x = 42; }; // error! lambdas don't capture by default.
}

Надеюсь, это поможет.

Да ... это НЕ карри.

Thomas Eding 26.02.2015 07:36

Другие ответы хорошо объясняют связующие, поэтому я не буду повторять эту часть здесь. Я только продемонстрирую, как каррирование и частичное применение могут быть выполнены с лямбдами в C++ 0x.

Пример кода: (Пояснение в комментариях)

#include <iostream>
#include <functional>

using namespace std;

const function<int(int, int)> & simple_add = 
  [](int a, int b) -> int {
    return a + b;
  };

const function<function<int(int)>(int)> & curried_add = 
  [](int a) -> function<int(int)> {
    return [a](int b) -> int {
      return a + b;
    };
  };

int main() {
  // Demonstrating simple_add
  cout << simple_add(4, 5) << endl; // prints 9

  // Demonstrating curried_add
  cout << curried_add(4)(5) << endl; // prints 9

  // Create a partially applied function from curried_add
  const auto & add_4 = curried_add(4);
  cout << add_4(5) << endl; // prints 9
}

Эти ссылки актуальны:

На странице Lambda Calculus в Википедии есть наглядный пример каррирования
. http://en.wikipedia.org/wiki/Lambda_calculus#Motivation

В этой статье рассматривается каррирование на C / C++
. http://asg.unige.ch/site/papers/Dami91a.pdf

Если вы используете C++ 14, это очень просто:

template<typename Function, typename... Arguments>
auto curry(Function function, Arguments... args) {
    return [=](auto... rest) {
        return function(args..., rest...);
    }; // don't forget semicolumn
}

Затем вы можете использовать его так:

auto add = [](auto x, auto y) { return x + y; }

// curry 4 into add
auto add4 = curry(add, 4);

add4(6); // 10

Технически это частичная оценка функции: полное карри принимает функцию f с 3 аргументами и разрешает curry(f)(3)(2)(1) перед вызовом. +1, потому что частичная оценка функции по-прежнему полезна (и часто то, что люди подразумевают под «карри»), и из-за того, насколько она жесткая. Вы можете повысить производительность с помощью гораздо большего количества кода и идеальной пересылки.

Yakk - Adam Nevraumont 30.10.2014 16:48

... или с небольшим количеством кода и перемещением аргументов из внешней "фабричной" функции в возвращенную лямбду! ... или, если известно, что они всегда немодифицированы, просто возьмите константные ссылки и не нужно ничего копировать или перемещать. Тем не менее, до C++ 20 было немного больно перемещать вариативный пакет аргументов в лямбда: до этого вы должны спрятать их в tuple, а затем в apply.

underscore_d 02.11.2019 18:05

@underscore_d C++ 20 уже здесь; можно ли улучшить этот ответ сейчас?

TamaMcGlinn 07.12.2020 12:46

1. Что такое карри?

Каррирование просто означает преобразование функции нескольких аргументов в функцию одного аргумента. Проще всего это проиллюстрировать на примере:

Возьмем функцию f, которая принимает три аргумента:

int
f(int a,std::string b,float c)
{
    // do something with a, b, and c
    return 0;
}

Если мы хотим вызвать f, мы должны предоставить все его аргументы f(1,"some string",19.7f).

Затем каррированная версия f, назовем ее curried_f=curry(f), ожидает только один аргумент, который соответствует первому аргументу f, а именно аргументу a. Кроме того, f(1,"some string",19.7f) может быть написан с использованием каррированной версии как curried_f(1)("some string")(19.7f). С другой стороны, возвращаемое значение curried_f(1) - это просто еще одна функция, которая обрабатывает следующий аргумент f. В итоге мы получаем функцию или вызываемый curried_f, который удовлетворяет следующему равенству:

curried_f(first_arg)(second_arg)...(last_arg) == f(first_arg,second_arg,...,last_arg).

2. Как добиться каррирования в C++?

Следующее немного сложнее, но для меня работает очень хорошо (с использованием C++ 11) ... Это также позволяет каррирование произвольной степени, например: auto curried=curry(f)(arg1)(arg2)(arg3) и более поздние версии auto result=curried(arg4)(arg5). Вот оно:

#include <functional>

namespace _dtl {

    template <typename FUNCTION> struct
    _curry;

    // specialization for functions with a single argument
    template <typename R,typename T> struct
    _curry<std::function<R(T)>> {
        using
        type = std::function<R(T)>;
        
        const type
        result;
        
        _curry(type fun) : result(fun) {}
        
    };

    // recursive specialization for functions with more arguments
    template <typename R,typename T,typename...Ts> struct
    _curry<std::function<R(T,Ts...)>> {
        using
        remaining_type = typename _curry<std::function<R(Ts...)> >::type;
        
        using
        type = std::function<remaining_type(T)>;
        
        const type
        result;
        
        _curry(std::function<R(T,Ts...)> fun)
        : result (
            [=](const T& t) {
                return _curry<std::function<R(Ts...)>>(
                    [=](const Ts&...ts){ 
                        return fun(t, ts...); 
                    }
                ).result;
            }
        ) {}
    };
}

template <typename R,typename...Ts> auto
curry(const std::function<R(Ts...)> fun)
-> typename _dtl::_curry<std::function<R(Ts...)>>::type
{
    return _dtl::_curry<std::function<R(Ts...)>>(fun).result;
}

template <typename R,typename...Ts> auto
curry(R(* const fun)(Ts...))
-> typename _dtl::_curry<std::function<R(Ts...)>>::type
{
    return _dtl::_curry<std::function<R(Ts...)>>(fun).result;
}

#include <iostream>

void 
f(std::string a,std::string b,std::string c)
{
    std::cout << a << b << c;
}

int 
main() {
    curry(f)("Hello ")("functional ")("world!");
    return 0;
}

Просмотр вывода

Хорошо, как прокомментировал Самер, я должен добавить несколько пояснений, как это работает. Фактическая реализация выполняется в _dtl::_curry, в то время как функции шаблона curry являются лишь удобными оболочками. Реализация рекурсивна по аргументам аргумента шаблона std::functionFUNCTION.

Для функции только с одним аргументом результат идентичен исходной функции.

        _curry(std::function<R(T,Ts...)> fun)
        : result (
            [=](const T& t) {
                return _curry<std::function<R(Ts...)>>(
                    [=](const Ts&...ts){ 
                        return fun(t, ts...); 
                    }
                ).result;
            }
        ) {}

Здесь есть хитрость: для функции с большим количеством аргументов мы возвращаем лямбду, аргумент которой привязан к первому аргументу вызова fun. Наконец, оставшееся каррирование для оставшихся аргументов N-1 делегируется реализации _curry<Ts...> с одним аргументом шаблона меньше.

Обновление для C++ 14/17:

Мне только что пришла в голову новая идея подойти к проблеме каррирования ... С введением if constexpr в C++ 17 (и с помощью void_t, чтобы определить, полностью ли каррирована функция), все стало намного проще. :

template< class, class = std::void_t<> > struct 
needs_unapply : std::true_type { };
 
template< class T > struct 
needs_unapply<T, std::void_t<decltype(std::declval<T>()())>> : std::false_type { };

template <typename F> auto
curry(F&& f) {
  /// Check if f() is a valid function call. If not we need 
  /// to curry at least one argument:
  if constexpr (needs_unapply<decltype(f)>::value) {
       return [=](auto&& x) {
            return curry(
                [=](auto&&...xs) -> decltype(f(x,xs...)) {
                    return f(x,xs...);
                }
            );
        };    
  }
  else {  
    /// If 'f()' is a valid call, just call it, we are done.
    return f();
  }
}

int 
main()
{
  auto f = [](auto a, auto b, auto c, auto d) {
    return a  * b * c * d;
  };
  
  return curry(f)(1)(2)(3)(4);
}

См. Код в действии на здесь. Используя аналогичный подход, здесь позволяет каррировать функции с произвольным количеством аргументов.

Та же идея, похоже, работает и в C++ 14, если мы заменим constexpr if на выбор шаблона в зависимости от теста needs_unapply<decltype(f)>::value:

template <typename F> auto
curry(F&& f);

template <bool> struct
curry_on;

template <> struct
curry_on<false> {
    template <typename F> static auto
    apply(F&& f) {
        return f();
    }
};

template <> struct
curry_on<true> {
    template <typename F> static auto 
    apply(F&& f) {
        return [=](auto&& x) {
            return curry(
                [=](auto&&...xs) -> decltype(f(x,xs...)) {
                    return f(x,xs...);
                }
            );
        };
    }
};

template <typename F> auto
curry(F&& f) {
    return curry_on<needs_unapply<decltype(f)>::value>::template apply(f);
}

вам нужно объяснить в формулировке ответ, а не просто опубликовать код, OP не просто запрашивал код, он просил объяснений!

Samer 06.11.2014 01:36

+1 Это первый ответ, который на самом деле предлагает решение вопроса «Как каррирование выполняется в C++?». Как указано в комментариях к некоторым другим ответам, общее каррирование (как здесь реализовано) отличается от частичного применения конечного числа конкретных аргументов.

Oguk 07.11.2014 00:47

проверьте также ответ Люка Дантона в Частичное приложение с лямбдой C++?, который также обеспечивает красивую и, возможно, несколько более полную реализацию в отношении частичного применения.

Julian 07.11.2014 22:36

Вот ответ, который я ищу: реализация каррирования с использованием вариативных шаблонов функций.

Yongwei Wu 21.12.2014 10:36

Не используйте двойные подчеркивания в коде C++ (если вы не являетесь поставщиком компилятора). Это просто незаконно.

Thomas Eding 26.02.2015 07:42

@ThomasEding Разве это не законно, если вы не начинаете с двойного подчеркивания?

user146043 26.02.2015 13:04

@ThomasEding: Спасибо за комментарий! На самом деле я этого не знал, так как он отлично скомпилировался ...

Julian 26.02.2015 17:58

@Poldie: нельзя начинать идентификатор с подчеркивания, за которым следует заглавная буква.

Thomas Eding 26.02.2015 22:03

@ThomasEding Или числовую цифру.

user146043 27.02.2015 00:54

@Julian У меня есть код карри, который в основном основан на вашем коде: nvwa.cvs.sourceforge.net/viewvc/nvwa/nvwa/…. Если вы можете прислать мне свое имя, я могу добавить его в качестве кредита. Ваши комментарии также будут приветствоваться. Вы можете связаться со мной по адресу wuyongwei AT GMail.

Yongwei Wu 28.02.2015 18:37

std::void_t также является C++ 17, но может быть заменен: en.cppreference.com/w/cpp/types/void_t

Morten 06.12.2016 17:52

Без каких-либо объяснений это совершенно бесполезно.

moose 11.03.2017 23:37

Я также реализовал каррирование с помощью вариативных шаблонов (см. Ответ Джулиана). Однако я не использовал рекурсию или std::function. Примечание: он использует ряд функций C++ 14.

Приведенный пример (функция main) фактически выполняется во время компиляции, доказывая, что метод каррирования не превосходит существенные оптимизации компилятора.

Код можно найти здесь: https://gist.github.com/Garciat/c7e4bef299ee5c607948

с помощью этого вспомогательного файла: https://gist.github.com/Garciat/cafe27d04cfdff0e891e

Код все еще требует (большой) работы, которую я, возможно, завершу в ближайшее время. В любом случае, я публикую это здесь для использования в будущем.

Код публикации в случае, если ссылки умирают (хотя и не должны):

#include <type_traits>
#include <tuple>
#include <functional>
#include <iostream>

// ---

template <typename FType>
struct function_traits;

template <typename RType, typename... ArgTypes>
struct function_traits<RType(ArgTypes...)> {
    using arity = std::integral_constant<size_t, sizeof...(ArgTypes)>;

    using result_type = RType;

    template <size_t Index>
    using arg_type = typename std::tuple_element<Index, std::tuple<ArgTypes...>>::type;
};

// ---

namespace details {
    template <typename T>
    struct function_type_impl
      : function_type_impl<decltype(&T::operator())>
    { };

    template <typename RType, typename... ArgTypes>
    struct function_type_impl<RType(ArgTypes...)> {
        using type = RType(ArgTypes...);
    };

    template <typename RType, typename... ArgTypes>
    struct function_type_impl<RType(*)(ArgTypes...)> {
        using type = RType(ArgTypes...);
    };

    template <typename RType, typename... ArgTypes>
    struct function_type_impl<std::function<RType(ArgTypes...)>> {
        using type = RType(ArgTypes...);
    };

    template <typename T, typename RType, typename... ArgTypes>
    struct function_type_impl<RType(T::*)(ArgTypes...)> {
        using type = RType(ArgTypes...);
    };

    template <typename T, typename RType, typename... ArgTypes>
    struct function_type_impl<RType(T::*)(ArgTypes...) const> {
        using type = RType(ArgTypes...);
    };
}

template <typename T>
struct function_type
  : details::function_type_impl<typename std::remove_cv<typename std::remove_reference<T>::type>::type>
{ };

// ---

template <typename Args, typename Params>
struct apply_args;

template <typename HeadArgs, typename... Args, typename HeadParams, typename... Params>
struct apply_args<std::tuple<HeadArgs, Args...>, std::tuple<HeadParams, Params...>>
  : std::enable_if<
        std::is_constructible<HeadParams, HeadArgs>::value,
        apply_args<std::tuple<Args...>, std::tuple<Params...>>
    >::type
{ };

template <typename... Params>
struct apply_args<std::tuple<>, std::tuple<Params...>> {
    using type = std::tuple<Params...>;
};

// ---

template <typename TupleType>
struct is_empty_tuple : std::false_type { };

template <>
struct is_empty_tuple<std::tuple<>> : std::true_type { };

// ----

template <typename FType, typename GivenArgs, typename RestArgs>
struct currying;

template <typename FType, typename... GivenArgs, typename... RestArgs>
struct currying<FType, std::tuple<GivenArgs...>, std::tuple<RestArgs...>> {
    std::tuple<GivenArgs...> given_args;

    FType func;

    template <typename Func, typename... GivenArgsReal>
    constexpr
    currying(Func&& func, GivenArgsReal&&... args) :
      given_args(std::forward<GivenArgsReal>(args)...),
      func(std::move(func))
    { }

    template <typename... Args>
    constexpr
    auto operator() (Args&&... args) const& {
        using ParamsTuple = std::tuple<RestArgs...>;
        using ArgsTuple = std::tuple<Args...>;

        using RestArgsPrime = typename apply_args<ArgsTuple, ParamsTuple>::type;

        using CanExecute = is_empty_tuple<RestArgsPrime>;

        return apply(CanExecute{}, std::make_index_sequence<sizeof...(GivenArgs)>{}, std::forward<Args>(args)...);
    }

    template <typename... Args>
    constexpr
    auto operator() (Args&&... args) && {
        using ParamsTuple = std::tuple<RestArgs...>;
        using ArgsTuple = std::tuple<Args...>;

        using RestArgsPrime = typename apply_args<ArgsTuple, ParamsTuple>::type;

        using CanExecute = is_empty_tuple<RestArgsPrime>;

        return std::move(*this).apply(CanExecute{}, std::make_index_sequence<sizeof...(GivenArgs)>{}, std::forward<Args>(args)...);
    }

private:
    template <typename... Args, size_t... Indices>
    constexpr
    auto apply(std::false_type, std::index_sequence<Indices...>, Args&&... args) const& {
        using ParamsTuple = std::tuple<RestArgs...>;
        using ArgsTuple = std::tuple<Args...>;

        using RestArgsPrime = typename apply_args<ArgsTuple, ParamsTuple>::type;

        using CurryType = currying<FType, std::tuple<GivenArgs..., Args...>, RestArgsPrime>;

        return CurryType{ func, std::get<Indices>(given_args)..., std::forward<Args>(args)... };
    }

    template <typename... Args, size_t... Indices>
    constexpr
    auto apply(std::false_type, std::index_sequence<Indices...>, Args&&... args) && {
        using ParamsTuple = std::tuple<RestArgs...>;
        using ArgsTuple = std::tuple<Args...>;

        using RestArgsPrime = typename apply_args<ArgsTuple, ParamsTuple>::type;

        using CurryType = currying<FType, std::tuple<GivenArgs..., Args...>, RestArgsPrime>;

        return CurryType{ std::move(func), std::get<Indices>(std::move(given_args))..., std::forward<Args>(args)... };
    }

    template <typename... Args, size_t... Indices>
    constexpr
    auto apply(std::true_type, std::index_sequence<Indices...>, Args&&... args) const& {
        return func(std::get<Indices>(given_args)..., std::forward<Args>(args)...);
    }

    template <typename... Args, size_t... Indices>
    constexpr
    auto apply(std::true_type, std::index_sequence<Indices...>, Args&&... args) && {
        return func(std::get<Indices>(std::move(given_args))..., std::forward<Args>(args)...);
    }
};

// ---

template <typename FType, size_t... Indices>
constexpr
auto curry(FType&& func, std::index_sequence<Indices...>) {
    using RealFType = typename function_type<FType>::type;
    using FTypeTraits = function_traits<RealFType>;

    using CurryType = currying<FType, std::tuple<>, std::tuple<typename FTypeTraits::template arg_type<Indices>...>>;

    return CurryType{ std::move(func) };
}

template <typename FType>
constexpr
auto curry(FType&& func) {
    using RealFType = typename function_type<FType>::type;
    using FTypeArity = typename function_traits<RealFType>::arity;

    return curry(std::move(func), std::make_index_sequence<FTypeArity::value>{});
}

// ---

int main() {
    auto add = curry([](int a, int b) { return a + b; });

    std::cout << add(5)(10) << std::endl;
}

Здесь есть отличные ответы. Я подумал, что добавлю свой, потому что было весело поиграть с концепцией.

Приложение с частичной функцией: процесс «привязки» функции только к некоторым ее параметрам, откладывание остальных для заполнения позже. В результате получилась еще одна функция с меньшим количеством параметров.

Каррирование: это особая форма приложения частичной функции, в которой вы можете «привязать» только один аргумент за раз. В результате получается еще одна функция с ровно на 1 параметр меньше.

Код, который я собираюсь представить, - это приложение с частичной функцией, из которого каррирование возможно, но это не единственная возможность. Он предлагает несколько преимуществ по сравнению с вышеуказанными реализациями каррирования (в основном потому, что это частичное приложение функции, а не каррирование, хех).

  • Применение над пустой функцией:

    auto sum0 = [](){return 0;};
    std::cout << partial_apply(sum0)() << std::endl;
    
  • Применение нескольких аргументов одновременно:

    auto sum10 = [](int a, int b, int c, int d, int e, int f, int g, int h, int i, int j){return a+b+c+d+e+f+g+h+i+j;};
    std::cout << partial_apply(sum10)(1)(1,1)(1,1,1)(1,1,1,1) << std::endl; // 10
    
  • Поддержка constexpr, которая позволяет использовать static_assert во время компиляции:

    static_assert(partial_apply(sum0)() == 0);
    
  • Полезное сообщение об ошибке, если вы случайно зашли слишком далеко в предоставлении аргументов:

    auto sum1 = [](int x){ return x;};
    partial_apply(sum1)(1)(1);
    

    error: static_assert failed "Attempting to apply too many arguments!"

Другие ответы выше возвращают лямбды, которые связывают аргумент, а затем возвращают другие лямбды. Этот подход превращает эту важную функциональность в вызываемый объект. Определения для operator() позволяют вызывать внутреннюю лямбду. Вариативные шаблоны позволяют нам проверить, не зашел ли кто-то слишком далеко, а функция неявного преобразования в тип результата вызова функции позволяет нам распечатать результат или сравнить объект с примитивом.

Код:

namespace detail{
template<class F>
using is_zero_callable = decltype(std::declval<F>()());

template<class F>
constexpr bool is_zero_callable_v = std::experimental::is_detected_v<is_zero_callable, F>;
}

template<class F>
struct partial_apply_t
{
    template<class... Args>
    constexpr auto operator()(Args... args)
    {
        static_assert(sizeof...(args) == 0 || !is_zero_callable, "Attempting to apply too many arguments!");
        auto bind_some = [=](auto... rest) -> decltype(myFun(args..., rest...))
        {
           return myFun(args..., rest...);
        };
        using bind_t = decltype(bind_some);

        return partial_apply_t<bind_t>{bind_some};
    }
    explicit constexpr partial_apply_t(F fun) : myFun(fun){}

    constexpr operator auto()
    {
        if constexpr (is_zero_callable)
            return myFun();
        else
            return *this; // a callable
    }
    static constexpr bool is_zero_callable = detail::is_zero_callable_v<F>;
    F myFun;
};

Живая демонстрация

Еще несколько примечаний:

  • Я решил использовать is_detected в основном для удовольствия и практики; он служит здесь так же, как и обычный типовой признак.
  • Определенно можно было бы проделать больше работы для поддержки идеальной пересылки по соображениям производительности.
  • Код написан на C++ 17, поскольку для поддержки лямбда-выражения constexpr требуется в C++ 17.
    • И кажется, что GCC 7.0.1 еще не совсем там, поэтому я использовал Clang 5.0.0

Некоторые тесты:

auto sum0 = [](){return 0;};
auto sum1 = [](int x){ return x;};
auto sum2 = [](int x, int y){ return x + y;};
auto sum3 = [](int x, int y, int z){ return x + y + z; };
auto sum10 = [](int a, int b, int c, int d, int e, int f, int g, int h, int i, int j){return a+b+c+d+e+f+g+h+i+j;};

std::cout << partial_apply(sum0)() << std::endl; //0
static_assert(partial_apply(sum0)() == 0, "sum0 should return 0");
std::cout << partial_apply(sum1)(1) << std::endl; // 1
std::cout << partial_apply(sum2)(1)(1) << std::endl; // 2
std::cout << partial_apply(sum3)(1)(1)(1) << std::endl; // 3
static_assert(partial_apply(sum3)(1)(1)(1) == 3, "sum3 should return 3");
std::cout << partial_apply(sum10)(1)(1,1)(1,1,1)(1,1,1,1) << std::endl; // 10
//partial_apply(sum1)(1)(1); // fails static assert
auto partiallyApplied = partial_apply(sum3)(1)(1);
std::function<int(int)> finish_applying = partiallyApplied;
std::cout << std::boolalpha << (finish_applying(1) == 3) << std::endl; // true

auto plus2 = partial_apply(sum3)(1)(1);
std::cout << std::boolalpha << (plus2(1) == 3) << std::endl; // true
std::cout << std::boolalpha << (plus2(3) == 5) << std::endl; // true

Другие вопросы по теме