从自身内部替换 std::function(通过移动赋值给 *this?)

Replacing std::function from within itself (by move-assignment to *this?)

是否可以用另一个 std::function 替换一个 std::function

以下代码无法编译:

#include <iostream>
#include <functional>

int main()
{
    std::function<void()> func = []()
    {
        std::cout << "a\n";
        *this = std::move([]() { std::cout << "b\n"; });
    };
    func();
    func();
    func();
}

是否可以修改编译?
现在的错误消息是:'this' was not captured for this lambda function - 我完全理解。但是,我不知道如何捕获 functhis 指针。我想,它甚至不是 lambda 内部的 std::function,是吗?!如何做到这一点?

背景:我想要实现的是:在给定std::function的第一次调用中,我想做一些初始化工作然后替换原始功能与优化功能。我想为我的功能的用户透明地实现这一点。

上面例子的预期输出是:

a
b
b

您不能在 lambda 内部使用 this 来引用 lambda。 this 将仅引用封闭的 class,在您的情况下有 none,因此您不能使用 this。但是,您可以做的是捕获 func 并重新分配:

std::function<void()> func = [&func]()
{
    std::cout << "a\n";
    func = []() { std::cout << "b\n"; }; // note the missing move, a lambda
                                         // is already an rvalue
};

但是请注意,如果您让 func 超出其范围(例如通过按值从函数返回它)而不先调用它(有效地重新分配存储的函数对象),那么您将得到一个悬空引用.

I guess, it is not even a std::function inside the lambda, yet?!

确实如此。名称在其声明符之后立即进入作用域,因此就在 =func 类型 std::function<void()> 被引入之前。所以在你引入 lambda 的时候,你已经可以捕获 func.

这在技术上解决了您的问题:

std::function<void()> func = [&func]{
  std::cout << "a\n";
  func = []{ std::cout << "b\n"; };
};

但这不是一个好计划。 std 函数的生命周期和行为与本地堆栈变量相关联。副本几乎在任何意义上都无法执行您可能想要的操作——它们会继续打印 "a\b",除非它们出现段错误,因为原始 func 超出了范围。


为了解决这个问题,我们必须部署一些大炮;首先,函数式编程女王 Y Combinator 女士:

std::function<void()> func = y_combinate( [](auto&& self){
  std::cout << "a\n";
  self = []{ std::cout << "b\"; };
} );

Y 组合器采用签名 F = (F,Args...)->R 的函数,然后 returns 签名 (Args...)->R 的函数。这就是无状态语言如何在您无法在给定名称之前为自己命名时管理递归。

编写 Y 组合器比您在 C++ 中担心的要容易:

template<class F>
struct y_combinator_t {
  F f;
  template<class...Args>
  auto operator()(Args&&...args)
  -> typename std::result_of< F&( F&, Args&&... ) >::type
  {
    return f( f, std::forward<Args>(args)... );
  }
};
template<class F>
y_combinator_t<typename std::decay<F>::type> y_combinate( F&& f ) {
  return {std::forward<F>(f)};
}

遗憾的是,这不起作用,因为传递给 lambda 的 self 类型实际上是原始 lambda 的类型。 b-printing lambda 是不相关的类型。因此,当您尝试 self = []{ std::cout << "b\n"; } 时,您会得到一个嵌入在一些相对较深的模板垃圾邮件错误中的错误。

悲伤;但只是暂时的挫折。

我们需要的是一种很难命名的类型 -- F = std::function<void(F)> -- 一个 std::function 将同一类型对象的一个​​实例作为其参数。

通常没有办法做到这一点,但有一点模板 tomfoolery...Here, I did it 之前。

那么您的代码为:

std::function<void()> func = y_combinate( recursive_func< void(own_type&) >([](auto&& self){
  std::cout << "a\n";
  self = [](auto&&){ std::cout << "b\n"; };
}) );

并且调用给定的 func 副本首先打印 "a\n",然后以后的每个调用打印 "b\n"。 b 打印机的副本也打印 b,但 a 打印机的副本将在转换前第一次打印 a。

Live example.

此代码中的

self 是一个 recursive_func< void(own_type&) >,因此您可以在 b 打印机中执行与在 a 打印机中相同的操作。