- mongodb - 在 MongoDB mapreduce 中,如何展平值对象?
- javascript - 对象传播与 Object.assign
- html - 输入类型 ="submit"Vs 按钮标签它们可以互换吗?
- sql - 使用 MongoDB 而不是 MS SQL Server 的优缺点
在 C++14 中,柯里化(Currying)函数或函数对象的好方法是什么?
特别是,我有一个重载函数 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;
这让我可以将重载集作为单个对象传递。
如果我想 curry foo
,我应该怎么做?
由于 curry
和部分函数应用程序经常可以互换使用,curry
我的意思是如果 foo(a,b,c,d)
是一个有效的调用,那么 curry(call_foo)(a)(b)(c)(d)
必须是一个有效的调用。
最佳答案
这是我目前最好的尝试。
#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;
一个特征类,它告诉你 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,因此我们可以在右值上下文中使用它时启用正确的右值转发,这在柯里化(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)...);
}
};
肉和土 bean :
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)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 条款冗长而复杂。我最终同时使用了可变参数 和 可变参数,其中可变参数确保方法中不重要的签名差异(我认为优先级较低,并不重要,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()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";
}
关于c++ - 我应该如何制作功能 curry ?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/26655685/
#include using namespace std; class C{ private: int value; public: C(){ value = 0;
这个问题已经有答案了: What is the difference between char a[] = ?string?; and char *p = ?string?;? (8 个回答) 已关闭
关闭。此题需要details or clarity 。目前不接受答案。 想要改进这个问题吗?通过 editing this post 添加详细信息并澄清问题. 已关闭 7 年前。 此帖子已于 8 个月
除了调试之外,是否有任何针对 c、c++ 或 c# 的测试工具,其工作原理类似于将独立函数复制粘贴到某个文本框,然后在其他文本框中输入参数? 最佳答案 也许您会考虑单元测试。我推荐你谷歌测试和谷歌模拟
我想在第二台显示器中移动一个窗口 (HWND)。问题是我尝试了很多方法,例如将分辨率加倍或输入负值,但它永远无法将窗口放在我的第二台显示器上。 关于如何在 C/C++/c# 中执行此操作的任何线索 最
我正在寻找 C/C++/C## 中不同类型 DES 的现有实现。我的运行平台是Windows XP/Vista/7。 我正在尝试编写一个 C# 程序,它将使用 DES 算法进行加密和解密。我需要一些实
很难说出这里要问什么。这个问题模棱两可、含糊不清、不完整、过于宽泛或夸夸其谈,无法以目前的形式得到合理的回答。如需帮助澄清此问题以便重新打开,visit the help center . 关闭 1
有没有办法强制将另一个 窗口置于顶部? 不是应用程序的窗口,而是另一个已经在系统上运行的窗口。 (Windows, C/C++/C#) 最佳答案 SetWindowPos(that_window_ha
假设您可以在 C/C++ 或 Csharp 之间做出选择,并且您打算在 Windows 和 Linux 服务器上运行同一服务器的多个实例,那么构建套接字服务器应用程序的最明智选择是什么? 最佳答案 如
你们能告诉我它们之间的区别吗? 顺便问一下,有什么叫C++库或C库的吗? 最佳答案 C++ 标准库 和 C 标准库 是 C++ 和 C 标准定义的库,提供给 C++ 和 C 程序使用。那是那些词的共同
下面的测试代码,我将输出信息放在注释中。我使用的是 gcc 4.8.5 和 Centos 7.2。 #include #include class C { public:
很难说出这里问的是什么。这个问题是含糊的、模糊的、不完整的、过于宽泛的或修辞性的,无法以目前的形式得到合理的回答。如需帮助澄清此问题以便重新打开它,visit the help center 。 已关
我的客户将使用名为 annoucement 的结构/类与客户通信。我想我会用 C++ 编写服务器。会有很多不同的类继承annoucement。我的问题是通过网络将这些类发送给客户端 我想也许我应该使用
我在 C# 中有以下函数: public Matrix ConcatDescriptors(IList> descriptors) { int cols = descriptors[0].Co
我有一个项目要编写一个函数来对某些数据执行某些操作。我可以用 C/C++ 编写代码,但我不想与雇主共享该函数的代码。相反,我只想让他有权在他自己的代码中调用该函数。是否可以?我想到了这两种方法 - 在
我使用的是编写糟糕的第 3 方 (C/C++) Api。我从托管代码(C++/CLI)中使用它。有时会出现“访问冲突错误”。这使整个应用程序崩溃。我知道我无法处理这些错误[如果指针访问非法内存位置等,
关闭。这个问题不符合Stack Overflow guidelines .它目前不接受答案。 我们不允许提问寻求书籍、工具、软件库等的推荐。您可以编辑问题,以便用事实和引用来回答。 关闭 7 年前。
已关闭。此问题不符合Stack Overflow guidelines 。目前不接受答案。 要求我们推荐或查找工具、库或最喜欢的场外资源的问题对于 Stack Overflow 来说是偏离主题的,因为
我有一些 C 代码,将使用 P/Invoke 从 C# 调用。我正在尝试为这个 C 函数定义一个 C# 等效项。 SomeData* DoSomething(); struct SomeData {
这个问题已经有答案了: Why are these constructs using pre and post-increment undefined behavior? (14 个回答) 已关闭 6
我是一名优秀的程序员,十分优秀!