Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Passing a non-copyable closure object to std::function parameter [duplicate]

Tags:

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)

like image 345
Joseph Mansfield Avatar asked Dec 30 '13 16:12

Joseph Mansfield


1 Answers

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.

like image 68
Yakk - Adam Nevraumont Avatar answered Oct 07 '22 21:10

Yakk - Adam Nevraumont