我一直在学习C++的函数式编程基础。我正在尝试创建一个函数f(a)(b)(c)
,它将返回a + b + c
。我已经成功实现了函数f(a)(b)
,它返回a + b。这是它的代码:
std::function<double(double)> plus2(double a){
return[a](double b){return a + b; };
}
我只是无法弄清楚如何实现函数f(a)(b)(c)
,正如我之前所说,它应该返回a + b + c
。
我一直在学习C++的函数式编程基础。我正在尝试创建一个函数f(a)(b)(c)
,它将返回a + b + c
。我已经成功实现了函数f(a)(b)
,它返回a + b。这是它的代码:
std::function<double(double)> plus2(double a){
return[a](double b){return a + b; };
}
我只是无法弄清楚如何实现函数f(a)(b)(c)
,正如我之前所说,它应该返回a + b + c
。
您可以通过让您的函数 f
返回一个函数对象,即一个实现 operator()
的对象来实现。以下是一种方法:
struct sum
{
double val;
sum(double a) : val(a) {}
sum operator()(double a) { return val + a; }
operator double() const { return val; }
};
sum f(double a)
{
return a;
}
int main()
{
std::cout << f(1)(2)(3)(4) << std::endl;
}
你甚至可以写一个模板版本,让编译器推断类型。在这里试试。
template <class T>
struct sum
{
T val;
sum(T a) : val(a) {}
template <class T2>
auto operator()(T2 a) -> sum<decltype(val + a)> { return val + a; }
operator T() const { return val; }
};
template <class T>
sum<T> f(T a)
{
return a;
}
在这个例子中,T
最终会被解析为 double
:
std::cout << f(1)(2.5)(3.1f)(4) << std::endl;
std::function
与讨论无关--它是用于类型抹除的。 - Quentinsum::operator()
返回sum
。由于缺乏名称,这种自我引用对于Lambda函数来说是不可能的。 - MSaltersoperator()
类的另一种语法形式。使用“正常”的语法来创建这样一个类并没有任何问题(特别是对于某些不适合使用lambda表达式语法的情况)。 - Jerry Coffin只需将您的2元解决方案扩展,通过使用另一个lambda将其包装。
由于您想返回一个获取double并返回double加法lambda的lambda,所以您只需要使用另一个函数包装当前的返回类型,并在当前lambda中添加一个嵌套的lambda(返回lambda的lambda):
std::function<std::function<double(double)>(double)> plus3 (double a){
return [a] (double b) {
return [a, b] (double c) {
return a + b + c;
};
};
}
正如 @Ðаn 所指出的,您可以跳过
std::function<std::function<double(double)>(double)>
并使用auto
:
auto plus3 (double a){
return [a] (double b) {
return [a, b] (double c) { return a + b + c; };
};
}
你可以使用更深层次的嵌套lambda为每个元素扩展这个结构。以下是4个元素的示例演示:
auto plus4 (double a){
return [a] (double b) {
return [a, b] (double c) {
return [a, b, c] (double d) {
return a + b + c + d;
};
};
};
}
plus<N-1>
定义 plus<N>
。缺点很明显,你仍然需要事先指定精确的参数数量。 - MSalters这是一种稍微不同的方法,它从operator()
返回对*this
的引用,这样你就不会有任何浮动的副本了。这是一个非常简单的函数对象的实现,它存储状态并在自身上递归地进行左折叠:
#include <iostream>
template<typename T>
class Sum
{
T x_{};
public:
Sum& operator()(T x)
{
x_ += x;
return *this;
}
operator T() const
{
return x_;
}
};
int main()
{
Sum<int> s;
std::cout << s(1)(2)(3);
}
cout << s(1)(2)(3)
中,编译器首先看到它不能简单地显示它,然后搜索用户定义的转换运算符,并找到 Sum::operator T() const
。然后将转换应用于 T
,如果可以显示 T
(在我们的情况下,它是一个 int
),则显示它。 - vsoftco这不是 f(a)(b)(c)
,而是 curry(f)(a)(b)(c)
。我们包装了f
,使得每个额外的参数都返回另一个curry
或者立即调用函数。这只适用于C++17,但可以在C++11中通过大量工作实现。
请注意,这是一种对函数进行柯里化的解决方案 - 这是我从问题中得到的印象 - 而不是对二元函数进行折叠的解决方案。
template <class F>
auto curry(F f) {
return [f](auto... args) -> decltype(auto) {
if constexpr(std::is_invocable<F&, decltype(args)...>{}) {
return std::invoke(f, args...);
}
else {
return curry([=](auto... new_args)
-> decltype(std::invoke(f, args..., new_args...))
{
return std::invoke(f, args..., new_args...);
});
}
};
}
出于简洁起见,我省略了转发引用。例如使用方法如下:
int add(int a, int b, int c) { return a+b+c; }
curry(add)(1,2,2); // 5
curry(add)(1)(2)(2); // also 5
curry(add)(1, 2)(2); // still the 5th
curry(add)()()(1,2,2); // FIVE
auto f = curry(add)(1,2);
f(2); // i plead the 5th
tup
和可能从lambda捕获列表中解耦f
,以便根据 ()
的调用方式传入不同的rvalue。然而,这样的解决方案超出了此问题的范围。 - Yakk - Adam Nevraumont*this
上下文和封闭方法的*this
上下文?嗯。) - Yakk - Adam Nevraumontauto fact = [] self (int i) { return (i < = 1) ? 1 : i * self(i-1); };
,但我找不到相关文件。 - Barryplus2()
来定义plus3()
。std::function<double(double)> plus2(double a){
return[a](double b){return a + b; };
}
auto plus3(double a) {
return [a](double b){ return plus2(a + b); };
}
这将第一个和第二个参数列表合并为单个arglist,用于调用plus2()
。 这样做可以最小限度地重复使用我们现有的代码,并且可以很容易地在未来进行扩展; plusN()
只需要返回调用plusN-1()
的lambda函数,该函数将依次将调用传递给以前的函数,直到达到plus2()
。 它可以像这样使用:
int main() {
std::cout << plus2(1)(2) << ' '
<< plus3(1)(2)(3) << '\n';
}
// Output: 3 6
考虑到我们只是顺序调用,我们可以轻松将其转换为函数模板,这消除了创建额外参数版本的需要。
template<int N>
auto plus(double a);
template<int N>
auto plus(double a) {
return [a](double b){ return plus<N - 1>(a + b); };
}
template<>
auto plus<1>(double a) {
return a;
}
int main() {
std::cout << plus<2>(1)(2) << ' '
<< plus<3>(1)(2)(3) << ' '
<< plus<4>(1)(2)(3)(4) << ' '
<< plus<5>(1)(2)(3)(4)(5) << '\n';
}
// Output: 3 6 10 15
在此处可以同时查看两者的效果 here。
constexpr
lambda,这个问题就可以得到缓解。 - Justin Time - Reinstate Monica我将要玩。
您想进行柯里化折叠加法。我们可以解决这一个问题,也可以解决包括此在内的一类问题。
首先是加法:
auto add = [](auto lhs, auto rhs){ return std::move(lhs)+std::move(rhs); };
这很好地表达了加法的概念。
现在,折叠:
template<class F, class T>
struct folder_t {
F f;
T t;
folder_t( F fin, T tin ):
f(std::move(fin)),
t(std::move(tin))
{}
template<class Lhs, class Rhs>
folder_t( F fin, Lhs&&lhs, Rhs&&rhs):
f(std::move(fin)),
t(
f(std::forward<Lhs>(lhs), std::forward<Rhs>(rhs))
)
{}
template<class U>
folder_t<F, std::result_of_t<F&(T, U)>> operator()( U&& u )&&{
return {std::move(f), std::move(t), std::forward<U>(u)};
}
template<class U>
folder_t<F, std::result_of_t<F&(T const&, U)>> operator()( U&& u )const&{
return {f, t, std::forward<U>(u)};
}
operator T()&&{
return std::move(t);
}
operator T() const&{
return t;
}
};
template<class F, class T>
folder_t<F, T> folder( F fin, T tin ) {
return {std::move(fin), std::move(tin)};
}
auto adder = folder(add, 0);
std::cout << adder(2)(3)(4) << "\n";
我们也可以使用 folder
来进行其他操作:
auto append = [](auto vec, auto element){
vec.push_back(std::move(element));
return vec;
};
使用:
auto appender = folder(append, std::vector<int>{});
for (int x : appender(1)(2)(3).get())
std::cout << x << "\n";
实际演示。
这里我们必须调用.get()
,因为for(:)
循环无法理解我们文件夹的operator T()
。我们可以通过一些努力来解决这个问题,但是.get()
更容易。
std::bind
的替代品 :) - Slavaauto f = fold<4>(add);
,这可能比我们任何一个答案更符合OP的要求。 - Yakk - Adam Nevraumontdouble plus4_impl(double a, double b, double c, double d) {
return a + b + c + d;
}
constexpr auto plus4 = boost::hana::curry<4>(plus4_impl);
然后使用它就像你想要的那样:
int main() {
std::cout << plus4(1)(1.0)(3)(4.3f) << '\n';
std::cout << plus4(1, 1.0)(3)(4.3f) << '\n'; // you can also do up to 4 args at a time
}
auto f = [] (double a) {
return [=] (double b) {
return [=] (double c) {
return a + b + c;
};
};
};
这段代码可以完全满足您的需求,并且它适用于C++11,而不像其他答案那样可能大多数都无法使用。
请注意,它不使用std::function
,因为这会导致性能下降,实际上,在许多情况下它可以被内联。
operator()
来改变状态。
编辑: 将不必要的赋值替换为初始化。#include<iostream>
class adder{
private:
adder(double a)val(a){}
double val = 0.0;
static adder* mInstance;
public:
adder operator()(double a){
val += a;
return *this;}
static adder add(double a){
if(mInstance) delete mInstance;
mInstance = new adder(a);
return *mInstance;}
double get(){return val;}
};
adder* adder::mInstance = 0;
int main(){
adder a = adder::add(1.0)(2.0)(1.0);
std::cout<<a.get()<<std::endl;
std::cout<<adder::add(1.0)(2.0)(3.0).get()<<std::endl;
return 0;
}
add
函数的意义。在内部成员中存储实例指针有什么意义呢?你好像也没有问题返回 值。 - JDługoszmInstance
,用return {a};
替换add
的整个函数体,你的代码就不会再浪费资源了。堆分配和静态状态什么也没做。虽然这并没有解决原问题,因为OP不想要.get()
,但至少不会太糟糕。 - Yakk - Adam Nevraumont
f(a)(b)(c)
,不确定能否完成此操作。如果你想使用f(a)(b)(c)()
,应该很容易让它正常工作。 - NathanOliverauto
,从而避免使用std::function
所带来的类型抹除代价。 - Quentin