问题
Is it possible to replace one std::function
from within itself with another std::function
?
The following code does not compile:
#include <iostream>
#include <functional>
int main()
{
std::function<void()> func = []()
{
std::cout << "a\n";
*this = std::move([]() { std::cout << "b\n"; });
};
func();
func();
func();
}
Can it be modified to compile?
The error message right now is: 'this' was not captured for this lambda function - which I completely understand. I don't know, however, how I could capture func
's this
-pointer. I guess, it is not even a std::function
inside the lambda, yet?! How can this be done?
Background: What I want to achieve is the following: In the first invocation of a given std::function
, i would like do some initialization work and then replace the original function with an optimized one. I want to achieve this transparently for the user of my function.
The expected output of the example above is:
a
b
b
回答1:
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
.
回答2:
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.
来源:https://stackoverflow.com/questions/50568739/replacing-stdfunction-from-within-itself-by-move-assignment-to-this