C ++中的recursionlambda函数

在C ++ 11中编写recursionlambda函数有一个经常重复的“技巧”,如下所示:

std::function<int(int)> factorial; factorial = [&factorial](int n) { return n < 2 ? 1 : n * factorial(n - 1); }; assert( factorial(5) == 120 ); 

(例如C ++ 0x中的recursionlambda函数 。)

这个技巧有两个直接的缺点: std::function<Sig>对象的目标被绑定(通过引用捕获)到一个非常特殊的std::function<Sig>对象(这里是factorial )。 这意味着函数通常不能从函数返回,否则引用将被留下。

另一个(虽然不那么直接)的问题是std::function的使用通常会阻止编译器优化,这是在实现中需要types擦除的副作用。 这不是假设的,可以很容易地进行testing。

在recursionlambdaexpression式真的很方便的假设情况下,是否有办法解决这些问题?

问题的症结在于,在C ++ lambdaexpression式中, 隐含的 this参数将始终引用expression式的封闭上下文的对象(如果存在的话),而不是由lambdaexpression式产生的函子对象。

借用匿名recursion (有时也称为“开放式recursion”)的叶子,我们可以使用C ++ 14的通用lambdaexpression式重新引入一个显式参数来引用我们想要的recursion函数:

 auto f = [](auto&& self, int n) -> int { return n < 2 ? 1 : n * self(/* hold on */); }; 

呼叫者现在有一个新的forms调用的负担,例如f(f, 5) 。 由于我们的lambdaexpression式是自引用的,因此它实际上是自己的调用者,因此我们应该return n < 2 ? 1 : n * self(self, n - 1); return n < 2 ? 1 : n * self(self, n - 1);

由于明确地将函数对象本身放在第一位置的模式是可以预测的,所以我们可以重构这个丑陋的疣状物:

 template<typename Functor> struct fix_type { Functor functor; template<typename... Args> decltype(auto) operator()(Args&&... args) const& { return functor(functor, std::forward<Args>(args)...); } /* other cv- and ref-qualified overloads of operator() omitted for brevity */ }; template<typename Functor> fix_type<typename std::decay<Functor>::type> fix(Functor&& functor) { return { std::forward<Functor>(functor) }; } 

这允许一个人写:

 auto factorial = fix([](auto&& self, int n) -> int { return n < 2 ? 1 : n * self(self, n - 1); }); assert( factorial(5) == 120 ); 

我们成功了吗? 由于fix_type<F>对象包含自己的函子,每次调用都传递给它,所以永远不会有悬挂引用的风险。 因此,我们的factorial对象可以真正无休止地复制,移出和移出function。

除了“外部”调用者可以很方便地调用外部factorial(5) ,因为在我们的lambdaexpression式中,recursion调用仍然看起来像self(self, /* actual interesting args */) 。 我们可以通过改变fix_type来改进,而不是传递functor到自己,而是通过传递*this来代替。 也就是说,我们传入了fix_type对象,该对象负责在第一个位置传递正确的“implicit-as-explicit”参数: return functor(*this, std::forward<Args>(args)...); 。 然后recursion变成n * self(n - 1) ,就像它应该是的那样。

最后,这是使用return factorial(5);main的生成代码return factorial(5); 而不是断言(对于任何一种fix_type ):

 00000000004005e0 <main>: 4005e0: b8 78 00 00 00 mov eax,0x78 4005e5: c3 ret 4005e6: 66 90 xchg ax,ax 

编译器能够优化所有的东西,就像使用普通的recursion函数一样。


成本是多less?

敏锐的读者可能已经注意到一个好奇的细节。 在从非generics转换为genericslambda的过程中,我添加了一个显式的返回types(即-> int )。 怎么来的?

这与推导的返回types是条件expression式的types有关,该types取决于对自己的调用,哪个types正在被推导。 正常函数的返回types推导的快速阅读将build议重写lambdaexpression式应如下工作:

 [](auto&& self, int n) { if(n < 2) return 1; // return type is deduced here else return n * self(/* args */); // this has no impact } 

GCC实际上只接受第一种forms的fix_type (通过functor )的代码。 我无法确定是否正确地抱怨其他表格( *this是通过的)。 我把它留给读者select什么权衡:减lesstypes扣除,或不太丑陋的recursion调用(当然也完全可以访问任何一种风味)。


GCC 4.9的例子

  • 完整的代码,第一味
  • 完整的代码,第二味道
  • 完整的代码,第一味,C ++ 11
  • 用于一组相互recursionlambdaexpression式的可变定义的例子

这不是一个lambdaexpression式,但几乎没有更多的代码,使用C ++ 98, 可以recursion:

 struct { int operator()(int n) const { return n < 2 ? 1 : n * (*this)(n-1); } } fact; return fact(5); 

根据[class.local]/1 ,它可以访问封闭函数有权访问的所有名称,这对于成员函数中的私有名称很重要。

当然,不是一个lambda,如果你想捕获函数对象外的状态,你必须编写一个构造函数。