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

有人能解释一下吗?


当前回答

在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

其他回答

这里证明了一个小主体的递归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。这两个函数有相同的性能。 请记住,这几乎是最糟糕的情况,因为函数体非常小。如果它更大,递归函数调用本身的部分就更小。

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

#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';
    }
}

我有另一个解决方案,但只与无状态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中的工作

我使用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捕获太大而无法放入一些库优化的空间时(我猜有点像各种短字符串优化?)

考虑一下自动版本和完全指定类型版本之间的区别。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中有使用它们的经验),只是它们与类型推断并不真正兼容。