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

有人能解释一下吗?


当前回答

您正在尝试捕获正在定义的变量(sum)。那可不太好。

我不认为真正的自递归c++ 0x是可能的。不过,您应该能够捕获其他lambda。

其他回答

c++ 14: 这是lambdas的递归匿名无状态/无捕获泛型集 输出从1,20开始的所有数字

([](auto f, auto n, auto m) {
    f(f, n, m);
})(
    [](auto f, auto n, auto m) -> void
{
    cout << typeid(n).name() << el;
    cout << n << el;
    if (n<m)
        f(f, ++n, m);
},
    1, 20);

如果我没理解错,这是用y组合子解

这是(n, m)的和

auto sum = [](auto n, auto m) {
    return ([](auto f, auto n, auto m) {
        int res = f(f, n, m);
        return res;
    })(
        [](auto f, auto n, auto m) -> int
        {
            if (n > m)
                return 0;
            else {
                int sum = n + f(f, n + 1, m);
                return sum;
            }
        },
        n, m); };

auto result = sum(1, 10); //result == 55

我使用std::function<>捕获方法运行了一个基准测试,比较递归函数和递归lambda函数。在clang版本4.1上启用了完全优化后,lambda版本的运行速度明显变慢了。

#include <iostream>
#include <functional>
#include <chrono>

uint64_t sum1(int n) {
  return (n <= 1) ? 1 : n + sum1(n - 1);
}

std::function<uint64_t(int)> sum2 = [&] (int n) {
  return (n <= 1) ? 1 : n + sum2(n - 1);
};

auto const ITERATIONS = 10000;
auto const DEPTH = 100000;

template <class Func, class Input>
void benchmark(Func&& func, Input&& input) {
  auto t1 = std::chrono::high_resolution_clock::now();
  for (auto i = 0; i != ITERATIONS; ++i) {
    func(input);
  }
  auto t2 = std::chrono::high_resolution_clock::now();
  auto duration = std::chrono::duration_cast<std::chrono::milliseconds>(t2-t1).count();
  std::cout << "Duration: " << duration << std::endl;
}

int main() {
  benchmark(sum1, DEPTH);
  benchmark(sum2, DEPTH);
}

产生的结果:

Duration: 0 // regular function
Duration: 4027 // lambda function

(注意:我还确认了一个从cin获取输入的版本,以消除编译时计算)

Clang还会产生一个编译器警告:

main.cc:10:29: warning: variable 'sum2' is uninitialized when used within its own initialization [-Wuninitialized]

这是意料之中的,也是安全的,但应该注意。

在我们的工具中有一个解决方案是很好的,但我认为如果要与当前的方法相比,该语言需要更好的方法来处理这种情况。

注意:

正如一位评论者指出的那样,最新版本的vc++似乎已经找到了一种方法来优化这一点,以达到同等的性能。也许我们不需要更好的方法来处理这个问题(除了语法糖)。

另外,正如最近几周其他一些SO帖子所概述的那样,std::function<>本身的性能可能是导致直接调用function速度变慢的原因,至少当lambda捕获太大而无法放入一些库优化的空间时(我猜有点像各种短字符串优化?)

在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);
  }
}

这是一个稍微简单的固定点操作符的实现,这使得它更明显地发生了什么。

#include <iostream>
#include <functional>

using namespace std;

template<typename T, typename... Args>
struct fixpoint
{
    typedef function<T(Args...)> effective_type;
    typedef function<T(const effective_type&, Args...)> function_type;

    function_type f_nonr;

    T operator()(Args... args) const
    {
        return f_nonr(*this, args...);
    }

    fixpoint(const function_type& p_f)
        : f_nonr(p_f)
    {
    }
};


int main()
{
    auto fib_nonr = [](const function<int(int)>& f, int n) -> int
    {
        return n < 2 ? n : f(n-1) + f(n-2);
    };

    auto fib = fixpoint<int,int>(fib_nonr);

    for (int i = 0; i < 6; ++i)
    {
        cout << fib(i) << '\n';
    }
}

诀窍是将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表达式接收自身作为参数值