In C++14, what is a good way to curry functions or function objects?
In particular, I have an overloaded function foo
with some random number of overloads: some overloads may be found via ADL, others may be defined in a myriad of places.
I have a helper object:
static struct {
template<class...Args>
auto operator()(Args&&...args)const
-> decltype(foo(std::forward<Args>(args)...))
{ return (foo(std::forward<Args>(args)...));}
} call_foo;
that lets me pass the overload set around as a single object.
If I wanted to curry foo
, how should I do so?
As curry
and partial function application are often used interchangeably, by curry
I mean if foo(a,b,c,d)
is a valid call, then curry(call_foo)(a)(b)(c)(d)
must be a valid call.
Here is my current best attempt.
#include <iostream>
#include <utility>
#include <memory>
SFINAE utility helper class:
template<class T>struct voider{using type=void;};
template<class T>using void_t=typename voider<T>::type;
A traits class that tells you if Sig is a valid invokation -- ie, if std::result_of<Sig>::type
is defined behavior. In some C++ implementations simply checking std::result_of
is enough, but that isn't required by the standard:
template<class Sig,class=void>
struct is_invokable:std::false_type {};
template<class F, class... Ts>
struct is_invokable<
F(Ts...),
void_t<decltype(std::declval<F>()(std::declval<Ts>()...))>
>:std::true_type {
using type=decltype(std::declval<F>()(std::declval<Ts>()...));
};
template<class Sig>
using invoke_result=typename is_invokable<Sig>::type;
template<class T> using type=T;
Curry helper is sort of a manual lambda. It captures a function and one argument. It isn't written as a lambda so we can enable proper rvalue forwarding when it is used in an rvalue context, which is important when currying:
template<class F, class T>
struct curry_helper {
F f;
T t;
template<class...Args>
invoke_result< type<F const&>(T const&, Args...) >
operator()(Args&&...args)const&
{
return f(t, std::forward<Args>(args)...);
}
template<class...Args>
invoke_result< type<F&>(T const&, Args...) >
operator()(Args&&...args)&
{
return f(t, std::forward<Args>(args)...);
}
template<class...Args>
invoke_result< type<F>(T const&, Args...) >
operator()(Args&&...args)&&
{
return std::move(f)(std::move(t), std::forward<Args>(args)...);
}
};
The meat and potatoes:
template<class F>
struct curry_t {
F f;
template<class Arg>
using next_curry=curry_t< curry_helper<F, std::decay_t<Arg> >;
// the non-terminating cases. When the signature passed does not evaluate
// we simply store the value in a curry_helper, and curry the result:
template<class Arg,class=std::enable_if_t<!is_invokable<type<F const&>(Arg)>::value>>
auto operator()(Arg&& arg)const&
{
return next_curry<Arg>{{ f, std::forward<Arg>(arg) }};
}
template<class Arg,class=std::enable_if_t<!is_invokable<type<F&>(Arg)>::value>>
auto operator()(Arg&& arg)&
{
return next_curry<Arg>{{ f, std::forward<Arg>(arg) }};
}
template<class Arg,class=std::enable_if_t<!is_invokable<F(Arg)>::value>>
auto operator()(Arg&& arg)&&
{
return next_curry<Arg>{{ std::move(f), std::forward<Arg>(arg) }};
}
// These are helper functions that make curry(blah)(a,b,c) somewhat of a shortcut for curry(blah)(a)(b)(c)
// *if* the latter is valid, *and* it isn't just directly invoked. Not quite, because this can *jump over*
// terminating cases...
template<class Arg,class...Args,class=std::enable_if_t<!is_invokable<type<F const&>(Arg,Args...)>::value>>
auto operator()(Arg&& arg,Args&&...args)const&
{
return next_curry<Arg>{{ f, std::forward<Arg>(arg) }}(std::forward<Args>(args)...);
}
template<class Arg,class...Args,class=std::enable_if_t<!is_invokable<type<F&>(Arg,Args...)>::value>>
auto operator()(Arg&& arg,Args&&...args)&
{
return next_curry<Arg>{{ f, std::forward<Arg>(arg) }}(std::forward<Args>(args)...);
}
template<class Arg,class...Args,class=std::enable_if_t<!is_invokable<F(Arg,Args...)>::value>>
auto operator()(Arg&& arg,Args&&...args)&&
{
return next_curry<Arg>{{ std::move(f), std::forward<Arg>(arg) }}(std::forward<Args>(args)...);
}
// The terminating cases. If we run into a case where the arguments would evaluate, this calls the underlying curried function:
template<class... Args,class=std::enable_if_t<is_invokable<type<F const&>(Args...)>::value>>
auto operator()(Args&&... args,...)const&
{
return f(std::forward<Args>(args)...);
}
template<class... Args,class=std::enable_if_t<is_invokable<type<F&>(Args...)>::value>>
auto operator()(Args&&... args,...)&
{
return f(std::forward<Args>(args)...);
}
template<class... Args,class=std::enable_if_t<is_invokable<F(Args...)>::value>>
auto operator()(Args&&... args,...)&&
{
return std::move(f)(std::forward<Args>(args)...);
}
};
template<class F>
curry_t<typename std::decay<F>::type> curry( F&& f ) { return {std::forward<F>(f)}; }
The final function is humorously short.
Note that no type erasure is done. Also note that the theoretical hand-crafted solution can have far fewer move
s, but I don't think I needlessly copy anywhere.
Here is a test function object:
static struct {
double operator()(double x, int y, std::nullptr_t, std::nullptr_t)const{std::cout << "first\n"; return x*y;}
char operator()(char c, int x)const{std::cout << "second\n"; return c+x;}
void operator()(char const*s)const{std::cout << "hello " << s << "\n";}
} foo;
And some test code to see how it works:
int main() {
auto f = curry(foo);
// testing the ability to "jump over" the second overload:
std::cout << f(3.14,10,std::nullptr_t{})(std::nullptr_t{}) << "\n";
// Call the 3rd overload:
f("world");
// call the 2nd overload:
auto x = f('a',2);
std::cout << x << "\n";
// again:
x = f('a')(2);
}
live example
The resulting code is more than a bit of a mess. Lots of methods had to be repeated 3 times to handle &
, const&
and &&
cases optimally. The SFINAE clauses are long and complex. I ended up using both variardic args and varargs, with the varargs there to ensure a non-important signature difference in the method (and lower priority I think, not that it matters, the SFINAE ensures only one overload is ever valid, except this
qualifiers).
The result of curry(call_foo)
is an object that can be called one argument at a time, or many arguments at a time. You can call it with 3 arguments, then 1, then 1, then 2, and it does mostly the right thing. No evidence is exposed telling you how many arguments it wants, other than just trying to feed it arguments and seeing if the call is valid. This is required to handle overloading cases.
A quirk of the multiple-argument case is that it won't partially pass the packet to one curry
, and use the rest as arguments to the return value. I could change that relatively easily by changing:
return curry_t< curry_helper<F, std::decay_t<Arg> >>{{ f, std::forward<Arg>(arg) }}(std::forward<Args>(args)...);
to
return (*this)(std::forward<Arg>(arg))(std::forward<Args>(args)...);
and the other two similar ones. That would prevent the technique of "jumping over" an overload that would otherwise be valid. Thoughts? It would mean that curry(foo)(a,b,c)
would be logically identical to curry(foo)(a)(b)(c)
which seems elegant.
Thanks to @Casey whose answer inspired much of this.
Most recent revision. It makes (a,b,c)
behave much like (a)(b)(c)
unless it is call that works directly.
#include <type_traits>
#include <utility>
template<class...>
struct voider { using type = void; };
template<class...Ts>
using void_t = typename voider<Ts...>::type;
template<class T>
using decay_t = typename std::decay<T>::type;
template<class Sig,class=void>
struct is_invokable:std::false_type {};
template<class F, class... Ts>
struct is_invokable<
F(Ts...),
void_t<decltype(std::declval<F>()(std::declval<Ts>()...))>
>:std::true_type {};
#define RETURNS(...) decltype(__VA_ARGS__){return (__VA_ARGS__);}
template<class D>
class rvalue_invoke_support {
D& self(){return *static_cast<D*>(this);}
D const& self()const{return *static_cast<D const*>(this);}
public:
template<class...Args>
auto operator()(Args&&...args)&->
RETURNS( invoke( this->self(), std::forward<Args>(args)... ) )
template<class...Args>
auto operator()(Args&&...args)const&->
RETURNS( invoke( this->self(), std::forward<Args>(args)... ) )
template<class...Args>
auto operator()(Args&&...args)&&->
RETURNS( invoke( std::move(this->self()), std::forward<Args>(args)... ) )
template<class...Args>
auto operator()(Args&&...args)const&&->
RETURNS( invoke( std::move(this->self()), std::forward<Args>(args)... ) )
};
namespace curryDetails {
// Curry helper is sort of a manual lambda. It captures a function and one argument
// It isn't written as a lambda so we can enable proper rvalue forwarding when it is
// used in an rvalue context, which is important when currying:
template<class F, class T>
struct curry_helper: rvalue_invoke_support<curry_helper<F,T>> {
F f;
T t;
template<class A, class B>
curry_helper(A&& a, B&& b):f(std::forward<A>(a)), t(std::forward<B>(b)) {}
template<class curry_helper, class...Args>
friend auto invoke( curry_helper&& self, Args&&... args)->
RETURNS( std::forward<curry_helper>(self).f( std::forward<curry_helper>(self).t, std::forward<Args>(args)... ) )
};
}
namespace curryNS {
// the rvalue-ref qualified function type of a curry_t:
template<class curry>
using function_type = decltype(std::declval<curry>().f);
template <class> struct curry_t;
// the next curry type if we chain given a new arg A0:
template<class curry, class A0>
using next_curry = curry_t<::curryDetails::curry_helper<decay_t<function_type<curry>>, decay_t<A0>>>;
// 3 invoke_ overloads
// The first is one argument when invoking f with A0 does not work:
template<class curry, class A0>
auto invoke_(std::false_type, curry&& self, A0&&a0 )->
RETURNS(next_curry<curry, A0>{std::forward<curry>(self).f,std::forward<A0>(a0)})
// This is the 2+ argument overload where invoking with the arguments does not work
// invoke a chain of the top one:
template<class curry, class A0, class A1, class... Args>
auto invoke_(std::false_type, curry&& self, A0&&a0, A1&& a1, Args&&... args )->
RETURNS(std::forward<curry>(self)(std::forward<A0>(a0))(std::forward<A1>(a1), std::forward<Args>(args)...))
// This is the any number of argument overload when it is a valid call to f:
template<class curry, class...Args>
auto invoke_(std::true_type, curry&& self, Args&&...args )->
RETURNS(std::forward<curry>(self).f(std::forward<Args>(args)...))
template<class F>
struct curry_t : rvalue_invoke_support<curry_t<F>> {
F f;
template<class... U>curry_t(U&&...u):f(std::forward<U>(u)...){}
template<class curry, class...Args>
friend auto invoke( curry&& self, Args&&...args )->
RETURNS(invoke_(is_invokable<function_type<curry>(Args...)>{}, std::forward<curry>(self), std::forward<Args>(args)...))
};
}
template<class F>
curryNS::curry_t<decay_t<F>> curry( F&& f ) { return {std::forward<F>(f)}; }
#include <iostream>
static struct foo_t {
double operator()(double x, int y, std::nullptr_t, std::nullptr_t)const{std::cout << "first\n"; return x*y;}
char operator()(char c, int x)const{std::cout << "second\n"; return c+x;}
void operator()(char const*s)const{std::cout << "hello " << s << "\n";}
} foo;
int main() {
auto f = curry(foo);
using C = decltype((f));
std::cout << is_invokable<curryNS::function_type<C>(const char(&)[5])>{} << "\n";
invoke( f, "world" );
// Call the 3rd overload:
f("world");
// testing the ability to "jump over" the second overload:
std::cout << f(3.14,10,nullptr,nullptr) << "\n";
// call the 2nd overload:
auto x = f('a',2);
std::cout << x << "\n";
// again:
x = f('a')(2);
std::cout << x << "\n";
std::cout << is_invokable<decltype(foo)(double, int)>{} << "\n";
std::cout << is_invokable<decltype(foo)(double)>{} << "\n";
std::cout << is_invokable<decltype(f)(double, int)>{} << "\n";
std::cout << is_invokable<decltype(f)(double)>{} << "\n";
std::cout << is_invokable<decltype(f(3.14))(int)>{} << "\n";
decltype(std::declval<decltype((foo))>()(std::declval<double>(), std::declval<int>())) y = {3};
(void)y;
// std::cout << << "\n";
}
live version
Here's my attempt using eager semantics, i.e., returning as soon as enough arguments are accumulated for a valid call to the original function (Demo at Coliru):
namespace detail {
template <unsigned I>
struct priority_tag : priority_tag<I-1> {};
template <> struct priority_tag<0> {};
// High priority overload.
// If f is callable with zero args, return f()
template <typename F>
auto curry(F&& f, priority_tag<1>) -> decltype(std::forward<F>(f)()) {
return std::forward<F>(f)();
}
// Low priority overload.
// Return a partial application
template <typename F>
auto curry(F f, priority_tag<0>) {
return [f](auto&& t) {
return curry([f,t=std::forward<decltype(t)>(t)](auto&&...args) ->
decltype(f(t, std::forward<decltype(args)>(args)...)) {
return f(t, std::forward<decltype(args)>(args)...);
}, priority_tag<1>{});
};
}
} // namespace detail
// Dispatch to the implementation overload set in namespace detail.
template <typename F>
auto curry(F&& f) {
return detail::curry(std::forward<F>(f), detail::priority_tag<1>{});
}
and an alternate implementation without eager semantics that requires an extra ()
to invoke the partial application making it possible to access e.g. both f(int)
and f(int, int)
from the same overload set:
template <typename F>
class partial_application {
F f_;
public:
template <typename T>
explicit partial_application(T&& f) :
f_(std::forward<T>(f)) {}
auto operator()() { return f_(); }
template <typename T>
auto operator()(T&&);
};
template <typename F>
auto curry_explicit(F&& f) {
return partial_application<F>{std::forward<F>(f)};
}
template <typename F>
template <typename T>
auto partial_application<F>::operator()(T&& t) {
return curry_explicit([f=f_,t=std::forward<T>(t)](auto&&...args) ->
decltype(f_(t, std::forward<decltype(args)>(args)...)) {
return f(t, std::forward<decltype(args)>(args)...);
});
}
This isn't a trivial problem. Getting ownership semantics right is tricky. For comparison, let's consider a few lambdas and how they express ownership:
[=]() {} // capture by value, can't modify values of captures
[=]() mutable {} // capture by value, can modify values of captures
[&]() {} // capture by reference, can modify values of captures
[a, &b, c = std::move(foo)]() {} // mixture of value and reference, move-only if foo is
// can't modify value of a or c, but can b
My implementation by default captures by value, captures by reference when passed a std::reference_wrapper<>
(same behavior as std::make_tuple()
with std::ref()
), and forwards the invoking arguments as is (lvalues remain lvalues, rvalues remain rvalues). I couldn't decide on a satisfactory solution for mutable
, so all value captures are effectively const
.
Capture of a move-only type makes the functor move-only. This in turn means if c
is a curry_t
and d
is a move-only type, and c(std::move(d))
doesn't invoke the captured functor, then binding c(std::move(d))
to a lvalue means subsequent calls either have to contain enough arguments to invoke the captured functor, or the lvalue must be converted to an rvalue (possibly via std::move()
). This took some care with reference qualifiers. Keep in mind that *this
is always an lvalue, so the ref-qualifiers had to be applied to operator()
.
There's no way to know how many arguments the captured functor needs as there can be any number of overloads, so be careful. No static_assert
s that sizeof...(Captures) < max(N_ARGS)
.
Overall, the implementation takes about 70 lines of code. As discussed in the comments, I followed the convention of curry(foo)(a, b, c, d)
and foo(a, b, c, d)
being (roughly) equivalent, allowing access to every overload.
#include <cstddef>
#include <functional>
#include <type_traits>
#include <tuple>
#include <utility>
template<typename Functor>
auto curry(Functor&& f);
namespace curry_impl
{
/* helper: typing using type = T; is tedious */
template<typename T> struct identity { using type = T; };
/* helper: SFINAE magic :) */
template<typename...> struct void_t_impl : identity<void> {};
template<typename... Ts> using void_t = typename void_t_impl<Ts...>::type;
/* helper: true iff F(Args...) is invokable */
template<typename Signature, typename = void> struct is_invokable : std::false_type {};
template<typename F, typename... Args> struct is_invokable<F(Args...), void_t<decltype(std::declval<F>()(std::declval<Args>()...))>> : std::true_type {};
/* helper: unwraps references wrapped by std::ref() */
template<typename T> struct unwrap_reference : identity<T> {};
template<typename T> struct unwrap_reference<std::reference_wrapper<T>> : identity<T&> {};
template<typename T> using unwrap_reference_t = typename unwrap_reference<T>::type;
/* helper: same transformation as applied by std::make_tuple() *
* decays to value type unless wrapped with std::ref() *
* use: modeling value & reference captures *
* e.g. c(a) vs c(std::ref(a)) */
template<typename T> struct decay_reference : unwrap_reference<std::decay_t<T>> {};
template<typename T> using decay_reference_t = typename decay_reference<T>::type;
/* helper: true iff all template arguments are true */
template<bool...> struct all : std::true_type {};
template<bool... Booleans> struct all<false, Booleans...> : std::false_type {};
template<bool... Booleans> struct all<true, Booleans...> : all<Booleans...> {};
/* helper: std::move(u) iff T is not an lvalue *
* use: save on copies when curry_t is an rvalue *
* e.g. c(a)(b)(c) should only copy functor, a, b, etc. once */
template<bool = false> struct move_if_value_impl { template<typename U> auto&& operator()(U&& u) { return std::move(u); } };
template<> struct move_if_value_impl<true> { template<typename U> auto& operator()(U& u) { return u; } };
template<typename T, typename U> auto&& move_if_value(U&& u) { return move_if_value_impl<std::is_lvalue_reference<T>{}>{}(std::forward<U>(u)); }
/* the curry wrapper/functor */
template<typename Functor, typename... Captures>
struct curry_t {
/* unfortunately, ref qualifiers don't change *this (always lvalue), *
* so qualifiers have to be on operator(), *
* even though only invoke_impl(std::false_type, ...) needs qualifiers */
#define OPERATOR_PARENTHESES(X, Y) \
template<typename... Args> \
auto operator()(Args&&... args) X { \
return invoke_impl_##Y(is_invokable<Functor(Captures..., Args...)>{}, std::index_sequence_for<Captures...>{}, std::forward<Args>(args)...); \
}
OPERATOR_PARENTHESES(&, lv)
OPERATOR_PARENTHESES(&&, rv)
#undef OPERATOR_PARENTHESES
Functor functor;
std::tuple<Captures...> captures;
private:
/* tag dispatch for when Functor(Captures..., Args...) is an invokable expression *
* see above comment about duplicate code */
#define INVOKE_IMPL_TRUE(X) \
template<typename... Args, std::size_t... Is> \
auto invoke_impl_##X(std::true_type, std::index_sequence<Is...>, Args&&... args) { \
return functor(std::get<Is>(captures)..., std::forward<Args>(args)...); \
}
INVOKE_IMPL_TRUE(lv)
INVOKE_IMPL_TRUE(rv)
#undef INVOKE_IMPL_TRUE
/* tag dispatch for when Functor(Capture..., Args...) is NOT an invokable expression *
* lvalue ref qualifier version copies all captured values/references */
template<typename... Args, std::size_t... Is>
auto invoke_impl_lv(std::false_type, std::index_sequence<Is...>, Args&&... args) {
static_assert(all<std::is_copy_constructible<Functor>{}, std::is_copy_constructible<Captures>{}...>{}, "all captures must be copyable or curry_t must an rvalue");
return curry_t<Functor, Captures..., decay_reference_t<Args>...>{
functor,
std::tuple<Captures..., decay_reference_t<Args>...>{
std::get<Is>(captures)...,
std::forward<Args>(args)...
}
};
}
/* tag dispatch for when F(As..., Args...) is NOT an invokable expression *
* rvalue ref qualifier version moves all captured values, copies all references */
template<typename... Args, std::size_t... Is>
auto invoke_impl_rv(std::false_type, std::index_sequence<Is...>, Args&&... args) {
return curry_t<Functor, Captures..., decay_reference_t<Args>...>{
move_if_value<Functor>(functor),
std::tuple<Captures..., decay_reference_t<Args>...>{
move_if_value<Captures>(std::get<Is>(captures))...,
std::forward<Args>(args)...
}
};
}
};
}
/* helper function for creating curried functors */
template<typename Functor>
auto curry(Functor&& f) {
return curry_impl::curry_t<curry_impl::decay_reference_t<Functor>>{std::forward<Functor>(f), {}};
}
Live demo on Coliru demonstrating reference qualifier semantics.
Here is code i have been playing with while learning variadic templates.
It is toy example for ATD for function pointers, and ATD on std::function.
I have made example on lambdas, but havent find way to extract aruments, so no ATD for lamnda (yet)
#include <iostream>
#include <tuple>
#include <type_traits>
#include <functional>
#include <algorithm>
//this is helper class for calling (variadic) func from std::tuple
template <typename F,typename ...Args>
struct TupleCallHelper
{
template<int ...> struct seq {};
template<int N, int ...S> struct gens : gens<N-1, N-1, S...> {};
template<int ...S> struct gens<0, S...>{ typedef seq<S...> type; };
template<int ...S>
static inline auto callFunc(seq<S...>,std::tuple<Args...>& params, F f) ->
decltype(f(std::get<S>(params) ...))
{
return f(std::get<S>(params) ... );
}
static inline auto delayed_dispatch(F& f, std::tuple<Args... >& args) ->
decltype(callFunc(typename gens<sizeof...(Args)>::type(),args , f))
{
return callFunc(typename gens<sizeof...(Args)>::type(),args , f);
}
};
template <int Depth,typename F,typename ... Args>
struct CurriedImpl;
//curried base class, when all args are consumed
template <typename F,typename ... Args>
struct CurriedImpl<0,F,Args...>
{
std::tuple<Args...> m_tuple;
F m_func;
CurriedImpl(const F& a_func):m_func(a_func)
{
}
auto operator()() ->
decltype(TupleCallHelper<F,Args...>::delayed_dispatch(m_func,m_tuple))
{
return TupleCallHelper<F,Args...>::delayed_dispatch(m_func,m_tuple);
}
};
template <typename F,typename ... Args>
struct CurriedImpl<-1,F,Args ... > ; //this is against weird gcc bug
//curried before all args are consumed (stores arg in tuple)
template <int Depth,typename F,typename ... Args>
struct CurriedImpl : public CurriedImpl<Depth-1,F,Args...>
{
using parent_t = CurriedImpl<Depth-1,F,Args...>;
CurriedImpl(const F& a_func): parent_t(a_func)
{
}
template <typename First>
auto operator()(const First& a_first) -> CurriedImpl<Depth-1,F,Args...>
{
std::get<sizeof...(Args)-Depth>(parent_t::m_tuple) = a_first;
return *this;
}
template <typename First, typename... Rem>
auto operator()(const First& a_first,Rem ... a_rem) ->
CurriedImpl<Depth-1-sizeof...(Rem),F,Args...>
{
CurriedImpl<Depth-1,F,Args...> r = this->operator()(a_first);
return r(a_rem...);
}
};
template <typename F, typename ... Args>
struct Curried: public CurriedImpl<sizeof...(Args),F,Args...>
{
Curried(F a_f):
CurriedImpl<sizeof...(Args),F,Args...>(a_f)
{
}
};
template<typename A>
int varcout( A a_a)
{
std::cout << a_a << "\n" ;
return 0;
}
template<typename A,typename ... Var>
int varcout( A a_a, Var ... a_var)
{
std::cout << a_a << "\n" ;
return varcout(a_var ...);
}
template <typename F, typename ... Args>
auto curried(F(*a_f)(Args...)) -> Curried<F(*)(Args...),Args...>
{
return Curried<F(*)(Args...),Args...>(a_f);
}
template <typename R, typename ... Args>
auto curried(std::function<R(Args ... )> a_f) -> Curried<std::function<R(Args ... )>,Args...>
{
return Curried<std::function<R(Args ... )>,Args...>(a_f);
}
int main()
{
//function pointers
auto f = varcout<int,float>;
auto curried_funcp = curried(f);
curried_funcp(1)(10)();
//atd for std::function
std::function<int(int,float)> fun(f);
auto curried_func = curried(fun);
curried_func(2)(5)();
curried_func(2,5)();//works too
//example with std::lambda using Curried
auto lamb = [](int a , int b , std::string& s){
std::cout << a + b << s ;
};
Curried<decltype(lamb),int,int,std::string> co(lamb);
auto var1 = co(2);
auto var2 = var1(1," is sum\n");
var2(); //prints -> "3 is sum"
}