我应该如何制作功能咖喱?
Posted
技术标签:
【中文标题】我应该如何制作功能咖喱?【英文标题】:How should I make function curry? 【发布时间】:2014-12-26 15:40:01 【问题描述】:在 C++14 中,柯里化函数或函数对象的好方法是什么?
特别是,我有一个重载函数foo
,其中包含一些随机数量的重载:一些重载可以通过 ADL 找到,其他的可能在无数地方定义。
我有一个辅助对象:
static struct
template<class...Args>
auto operator()(Args&&...args)const
-> decltype(foo(std::forward<Args>(args)...))
return (foo(std::forward<Args>(args)...));
call_foo;
这让我可以将重载集作为单个对象传递。
如果我想咖喱foo
,我该怎么做?
由于curry
和部分函数应用程序经常互换使用,curry
我的意思是如果foo(a,b,c,d)
是一个有效的调用,那么curry(call_foo)(a)(b)(c)(d)
必须是一个有效的调用。
【问题讨论】:
这可能吗?考虑有两个函数void foo(int)
、void foo(int, int)
。 curry(call_foo)(5)
一定是什么。它要么必须评估第一个重载,要么创建一个等待另一个参数的临时文件。在第一种情况下curry(call_foo)(5)(6)
失败,在后一种情况下不可能调用第一个重载void foo(int)
。不过,我很好奇。
如果foo(1,2)
和foo(1)
都有效,你如何确定curry(call_foo)(1)
返回什么?我猜你必须要求所有的重载对于每个参数子集都是明确的?
@MarkusMayr 好问题。由于我们使用的是一种重载语言,我们是否需要尾随 ()
来调用参数?这有时会让事情变得尴尬。也许它应该返回一个表达式模板,它只在用于产生值时进行评估?那么我们应该如何处理预柯里化函数呢?
@MarkusMayr 实际上,从技术上讲,对于void
情况,表达式模板可以在自我毁灭时调用,并且永远不会被强制转换为其他东西。 :/ 但可能很傻。
@tclamb 所以我认为 uncurry 是不需要的听起来对吗?
【参考方案1】:
这是我目前最好的尝试。
#include <iostream>
#include <utility>
#include <memory>
SFINAE 实用程序助手类:
template<class T>struct voiderusing type=void;;
template<class T>using void_t=typename voider<T>::type;
一个特征类,它告诉你 Sig 是否是一个有效的调用——即,如果std::result_of<Sig>::type
是定义的行为。在某些 C++ 实现中,只需检查 std::result_of
就足够了,但这不是标准要求的:
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 助手是一种手动 lambda。它捕获一个函数和一个参数。它没有写成 lambda,因此我们可以在右值上下文中使用它时启用正确的右值转发,这在柯里化时很重要:
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)...);
;
肉和土豆:
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);
最后的功能很幽默。
请注意,没有进行类型擦除。另请注意,理论上手工制作的解决方案可以少得多move
s,但我认为我不需要在任何地方复制。
这是一个测试函数对象:
static struct
double operator()(double x, int y, std::nullptr_t, std::nullptr_t)conststd::cout << "first\n"; return x*y;
char operator()(char c, int x)conststd::cout << "second\n"; return c+x;
void operator()(char const*s)conststd::cout << "hello " << s << "\n";
foo;
还有一些测试代码来看看它是如何工作的:
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
生成的代码有点混乱。许多方法必须重复 3 次才能以最佳方式处理 &
、const&
和 &&
案例。 SFINAE 条款冗长而复杂。我最终使用了可变参数 和 可变参数,其中可变参数确保方法中不重要的签名差异(我认为优先级较低,并不重要,SFINAE 确保只有一个重载永远有效,this
限定符除外)。
curry(call_foo)
的结果是一个对象,一次可以调用一个参数,也可以一次调用多个参数。你可以用 3 个参数调用它,然后是 1,然后是 1,然后是 2,而且它大部分都是正确的。除了尝试向它提供参数并查看调用是否有效之外,没有任何证据可以告诉您它需要多少参数。这是处理重载情况所必需的。
多参数情况的一个怪癖是它不会将数据包部分传递给一个curry
,而是将其余部分用作返回值的参数。我可以通过更改相对轻松地更改:
return curry_t< curry_helper<F, std::decay_t<Arg> >> f, std::forward<Arg>(arg) (std::forward<Args>(args)...);
到
return (*this)(std::forward<Arg>(arg))(std::forward<Args>(args)...);
还有另外两个类似的。这将阻止“跳过”原本有效的重载的技术。想法?这意味着curry(foo)(a,b,c)
在逻辑上与curry(foo)(a)(b)(c)
相同,这看起来很优雅。
感谢@Casey,他的回答激发了很多灵感。
最新版本。它使(a,b,c)
的行为与(a)(b)(c)
非常相似,除非它是直接起作用的调用。
#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()constreturn *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)conststd::cout << "first\n"; return x*y;
char operator()(char c, int x)conststd::cout << "second\n"; return c+x;
void operator()(char const*s)conststd::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
【讨论】:
@casey 完美转发()
以释放invoke
并具有适当的r/l 值*this
并在那里工作?都喜欢吗?并且可能使用 crtp 编写一次。
Made a github repo with the code as I'm leaving it。我鄙视宏,但还是做了一个来减少-> decltype(a_really_long_expression) return a_really_long_expression;
的重复。 Now compiles as C++11.
@casey 这就是我所说的 CRTP 技巧的意思:coliru.stacked-crooked.com/a/3ba6d0b0682acffb——它节省了一些样板文件。我也尝试在一个子句中执行 enable_if,但您的标签调度看起来更干净。看看forward_member
,它使从对象向成员传递右值变得容易。
Here is the code with forward_member
elided and tag dispatch for curry_t::invoke
.。我认为消除enable_if
使其更易于理解。
@Casey 添加到帖子中。你为什么要拆分invoke
的声明和实现?通过将它们放在一起来摆脱一些样板。【参考方案2】:
这是我使用热切语义的尝试,即,一旦积累了足够的参数以对原始函数进行有效调用 (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>);
还有一个没有急切语义的替代实现,它需要额外的()
来调用部分应用程序,从而可以访问例如f(int)
和 f(int, int)
都来自同一个重载集:
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)...);
);
【讨论】:
[f=std::forward<F>(f),t=std::forward<decltype(t)>(t)]
可能效率更高一些?
@Yakk 是的,自发布此消息以来,我一直在尝试消除副本。 forward
-capturing t
is fine,但事情 seem to blow up when I forward
-capture f
。我讨厌直接跳到“编译器错误”,但代码对我来说似乎很明智。
你不能安全地转发它两次。如果在右值上下文中调用了外部 lambda,则内部 lambda 的 f=f
只能是 f=std::move(f)
,而您无法在 lambda 中确定。
取一个look at this -- 我将 lambdas 制作成对象,并根据调用对象的右值性支持细粒度的move
。这让我们可以移动函数对象而不是在许多情况下复制它......(它还允许您使用可变数量的参数进行“最终调用”。使其适用于可变数量的咖喱更棘手(需要弄乱tuple
s) 但应该是可行的。)理想情况下,如果 arg 超过 1 个,我们会找到最长的 args 序列来调用,然后在 retval 上尝试剩余的 args
@Yakk 我决定正确处理移动语义的唯一方法是使函子显式而不是 lambdas,并决定这比我愿意投入的工作更多。您应该发布that 作为答案。【参考方案3】:
这不是一个小问题。使所有权语义正确是棘手的。为了比较,让我们考虑几个 lambda 以及它们如何表达所有权:
[=]() // 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
默认情况下,我的实现按值捕获,在传递std::reference_wrapper<>
时按引用捕获(与std::make_tuple()
和std::ref()
的行为相同),并按原样转发调用参数(左值仍然是左值,右值仍然是右值)。我无法为mutable
确定一个令人满意的解决方案,所以所有的价值捕获都有效 const
。
只移动类型的捕获使函子只移动。这反过来意味着如果c
是curry_t
并且d
是仅移动类型,并且c(std::move(d))
不调用捕获的函子,那么将c(std::move(d))
绑定到左值意味着后续调用要么必须包含足够的参数来调用捕获的函子,或者必须将左值转换为右值(可能通过std::move()
)。这需要注意参考限定符。请记住,*this
始终是左值,因此必须将引用限定符应用于 operator()
。
没有办法知道捕获的函子需要多少个参数,因为可能有任意数量的重载,所以要小心。没有static_assert
s 那个sizeof...(Captures) < max(N_ARGS)
。
总体而言,实现需要大约 70 行代码。正如 cmets 中所讨论的,我遵循了 curry(foo)(a, b, c, d)
和 foo(a, b, c, d)
(大致)等效的约定,允许访问每个重载。
#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 演示引用限定符语义。
【讨论】:
很好的std::ref
支持。据我所知,“一次递归和取消一个论点”解决方案(上图)也解决了仅移动论点问题?我们还可以存储std::ref
s 并在调用时将它们解包,而不是将它们也转换为存储引用。
您发布的答案正是如此。由于您在存储参数时会衰减参数,因此reference_wrapper<>&&
会衰减为... reference_wrapper<>
。 :) 当你最终调用时,operator T&()
隐式地将reference_wrapper<T>
转换为它所包含的T&
。
标准中没有任何内容声明sizeof(reference_wrapper<T>) == sizeof(T&)
,所以我自己对这样做有点谨慎。但是,通过将move_if_value()
和decay_reference<>
替换为无条件的std::move()
和std::decay_t<>
,它确实节省了代码和复杂性。想法?
GCC 似乎对通过您的实现优化咖喱机制感到窒息。比较生成的 yours 和 mine 的程序集,两者都在 -O3。【参考方案4】:
这是我在学习可变参数模板时一直在使用的代码。 这是用于函数指针的 ATD 和 std::function 上的 ATD 的玩具示例。 我已经在 lambdas 上做了例子,但还没有找到提取参数的方法,所以 lamnda 还没有 ATD
#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"
【讨论】:
看上面,它与函数指针一起工作。所以不支持重载函数或者函数对象之类的? 实现是在模板化的struct Curried
中创建的,它采用通用函子。例如,curried
函数是函数指针的 ATD 重载并返回 Curried
object。一般来说,您只需要提供新的/重载的curried
即可实现 ATD。这个实现也非常轻量级,因为它将参数存储在元组 (POD) 中,并且不使用任何特殊的内存容器。
我为 std::function 和 lambda 做了例子
这些案例都有明确的参数列表。它如何处理具有多个operator()
重载的函数对象?
Curried
采用第一个模板参数仿函数(函数对象),然后是参数类型。它可以推断出 wwitch operator() 从提供的模板参数中调用。以上是关于我应该如何制作功能咖喱?的主要内容,如果未能解决你的问题,请参考以下文章