I am a bit confused by how std::function
is constructed at a given lambda. The constructor of std::function
is listed here. Which one is actually used to capture a lambda? Is it template< class F > function( F f );
? Looks like I can't construct std::function
with a lambda that captures non-copy-constructible objects. Why is this necessary for lambda capture?
// fu is an object of type std::future
std::function f = [future=std::move(fu)]() {...} // compile error
// foo is an object of type int
std::function f = [foo=std::move(foo)]() {...} // compile ok
The short answer is that the standard states only copyable function objects can be stored in a std::function
. This is unsatisfactory: why?
std::function
is a copyable type.
The standard states that when copied, it also copies its contents.
"But", you say, "I never copy it. Why should it need be copied?" An instance of std::function
records how to copy its contents even if it never does so. It typically uses a technique known as type erasure.
Here is a toy example:
struct invoke_later {
struct i_impl {
virtual ~i_impl() {}
virtual void invoke() const = 0;
virtual std::unique_ptr<i_impl> clone() const = 0;
};
template<class T>
struct impl:i_impl {
T t;
~impl() = default;
void invoke() const override {
t();
}
impl(T&& tin):t(std::move(tin)) {}
impl(T const& tin):t(tin) {}
virtual std::unique_ptr<i_impl> clone() const {
return std::make_unique<impl>(t);
};
};
std::unique_ptr<i_impl> pimpl;
template<class T,
// SFINAE suppress using this ctor instead of copy/move ctors:
std::enable_if_t< !std::is_same<std::decay_t<T>, invoke_later>{}, int>* =0
>
invoke_later( T&& t ):
pimpl( std::make_unique<impl<std::decay_t<T>>( std::forward<T>(t) ) )
{}
invoke_later(invoke_later&&)=default;
invoke_later(invoke_later const&o):
pimpl(o.pimpl?o.pimpl->clone():std::unique_ptr<i_impl>{})
{}
~invoke_later() = default;
// assignment goes here
void operator() const {
pimpl->invoke();
}
explicit operator bool() const { return !!pimpl; }
};
the above is a toy example of a std::function<void()>
.
The operation of copying is stored in the ->clone()
method of the pimpl
. It must be compiled even if you never call it.
The writers of the std
specification where aware of the above technique, and knew its limitations, and wanted to permit std::function
s to be implemented simply using it. In addition, they wanted simple operations on the std::function
to behave in predictable ways: with non-copyable contents, what should copying the std::function
do?
Note you can get around this issue by wrapping your state in a shared_ptr
. Then copyies of your std::function
will simply store shared references to your state, instead of copies.
template<class F>
auto shared_state( F&& f ) {
return [pf = std::make_shared<std::decay_t<F>>(std::forward<F>(f))]
(auto&&... args)->decltype(auto) {
return (*pf)(decltype(args)(args)...);
};
}
now:
std::function<Sig> f = shared_state([future=std::move(fu)]() {...});
will compile and work.
An alternative approach is to make a non-copyable std::function
and use that instead of std::function
.
Finally, whe working with future
, a shared_future
is a copyable future
type and may be cheaper than doing a shared_state
:
std::function<void()> f = [fu=fu.share()]{ /* code */ };
A lambda that captures a move-only object by value becomes itself move-only, which makes sense since it contains said object.
But std::function has to be copy-constructible and copy-assignable, which means that it can only contain copiable objects.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With