How to create an std::function from a move-capturing lambda expression?
Solution 1:
template<class F> function(F f);
template <class F, class A> function(allocator_arg_t, const A& a, F f);
Requires:
F
shall beCopyConstructible
.f
shall beCallable
for argument typesArgTypes
and return typeR
. The copy constructor and destructor of A shall not throw exceptions.§20.9.11.2.1 [func.wrap.func.con]
Note that operator =
is defined in terms of this constructor and swap
, so the same restrictions apply:
template<class F> function& operator=(F&& f);
Effects:
function(std::forward<F>(f)).swap(*this);
§20.9.11.2.1 [func.wrap.func.con]
So to answer your question: Yes, it is possible to construct a std::function
from a move-capturing lambda (since this only specifies how the lambda captures), but it is not possible to construct a std::function
from a move-only type (e.g. a move-capturing lambda which move-captures something that is not copy constructible).
Solution 2:
As std::function<?>
has to type-erase the copy constructor of the stored invocable object, you cannot construct it from a move-only type. Your lambda, because it captures a move-only type by value, is a move-only type. So... you cannot solve your problem. std::function
cannot store your lambda.
At least not directly.
This is C++, we simply route around the problem.
template<class F>
struct shared_function {
std::shared_ptr<F> f;
shared_function() = delete; // = default works, but I don't use it
shared_function(F&& f_):f(std::make_shared<F>(std::move(f_))){}
shared_function(shared_function const&)=default;
shared_function(shared_function&&)=default;
shared_function& operator=(shared_function const&)=default;
shared_function& operator=(shared_function&&)=default;
template<class...As>
auto operator()(As&&...as) const {
return (*f)(std::forward<As>(as)...);
}
};
template<class F>
shared_function< std::decay_t<F> > make_shared_function( F&& f ) {
return { std::forward<F>(f) };
}
now that the above is done, we can solve your problem.
auto pi = std::make_unique<int>(0);
auto foo = [q = std::move(pi)] {
*q = 5;
std::cout << *q << std::endl;
};
std::function< void() > test = make_shared_function( std::move(foo) );
test(); // prints 5
The semantics of a shared_function
is slightly different than other functions, as a copy of it shares the same state (including when turned into a std::function
) as the original.
We can also write a move-only fire-once function:
template<class Sig>
struct fire_once;
template<class T>
struct emplace_as {};
template<class R, class...Args>
struct fire_once<R(Args...)> {
// can be default ctored and moved:
fire_once() = default;
fire_once(fire_once&&)=default;
fire_once& operator=(fire_once&&)=default;
// implicitly create from a type that can be compatibly invoked
// and isn't a fire_once itself
template<class F,
std::enable_if_t<!std::is_same<std::decay_t<F>, fire_once>{}, int> =0,
std::enable_if_t<
std::is_convertible<std::result_of_t<std::decay_t<F>&(Args...)>, R>{}
|| std::is_same<R, void>{},
int
> =0
>
fire_once( F&& f ):
fire_once( emplace_as<std::decay_t<F>>{}, std::forward<F>(f) )
{}
// emplacement construct using the emplace_as tag type:
template<class F, class...FArgs>
fire_once( emplace_as<F>, FArgs&&...fargs ) {
rebind<F>(std::forward<FArgs>(fargs)...);
}
// invoke in the case where R is not void:
template<class R2=R,
std::enable_if_t<!std::is_same<R2, void>{}, int> = 0
>
R2 operator()(Args...args)&&{
try {
R2 ret = invoke( ptr.get(), std::forward<Args>(args)... );
clear();
return ret;
} catch(...) {
clear();
throw;
}
}
// invoke in the case where R is void:
template<class R2=R,
std::enable_if_t<std::is_same<R2, void>{}, int> = 0
>
R2 operator()(Args...args)&&{
try {
invoke( ptr.get(), std::forward<Args>(args)... );
clear();
} catch(...) {
clear();
throw;
}
}
// empty the fire_once:
void clear() {
invoke = nullptr;
ptr.reset();
}
// test if it is non-empty:
explicit operator bool()const{return (bool)ptr;}
// change what the fire_once contains:
template<class F, class...FArgs>
void rebind( FArgs&&... fargs ) {
clear();
auto pf = std::make_unique<F>(std::forward<FArgs>(fargs)...);
invoke = +[](void* pf, Args...args)->R {
return (*(F*)pf)(std::forward<Args>(args)...);
};
ptr = {
pf.release(),
[](void* pf){
delete (F*)(pf);
}
};
}
private:
// storage. A unique pointer with deleter
// and an invoker function pointer:
std::unique_ptr<void, void(*)(void*)> ptr{nullptr, +[](void*){}};
void(*invoke)(void*, Args...) = nullptr;
};
which supports even non-movable types via the emplace_as<T>
tag.
live example.
Note you have to evaluate ()
in an rvalue context (ie, after a std::move
), as a silent destructive ()
seemed rude.
This implementation does not use SBO, for if it did it would demand that the type stored be movable, and it would be more work (for me) to boot.
Solution 3:
Here's a simpler solution:
auto pi = std::make_unique<int>(0);
auto ppi = std::make_shared<std::unique_ptr<int>>(std::move(pi));
std::function<void()> bar = [ppi] {
**ppi = 5;
std::cout << **ppi << std::endl;
};
Live example here