在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)
必须是有效通话。< / p>
答案 0 :(得分:11)
这是我目前最好的尝试。
#include <iostream>
#include <utility>
#include <memory>
SFINAE实用程序帮助程序类:
template<class T>struct voider{using type=void;};
template<class T>using void_t=typename voider<T>::type;
一个traits类,它告诉你Sig是否是一个有效的invokation - 即,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 helper是一种手动lambda。它捕获一个函数和一个参数。它不是作为lambda编写的,因此我们可以在rvalue上下文中使用正确的rvalue转发,这在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)...);
}
};
肉和土豆:
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
,但我认为我不必在任何地方进行复制。
这是一个测试函数对象:
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;
以及一些测试代码,看看它是如何工作的:
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);
}
结果代码不仅仅是一团糟。许多方法必须重复3次才能最佳地处理&
,const&
和&&
个案例。 SFINAE条款冗长而复杂。我最终使用variardic args 和 varargs,其中的varargs确保方法中的非重要签名差异(我认为优先级较低,而不是重要,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)...);
和另外两个相似的。这将阻止&#34;跳过&#34;否则本身有效的重载。思考?这意味着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()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";
}
答案 1 :(得分:7)
这是我尝试使用急切语义,即,只要为原始函数(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)...);
});
}
答案 2 :(得分:4)
这不是一个小问题。获得所有权语义是很棘手的。为了比较,让我们考虑一些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()
的{{1}}相同的行为),并按原样转发调用参数(左值保持左值) ,rvalues仍然是rvalues)。我无法确定std::ref()
的令人满意的解决方案,因此所有值捕获都有效地 mutable
。
捕获仅移动类型使得仿函数仅移动。这反过来意味着const
是c
且curry_t
是仅移动类型,d
不会调用捕获的仿函数,然后绑定{{1} lvalue意味着后续调用必须包含足够的参数来调用捕获的仿函数,或者左值必须转换为rvalue(可能通过c(std::move(d))
)。这需要一些参考资格。请注意,c(std::move(d))
始终是左值,因此必须将参考限定符应用于std::move()
。
由于可能存在任意数量的重载,因此无法知道捕获的仿函数需要多少个参数,因此请小心。没有*this
的{{1}}。
总的来说,实现大约需要70行代码。正如评论中所讨论的那样,我遵循operator()
和static_assert
约定(大致)等同,允许访问每个重载。
sizeof...(Captures) < max(N_ARGS)
Live demo on Coliru演示了引用限定符语义。
答案 3 :(得分:0)
这是我在学习可变参数模板时一直在玩的代码 它是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"
}