将std::function 用于递归 lambda 并不是一个好计划。在您的情况下,您会在其中包含 lambda 之前获得该函数的未初始化副本。
这似乎很糟糕。运气好时未定义的行为会崩溃。
递归 Lambdas
假设我们希望将 Euclid 的 gcd() 写为 lambda。作为一个函数,它是:
int gcd(int a, int b) {
return b == 0 ? a : gcd(b, a%b);
}
但是 lambda 不能递归,它无法调用自身。 lambda 没有名称,在 lambda 的主体中使用 this 是指捕获的 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);
};
这可行,但应谨慎使用。它很慢(我们现在使用类型擦除而不是直接函数调用),它很脆弱(复制 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; // the lambda will be stored here
// a forwarding operator():
template <class... Args>
decltype(auto) operator()(Args&&... args) const {
// we pass ourselves to f, then the arguments.
// the lambda should take the first argument as `auto&& recurse` or similar.
return f(*this, std::forward<Args>(args)...);
}
};
// helper function that deduces the type of the lambda:
template <class F>
y_combinator<std::decay_t<F>> make_y_combinator(F&& f) {
return {std::forward<F>(f)};
}
// (Be aware that in C++17 we can do better than a `make_` function)
我们可以将gcd 实现为:
auto gcd = make_y_combinator(
[](auto&& gcd, int a, int b){
return b == 0 ? a : gcd(b, a%b);
}
);
y_combinator 是 lambda 演算中的一个概念,它允许您进行递归,而在定义之前无法命名自己。这正是 lambda 所面临的问题。
您创建了一个将“recurse”作为其第一个参数的 lambda。当你想递归时,你将参数传递给递归。
y_combinator 然后返回一个函数对象,该对象使用其参数调用该函数,但使用合适的“递归”对象(即y_combinator 本身)作为其第一个参数。它还将您调用 y_combinator 的其余参数转发给 lambda。
简而言之:
auto foo = make_y_combinator( [&](auto&& recurse, some arguments) {
// write body that processes some arguments
// when you want to recurse, call recurse(some other arguments)
});
并且您可以在没有严重限制或显着开销的 lambda 中进行递归。
这个答案的一部分(递归 Lambda)最初是由 @Barry 在已失效的堆栈溢出文档中编写的。