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

给你一囗甜甜゛ 提交于 2019-12-04 10:28:44

You cannot use this inside a lambda to refer to the lambda. this will only refer to the enclosing class, which in your case there is none so you cannot use this. What you can do however is capture func and reassign that:

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

Note however that if you let func outlive its scope (say by returning it from a function by value) without calling it first (effectively reassigning the stored function object) then you'll get a dangling reference.

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

It actually is. A name comes into scope right after its declarator, so right before the =, func of type std::function<void()> is introduced. So at the point where you introduce the lambda, you can already capture func.

This technically solves your problem:

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

But it is not a good plan. The lifetime and behaviour of the std function is tied to a local stack variable. Copies fail to do what you probably want in almost any sense -- they continue to print "a\b" unless they segfault because the original func fell out of scope.


To fix this we have to deploy some big guns; to start with, the Queen of functional programming, Ms. Y Combinator:

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

The Y Combinator takes a function of the signature F = (F,Args...)->R, then returns a function of the signature (Args...)->R. It is how stateless languages manage recursion when you cannot name yourself before you are given a name.

Writing a Y Combinator is easier than you would fear in 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)};
}

sadly this doesn't work as the type of self passed to a lambda is actually the type of the original lambda. And the b-printing lambda is an unrelated type. So when you try to self = []{ std::cout << "b\n"; } you get an error embedded in some relatively deep template spam errors.

Sad; but just a temporary setback.

What we need is a type very difficult to name -- a F = std::function<void(F)> -- a std::function that takes as its one argument an instance of an object of the same type.

There is usually no way to do this, but with a bit of template tomfoolery... Here, I did it before.

Then your code reads:

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

and a call to a given copy of func first prints "a\n", then each later call prints "b\n". Copies of b-printers also print b, but copies of a-printers will print a the first time before transitioning.

Live example.

self in this code is a recursive_func< void(own_type&) >, so you can do the same thing within the b-printer as you did in the a-printer.

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!