我是c++ 11的新手。我正在写下面的递归lambda函数,但它不能编译。

sum.cpp

#include <iostream>
#include <functional>

auto term = [](int a)->int {
  return a*a;
};

auto next = [](int a)->int {
  return ++a;
};

auto sum = [term,next,&sum](int a, int b)mutable ->int {
  if(a>b)
    return 0;
  else
    return term(a) + sum(next(a),b);
};

int main(){
  std::cout<<sum(1,10)<<std::endl;
  return 0;
}

编译错误:

vimal@linux-718q:~/Study/09C++/c++0x/lambda> g++ -std=c++0x sum.cpp

sum.cpp:在lambda函数中 sum.cpp:18:36:错误:' ((<lambda(int, int)>*)this)-><lambda(int, int)>::sum '不能用作函数

gcc版本

gcc版本4.5.0 20091231(实验性)(gcc)

但如果我改变sum()的声明如下所示,它可以工作:

std::function<int(int,int)> sum = [term,next,&sum](int a, int b)->int {
   if(a>b)
     return 0;
   else
     return term(a) + sum(next(a),b);
};

有人能解释一下吗?


当前回答

我有另一个解决方案,但只与无状态lambdas工作:

void f()
{
    static int (*self)(int) = [](int i)->int { return i>0 ? self(i-1)*i : 1; };
    std::cout<<self(10);
}

这里的技巧是lambdas可以访问静态变量,并且可以将无状态变量转换为函数指针。

你可以在标准的lambdas中使用它:

void g()
{
    int sum;
    auto rec = [&sum](int i) -> int
    {
        static int (*inner)(int&, int) = [](int& _sum, int i)->int 
        {
            _sum += i;
            return i>0 ? inner(_sum, i-1)*i : 1; 
        };
        return inner(sum, i);
    };
}

它在GCC 4.7中的工作

其他回答

这个答案不如扬克斯的,但还是这样:

using dp_type = void (*)();

using fp_type = void (*)(dp_type, unsigned, unsigned);

fp_type fp = [](dp_type dp, unsigned const a, unsigned const b) {
  ::std::cout << a << ::std::endl;
  return reinterpret_cast<fp_type>(dp)(dp, b, a + b);
};

fp(reinterpret_cast<dp_type>(fp), 0, 1);

你可以递归地调用lambda函数本身。您唯一需要做的是通过函数包装器引用它,以便编译器知道它的返回值和参数类型(您不能捕获尚未定义的变量——lambda本身)。

  function<int (int)> f;

  f = [&f](int x) {
    if (x == 0) return 0;
    return x + f(x-1);
  };

  printf("%d\n", f(10));

要非常小心,不要超出包装器f的范围。

在c++ 14中,现在很容易创建一个有效的递归lambda,而不必引起std::function的额外开销,只需几行代码:

template <class F>
struct y_combinator {
    F f; // the lambda will be stored here
    
    // a forwarding operator():
    template <class... Args>
    decltype(auto) operator()(Args&&... args) const {
        // we pass ourselves to f, then the arguments.
        return f(*this, std::forward<Args>(args)...);
    }
};

// helper function that deduces the type of the lambda:
template <class F>
y_combinator<std::decay_t<F>> make_y_combinator(F&& f) {
    return {std::forward<F>(f)};
}

你原来的求和尝试变成:

auto sum = make_y_combinator([term,next](auto sum, int a, int b) -> int {
  if (a>b) {
    return 0;
  }
  else {
    return term(a) + sum(next(a),b);
  }
});

在c++ 17中,使用CTAD,我们可以添加演绎指南:

template <class F> y_combinator(F) -> y_combinator<F>;

这样就不需要辅助函数了。我们可以写y_combinator{[](auto self,…){…直接}}。


在c++ 20中,使用CTAD进行聚合,就不需要演绎指南了。


在c++ 23中,通过演绎,你根本不需要y组合子:

auto sum = [term,next](this auto const& sum, int a, int b) -> int {
  if (a>b) {
    return 0;
  }
  else {
    return term(a) + sum(next(a),b);
  }
}

这里证明了一个小主体的递归lambda几乎具有与普通递归函数相同的性能,可以直接调用自己。

#include <iostream>
#include <chrono>
#include <type_traits>
#include <functional>
#include <atomic>
#include <cmath>

using namespace std;
using namespace chrono;

unsigned recursiveFn( unsigned x )
{
    if( x ) [[likely]]
        return recursiveFn( x - 1 ) + recursiveFn( x - 1 );
    else
        return 0;
};

atomic_uint result;

int main()
{
    auto perf = []( function<void ()> fn ) -> double
    {
        using dur_t = high_resolution_clock::duration;
        using urep_t = make_unsigned_t<dur_t::rep>;
        high_resolution_clock::duration durMin( (urep_t)-1 >> 1 );
        for( unsigned r = 10; r--; )
        {
            auto start = high_resolution_clock::now();
            fn();
            dur_t dur = high_resolution_clock::now() - start;
            if( dur < durMin )
                durMin = dur;
        }
        return durMin.count() / 1.0e9;
    };
    auto recursiveLamdba = []( auto &self, unsigned x ) -> unsigned
    {
        if( x ) [[likely]]
            return self( self, x - 1 ) + self( self, x - 1 );
        else
            return 0;
    };
    constexpr unsigned DEPTH = 28;
    double
        tLambda = perf( [&]() { ::result = recursiveLamdba( recursiveLamdba, DEPTH ); } ),
        tFn = perf( [&]() { ::result = recursiveFn( DEPTH ); } );
    cout << trunc( 1000.0 * (tLambda / tFn - 1.0) + 0.5 ) / 10.0 << "%" << endl;
}

对于我的AMD Zen1 CPU,目前的MSVC递归速度快10%左右。对于我的Phenom II x4 945和g++ 11.1。这两个函数有相同的性能。 请记住,这几乎是最糟糕的情况,因为函数体非常小。如果它更大,递归函数调用本身的部分就更小。

在c++ 23中,扣除这个(P0847)将被添加:

auto f = [](this auto& self, int i) -> int
{
    return i > 0 ? self(i - 1) + i : 0;
}

目前它只在EDG eccp和(部分)MSVC可用:

https://godbolt.org/z/f3E3xT3fY