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,如果你想捕获函数对象外的状态,你必须编写一个构造函数。