递归lambda和捕获(segfault)

recursive lambda and capture (segfault)

本文关键字:segfault lambda 递归      更新时间:2023-10-16

编译器

g++ (Ubuntu 5.2.1-22ubuntu2) 5.2.1 20151010

代码段1&捕获)

#include <functional>
int main()
{
  std::function<void ()> func = [&] () {
    func();
  };
  return 0;
}

代码段2func捕获)

#include <functional>
int main()
{
  std::function<void ()> func = [func] () {
    func();
  };
  return 0;
}

两个代码段都可以编译,但为什么运行第二个代码段会导致分段错误?

捕获发生在构造std::function之前。

因此,您捕获了std::function<void()> func的未初始化(甚至不是默认构造的!)副本。std::function的捕获本身就是UB(在构造变量之前复制变量!),调用它将是"更多的UB"(调用未构造的std::function的副本!)。

引用案例捕获对func的引用,即使在它被初始化之前,只要它只被初始化一次,它也是允许的。

引用情况的缺点是lambda仅在func的范围内保持有效。一旦func超出范围,其副本也将无效。根据我的经验,这很糟糕。

要实现真正的"全强度"递归lambda,您需要类似于y组合子的东西。

这里是一个C++14 y组合子的缩写:

template<class F>
auto y_combinate( F&& f ) {
  return [f = std::forward<F>(f)](auto&&...args) {
    return f(f, decltype(args)(args)...);
  };
}

您向它传递一个lambda,该lambda期望将对自身的引用作为第一个参数:

std::function<void ()> func = y_combinate( [](auto&& self) {
    self(self);
  }
);

它完成其余的工作。

y组合子的要求是因为您不能在lambda的主体中访问自己的this。所以我们加一个。

上面的y组合子只有90%,因为它不能完美地处理传入的函数对象的r/l值和常量。但它将在大部分时间内发挥作用。

这是一个稍微好一点的y组合:

template<class F>
struct y_combinate_t {
  F f;
  template<class...Args>
  decltype(auto) operator()(Args&&...args)const {
    return f(*this, std::forward<Args>(args)...);
  }
};
template<class F>
y_combinate_t<std::decay_t<F>> y_combinate( F&& f ) {
  return {std::forward<F>(f)};
}

这使得使用效果更好:

std::function<void ()> func = y_combinate( [](auto&& self) {
    self();
  }
);

现在传入的CCD_ 12在被调用时不必自身传入CCD_。