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





Короче говоря, каррирование принимает функцию 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;
}
Хотел бы я проголосовать за комментарий. Эта цепочка комментариев забавна. :)
На самом деле, я думаю, что это неправильный ответ; в этом ответе описывается «частичное приложение», которое выполняют функции bind. «Каррирование» - это процесс преобразования функции, принимающей N аргументов, в функцию, принимающую один аргумент и возвращающую функцию с N-1 аргументами, например void (*)(int, short, bool) становится X (*)(int), а X - void (*)(short, bool). Смотрите в haskell.org/haskellwiki/Currying все, что вы когда-либо хотели знать о каррировании и частичном применении функций.
@FrerichRaabe Вы совершенно правы; но цель каррирования - добиться (легкого) частичного применения.
@Marcin: Я нашел этот вопрос, потому что мне нужен карри, а не какое-либо частичное приложение. Разница в том, что когда у вас есть функция с 6 аргументами, тщательно написанными так, что вам нужно исправить первый аргумент, вы не хотите помещать туда все заполнители!
Каррирование - это способ сокращения функции, которая принимает несколько аргументов, до последовательности вложенных функций с одним аргументом каждая:
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++. да, но только для унарных и бинарных функций ...
а сломанные связыватели C++ 03 (текущий C++) не работают с функциями, имеющими ссылочные параметры. Они просто не справляются с проблемой ссылки на ссылку. буст-связующие намного умнее
@ugasoft B-но выполнение унарной операции похоже на разбавление вина в сброженном виноградном соке смущенный
Взгляните на 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 в качестве второго аргумента.
Я не могу рекомендовать Boost.Bind достаточно - изучите его, используйте, полюбите !!
Это не карри. Это частичное приложение.
@Thomas Вы, конечно, совершенно правы. На самом деле, меня очень раздражает, что R-сообщество использует неправильную терминологию для него. Мой ответ на самом деле не был попыткой продать это как каррирование, а просто показал лучший способ реализации принятого ответа. при этом я обновил свой ответ, чтобы прояснить это.
Разница между каррированием и частичным применением: частичное приложение принимает такую функцию, как 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).
Таким образом, каррирование дает нам «частичную фабрику приложений». Мы можем выбрать какое-нибудь значение, например 2, а затем написать h (2). Это даст нам функцию g (y), которая вызывает f (2, y): параметр x был частично применен. Мы каррировали ж, чтобы получить час, а затем вызвали час с определенным значением, чтобы вызвать частичное применение ж.
Упрощая пример Грегга, используя 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.
}
Надеюсь, это поможет.
Да ... это НЕ карри.
Другие ответы хорошо объясняют связующие, поэтому я не буду повторять эту часть здесь. Я только продемонстрирую, как каррирование и частичное применение могут быть выполнены с лямбдами в 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, потому что частичная оценка функции по-прежнему полезна (и часто то, что люди подразумевают под «карри»), и из-за того, насколько она жесткая. Вы можете повысить производительность с помощью гораздо большего количества кода и идеальной пересылки.
... или с небольшим количеством кода и перемещением аргументов из внешней "фабричной" функции в возвращенную лямбду! ... или, если известно, что они всегда немодифицированы, просто возьмите константные ссылки и не нужно ничего копировать или перемещать. Тем не менее, до C++ 20 было немного больно перемещать вариативный пакет аргументов в лямбда: до этого вы должны спрятать их в tuple, а затем в apply.
@underscore_d C++ 20 уже здесь; можно ли улучшить этот ответ сейчас?
Каррирование просто означает преобразование функции нескольких аргументов в функцию одного аргумента. Проще всего это проиллюстрировать на примере:
Возьмем функцию 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).
Следующее немного сложнее, но для меня работает очень хорошо (с использованием 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...> с одним аргументом шаблона меньше.
Мне только что пришла в голову новая идея подойти к проблеме каррирования ... С введением 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 не просто запрашивал код, он просил объяснений!
+1 Это первый ответ, который на самом деле предлагает решение вопроса «Как каррирование выполняется в C++?». Как указано в комментариях к некоторым другим ответам, общее каррирование (как здесь реализовано) отличается от частичного применения конечного числа конкретных аргументов.
проверьте также ответ Люка Дантона в Частичное приложение с лямбдой C++?, который также обеспечивает красивую и, возможно, несколько более полную реализацию в отношении частичного применения.
Вот ответ, который я ищу: реализация каррирования с использованием вариативных шаблонов функций.
Не используйте двойные подчеркивания в коде C++ (если вы не являетесь поставщиком компилятора). Это просто незаконно.
@ThomasEding Разве это не законно, если вы не начинаете с двойного подчеркивания?
@ThomasEding: Спасибо за комментарий! На самом деле я этого не знал, так как он отлично скомпилировался ...
@Poldie: нельзя начинать идентификатор с подчеркивания, за которым следует заглавная буква.
@ThomasEding Или числовую цифру.
@Julian У меня есть код карри, который в основном основан на вашем коде: nvwa.cvs.sourceforge.net/viewvc/nvwa/nvwa/…. Если вы можете прислать мне свое имя, я могу добавить его в качестве кредита. Ваши комментарии также будут приветствоваться. Вы можете связаться со мной по адресу wuyongwei AT GMail.
std::void_t также является C++ 17, но может быть заменен: en.cppreference.com/w/cpp/types/void_t
Без каких-либо объяснений это совершенно бесполезно.
Я также реализовал каррирование с помощью вариативных шаблонов (см. Ответ Джулиана). Однако я не использовал рекурсию или 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;
};
Еще несколько примечаний:
constexpr требуется в C++ 17.
Некоторые тесты:
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
Marcin: raj запросил пример C++, прежде чем я его добавил. :)