C++ 递归lambdas
示例
假设我们希望将Euclid编写gcd()为lambda。作为功能,它是:
int gcd(int a, int b) { return b == 0 ? a : gcd(b, a%b); }
但是lambda不能递归,它无法调用自身。Lambda没有名称,并且this在Lambda主体内使用是指捕获的内容this(假设Lambda在成员函数的主体中创建,否则为错误)。那么我们如何解决这个问题呢?
采用std::function
我们可以让lambda捕获对尚未构造的引用std::function:
std::function<int(int, int)> gcd = [&](int a, int b){ return b == 0 ? a : gcd(b, a%b); };
这可行,但应谨慎使用。它很慢(我们现在使用类型擦除来代替直接函数调用),它很脆弱(由于lambda引用原始对象,所以复制gcd或返回gcd会中断),并且不适用于泛型lambda。
使用两个智能指针:
auto gcd_self = std::make_shared<std::unique_ptr< std::function<int(int, int)> >>(); *gcd_self = std::make_unique<std::function<int(int, int)>>( [gcd_self](int a, int b){ return b == 0 ? a : (**gcd_self)(b, a%b); }; };
这增加了很多间接(这是开销),但是可以复制/返回它,并且所有副本共享状态。它的确可以让您返回lambda,否则它不如上面的解决方案那么脆弱。
使用Y组合器
借助简短的实用程序结构,我们可以解决所有这些问题:
template <class F> struct y_combinator { F f; //Lambda将存储在此处 //转发操作符(): template <class... Args> decltype(auto) operator()(Args&&... args) const { //我们将自己传递给f,然后传递给参数。 // the lambda should take the first argument as `auto&& recurse` or similar. return f(*this, std::forward<Args>(args)...); } }; //推断lambda类型的辅助函数: template <class F> y_combinator<std::decay_t<F>> make_y_combinator(F&& f) { return {std::forward<F>(f)}; } //(请注意,在C++17中,我们可以比`make_`函数做得更好)
我们可以实现gcd为:
auto gcd = make_y_combinator( [](auto&& gcd, int a, int b){ return b == 0 ? a : gcd(b, a%b); } );
这y_combinator是lambda演算中的一个概念,它使您可以递归,而不必在定义之前命名自己。这正是lambda所面临的问题。
您创建一个以“递归”为第一个参数的lambda。当您想递归时,您将参数传递给递归。
所述y_combinator然后返回一个函数对象调用该函数与它的参数,但用合适的“递归”的对象(即y_combinator本身)作为它的第一个参数。它将y_combinator与之一起调用的其余参数也转发给lambda。
简而言之:
auto foo = make_y_combinator( [&](auto&& recurse, some arguments) { //处理一些参数的写体 //当您想递归时,调用递归(其他一些参数) });
并且您可以在lambda中进行递归,而没有严重的限制或明显的开销。