In C++14, a lambda expression can capture variables by moving from them using capture initializers. However, this makes the resulting closure object non-copyable. If I have an existing function that takes a std::function
argument (that I cannot change), I cannot pass the closure object, because std::function
's constructor requires the given functor to be CopyConstructible
.
#include <iostream> #include <memory> void doit(std::function<void()> f) { f(); } int main() { std::unique_ptr<int> p(new int(5)); doit([p = std::move(p)] () { std::cout << *p << std::endl; }); }
This gives the following errors:
/usr/bin/../lib/gcc/x86_64-linux-gnu/4.8/../../../../include/c++/4.8/functional:1911:10: error: call to implicitly-deleted copy constructor of '<lambda at test.cpp:10:7>' new _Functor(*__source._M_access<_Functor*>()); ^ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ /usr/bin/../lib/gcc/x86_64-linux-gnu/4.8/../../../../include/c++/4.8/functional:1946:8: note: in instantiation of member function 'std::_Function_base::_Base_manager<<lambda at test.cpp:10:7> >::_M_clone' requested here _M_clone(__dest, __source, _Local_storage()); ^ /usr/bin/../lib/gcc/x86_64-linux-gnu/4.8/../../../../include/c++/4.8/functional:2457:33: note: in instantiation of member function 'std::_Function_base::_Base_manager<<lambda at test.cpp:10:7> >::_M_manager' requested here _M_manager = &_My_handler::_M_manager; ^ test.cpp:10:7: note: in instantiation of function template specialization 'std::function<void ()>::function<<lambda at test.cpp:10:7>, void>' requested here doit([p = std::move(p)] () { std::cout << *p << std::endl; }); ^ test.cpp:10:8: note: copy constructor of '' is implicitly deleted because field '' has a deleted copy constructor doit([p = std::move(p)] () { std::cout << *p << std::endl; }); ^ /usr/bin/../lib/gcc/x86_64-linux-gnu/4.8/../../../../include/c++/4.8/bits/unique_ptr.h:273:7: note: 'unique_ptr' has been explicitly marked deleted here unique_ptr(const unique_ptr&) = delete; ^
Is there a reasonable workaround?
Testing with Ubuntu clang version 3.5-1~exp1 (trunk)
There is this approach:
template< typename signature > struct make_copyable_function_helper; template< typename R, typename... Args > struct make_copyable_function_helper<R(Args...)> { template<typename input> std::function<R(Args...)> operator()( input&& i ) const { auto ptr = std::make_shared< typename std::decay<input>::type >( std::forward<input>(i) ); return [ptr]( Args... args )->R { return (*ptr)(std::forward<Args>(args)...); }; } }; template< typename signature, typename input > std::function<signature> make_copyable_function( input && i ) { return make_copyable_function_helper<signature>()( std::forward<input>(i) ); }
where we make a shared pointer to our data, then make a copyable lambda that captures that shared pointer, then we wrap that copyable lambda into a std::function
of the requested signature.
In your case above, you'd just:
doit( make_copyable_function<void()>( [p = std::move(p)] () { std::cout << *p << std::endl; } ) );
A slightly more advanced version defers the type erasure and adds a layer of perfect forwarding to reduce overhead:
template<typename input> struct copyable_function { typedef typename std::decay<input>::type stored_input; template<typename... Args> auto operator()( Args&&... args )-> decltype( std::declval<input&>()(std::forward<Args>(args)...) ) { return (*ptr)(std::forward<Args>(args)); } copyable_function( input&& i ):ptr( std::make_shared<stored_input>( std::forward<input>(i) ) ) {} copyable_function( copyable_function const& ) = default; private: std::shared_ptr<stored_input> ptr; }; template<typename input> copyable_function<input> make_copyable_function( input&& i ) { return {std::forward<input>(i)}; }
which does not require you to pass the signature in, and can be slightly more efficient in a few cases, but uses more obscure techniques.
In C++14 with this can be made even more brief:
template< class F > auto make_copyable_function( F&& f ) { using dF=std::decay_t<F>; auto spf = std::make_shared<dF>( std::forward<F>(f) ); return [spf](auto&&... args)->decltype(auto) { return (*spf)( decltype(args)(args)... ); }; }
doing away with the need for the helper type entirely.
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