我是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);
};

有人能解释一下吗?


当前回答

这里证明了一个小主体的递归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

考虑一下自动版本和完全指定类型版本之间的区别。auto关键字从初始化它的对象推断它的类型,但是初始化它的对象需要知道它的类型(在本例中,lambda闭包需要知道它捕获的类型)。有点像鸡生蛋还是蛋生鸡的问题。

另一方面,完全指定的函数对象的类型不需要“知道”任何被赋值给它的内容,因此lambda闭包同样可以完全知道它捕获的类型。

考虑一下对代码的轻微修改,它可能更有意义:

std::function<int(int, int)> sum;

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

显然,这在auto中行不通。递归lambda函数工作得非常好(至少它们在MSVC中是这样的,我在MSVC中有使用它们的经验),只是它们与类型推断并不真正兼容。

诀窍是将lambda实现作为参数提供给自身,而不是通过捕获。

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

计算机科学中的所有问题都可以通过另一种间接方式来解决。我第一次发现这个简单的技巧是在http://pedromelendez.com/blog/2015/07/16/recursive-lambdas-in-c14/

它确实需要c++ 14,而问题是c++ 11,但对大多数人来说可能很有趣。

这是戈德波特大学的完整例子。

使用std::function也是可能的,但会导致代码变慢。但并非总是如此。看看std::function vs template的答案


这不仅仅是c++的特性, 它直接映射到微积分的数学中。从维基百科:

Lambda微积分不能像其他表达式那样直接表示这个 符号: 所有的函数在微积分中都是匿名的,所以我们不能引用a 还没有定义的值,在定义它的lambda项中 相同的值。但是,递归仍然可以通过排列 Lambda表达式接收自身作为参数值

要使lambda递归而不使用外部类和函数(如std::function或定点组合子),可以在c++ 14中使用以下结构(现场示例):

#include <utility>
#include <list>
#include <memory>
#include <iostream>

int main()
{
    struct tree
    {
        int payload;
        std::list< tree > children = {}; // std::list of incomplete type is allowed
    };
    std::size_t indent = 0;
    // indication of result type here is essential
    const auto print = [&] (const auto & self, const tree & node) -> void
    {
        std::cout << std::string(indent, ' ') << node.payload << '\n';
        ++indent;
        for (const tree & t : node.children) {
            self(self, t);
        }
        --indent;
    };
    print(print, {1, {{2, {{8}}}, {3, {{5, {{7}}}, {6}}}, {4}}});
}

打印:

1
 2
  8
 3
  5
   7
  6
 4

注意,lambda的结果类型应该显式指定。

这里是op的最终答案。无论如何,Visual Studio 2010不支持捕获全局变量。您不需要捕获它们,因为全局变量可以通过define全局访问。下面的答案使用局部变量代替。

#include <functional>
#include <iostream>

template<typename T>
struct t2t
{
    typedef T t;
};

template<typename R, typename V1, typename V2>
struct fixpoint
{
    typedef std::function<R (V1, V2)> func_t;
    typedef std::function<func_t (func_t)> tfunc_t;
    typedef std::function<func_t (tfunc_t)> yfunc_t;

    class loopfunc_t {
    public:
        func_t operator()(loopfunc_t v)const {
            return func(v);
        }
        template<typename L>
        loopfunc_t(const L &l):func(l){}
        typedef V1 Parameter1_t;
        typedef V2 Parameter2_t;
    private:
        std::function<func_t (loopfunc_t)> func;
    };
    static yfunc_t fix;
};
template<typename R, typename V1, typename V2>
typename fixpoint<R, V1, V2>::yfunc_t fixpoint<R, V1, V2>::fix = [](tfunc_t f) -> func_t {
    return [f](fixpoint<R, V1, V2>::loopfunc_t x){  return f(x(x)); }
    ([f](fixpoint<R, V1, V2>::loopfunc_t x) -> fixpoint<R, V1, V2>::func_t{
        auto &ff = f;
        return [ff, x](t2t<decltype(x)>::t::Parameter1_t v1, 
            t2t<decltype(x)>::t::Parameter1_t v2){
            return ff(x(x))(v1, v2);
        }; 
    });
};

int _tmain(int argc, _TCHAR* argv[])
{
    auto term = [](int a)->int {
      return a*a;
    };

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

    auto sum = fixpoint<int, int, int>::fix(
    [term,next](std::function<int (int, int)> sum1) -> std::function<int (int, int)>{
        auto &term1 = term;
        auto &next1 = next;
        return [term1, next1, sum1](int a, int b)mutable ->int {
            if(a>b)
                return 0;
        else
            return term1(a) + sum1(next1(a),b);
        };
    });

    std::cout<<sum(1,10)<<std::endl; //385

    return 0;
}