这是一个基于 y-combinator 的快速递归引擎:
template<class F>
struct recursive_t {
F f;
// note Self must be an lvalue reference. Things get
// strange if it is an rvalue:
// invoke makes recursive ADL work a touch better.
template<class Self, class...Args>
friend auto invoke( Self& self, Args&&...args )
-> decltype( self.f( self, std::declval<Args>()... ) )
{
return self.f( self, std::forward<Args>(args)... );
}
// calculate return type using `invoke` above:
template<class Self, class...Args>
using R = decltype( invoke( std::declval<Self>(), std::declval<Args>()... ) );
template<class...Args>
R<recursive_t&, Args...> operator()(Args&&...args)
{
return invoke( *this, std::forward<Args>(args)... );
}
template<class...Args>
R<recursive_t const&, Args...> operator()(Args&&...args)const
{
return invoke( *this, std::forward<Args>(args)... );
}
};
template<class F>
recursive_t< std::decay_t<F> > recurse( F&& f )
{
return {std::forward<F>(f)};
}
现在你可以这样做:
auto f = recurse( [](auto&& f, auto x){ return x ? f(x/2)+1 : 0; } );
并且您得到一个没有&
捕获的递归 lambda(这将其使用限制在当前范围内)。
通过引用捕获 astd::function
意味着您的 lambda 的生命周期是当前范围,并且每个递归调用都需要遍历类型擦除(阻止递归调用上的任何可能的优化,例如尾递归)。其他类似的解决方案也是如此。
需要使用 ofrecursive_t
而不是使用 lambda,因为 lambda 不能在其自身内命名。
活生生的例子。
基于 lambda 的版本在实现上稍微简单一些。请注意,对于可变和不可变 lambda,您需要不同的类型函数:
template<class F>
auto recurse( F&& f ) {
return [f=std::forward<F>(f)](auto&&...args){
return f(f, decltype(args)(args)...);
};
};
recursive_t
作品如:
auto fib = recurse( [](auto&& fib, int x){ if (x<2) return 1; return fib(x-1)+fib(x-2); } );
lambda 版本的工作方式如下:
auto fib = recurse( [](auto&& self, int x){ if (x<2) return 1; return self(self, x-1)+self(self,x-2); } );
我个人觉得更尴尬。
也更难描述的类型recurse
。对于recursive_t
版本,recurse
类型为:
((A->B)->A->B)->(A->B)
这很尴尬,但是一种有限的类型。
lambda 版本更棘手。函数参数recursive
的类型为:
F:= F->A->B
这是令人讨厌的无限,然后recurse
是类型
F->A->(A->B)
它继承了无穷大。
无论如何,recurse
返回值可以存储在一个普通的std::function
,或者不存储在任何类型擦除的容器中。