Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to create an std::function from a move-capturing lambda expression?

I'm trying to create an std::function from a move-capturing lambda expression. Note that I can create a move-capturing lambda expression without problems; it's only when I try to wrap it in an std::function that I get an error.

For example:

auto pi = std::make_unique<int>(0);  // no problems here! auto foo = [q = std::move(pi)] {     *q = 5;     std::cout << *q << std::endl; };  // All of the attempts below yield: // "Call to implicitly-deleted copy constructor of '<lambda...."  std::function<void()> bar = foo; std::function<void()> bar{foo}; std::function<void()> bar{std::move(foo)}; std::function<void()> bar = std::move(foo); std::function<void()> bar{std::forward<std::function<void()>>(foo)}; std::function<void()> bar = std::forward<std::function<void()>>(foo); 

I'll explain why I want to write something like this. I've written a UI library which, similar to jQuery or JavaFX, allows the user to handle mouse/keyboard events by passing std::functions to methods with names like on_mouse_down(), on_mouse_drag(), push_undo_action(), etc.

Obviously, the std::function I want to pass in should ideally use a move-capturing lambda expression, otherwise I need to resort to the ugly "release/acquire-in-lambda" idiom I was using when C++11 was the standard:

std::function<void()> baz = [q = pi.release()] {     std::unique_ptr<int> p{q};     *p = 5;     std::cout << *q << std::endl; }; 

Note that calling baz twice would be an error in the above code. However, in my code, this closure is guaranteed to be called exactly once.

BTW, in my real code, I'm not passing an std::unique_ptr<int>, but something more interesting.

Finally, I'm using Xcode6-Beta4 which uses the following version of clang:

Apple LLVM version 5.1 (clang-503.0.40) (based on LLVM 3.4svn) Target: x86_64-apple-darwin13.3.0 Thread model: posix 
like image 421
seertaak Avatar asked Aug 21 '14 08:08

seertaak


2 Answers

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.

like image 32
Yakk - Adam Nevraumont Avatar answered Oct 06 '22 01:10

Yakk - Adam Nevraumont


template<class F> function(F f);

template <class F, class A> function(allocator_arg_t, const A& a, F f);

Requires: F shall be CopyConstructible. f shall be Callable for argument types ArgTypes and return type R. 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).

like image 146
Robert Allan Hennigan Leahy Avatar answered Oct 06 '22 02:10

Robert Allan Hennigan Leahy