¿Cómo crear una función std :: a partir de una expresión lambda que captura movimiento?

Estoy intentando crear una std::function partir de una expresión lambda que captura movimiento. Tenga en cuenta que puedo crear una expresión lambda de captura de movimiento sin problemas; es solo cuando bash envolverlo en una std::function que me sale un error.

Por ejemplo:

 auto pi = std::make_unique(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 bar = foo; std::function bar{foo}; std::function bar{std::move(foo)}; std::function bar = std::move(foo); std::function bar{std::forward<std::function>(foo)}; std::function bar = std::forward<std::function>(foo); 

Explicaré por qué quiero escribir algo como esto. Escribí una biblioteca UI que, similar a jQuery o JavaFX, permite al usuario manejar eventos de mouse / teclado pasando std::function s a métodos con nombres como on_mouse_down() , on_mouse_drag() , push_undo_action() , etc.

Obviamente, la std::function que quiero pasar idealmente debería usar una expresión lambda que captura movimiento, de lo contrario tendré que recurrir a la desagradable expresión “liberar / adquirir-en-lambda” que estaba usando cuando C ++ 11 era el estándar:

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

Tenga en cuenta que llamar a baz dos veces sería un error en el código anterior. Sin embargo, en mi código, se garantiza que este cierre se llamará exactamente una vez.

Por cierto, en mi código real, no estoy pasando un std::unique_ptr , pero es algo más interesante.

Finalmente, estoy usando Xcode6-Beta4 que usa la siguiente versión de 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 

template function(F f);

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

Requiere: F debe ser CopyConstructible . f debe ser Callable para los tipos de argumento ArgTypes y el tipo de retorno R El constructor de copia y el destructor de A no lanzarán excepciones.

§20.9.11.2.1 [func.wrap.func.con]

Tenga en cuenta que operator = se define en términos de este constructor y swap , por lo que se aplican las mismas restricciones:

template function& operator=(F&& f);

Efectos: function(std::forward(f)).swap(*this);

§20.9.11.2.1 [func.wrap.func.con]

Entonces, para responder a su pregunta: Sí, es posible construir una std::function partir de una lambda de captura de movimiento (ya que esto solo especifica cómo captura lambda), pero no es posible construir una std::function desde un movimiento -solo tipo (por ejemplo, un movimiento que captura lambda que mueve-captura algo que no es copiable).

Como std::function Tiene que escribir-borrar el constructor de copia del objeto invocable almacenado, no puede construirlo desde un tipo de solo movimiento. Su lambda, porque captura un tipo de movimiento solo por valor, es un tipo de solo movimiento. Entonces … no puedes resolver tu problema. std::function no puede almacenar su lambda.

Al menos no directamente.

Esto es C ++, simplemente enrutamos el problema.

 template struct shared_function { std::shared_ptr f; shared_function() = delete; // = default works, but I don't use it shared_function(F&& f_):f(std::make_shared(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 auto operator()(As&&...as) const { return (*f)(std::forward(as)...); } }; template shared_function< std::decay_t > make_shared_function( F&& f ) { return { std::forward(f) }; } 

ahora que lo anterior está hecho, podemos resolver su problema.

 auto pi = std::make_unique(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 

La semántica de una función shared_function es ligeramente diferente de otras funciones, ya que una copia comparte el mismo estado (incluso cuando se convierte en una std::function como el original.

También podemos escribir una función de disparar solo una vez:

 template struct fire_once; template struct emplace_as {}; template struct fire_once { // 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, fire_once>{}, int> =0, std::enable_if_t< std::is_convertible&(Args...)>, R>{} || std::is_same{}, int > =0 > fire_once( F&& f ): fire_once( emplace_as>{}, std::forward(f) ) {} // emplacement construct using the emplace_as tag type: template fire_once( emplace_as, FArgs&&...fargs ) { rebind(std::forward(fargs)...); } // invoke in the case where R is not void: template{}, int> = 0 > R2 operator()(Args...args)&&{ try { R2 ret = invoke( ptr.get(), std::forward(args)... ); clear(); return ret; } catch(...) { clear(); throw; } } // invoke in the case where R is void: template{}, int> = 0 > R2 operator()(Args...args)&&{ try { invoke( ptr.get(), std::forward(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 void rebind( FArgs&&... fargs ) { clear(); auto pf = std::make_unique(std::forward(fargs)...); invoke = +[](void* pf, Args...args)->R { return (*(F*)pf)(std::forward(args)...); }; ptr = { pf.release(), [](void* pf){ delete (F*)(pf); } }; } private: // storage. A unique pointer with deleter // and an invoker function pointer: std::unique_ptr ptr{nullptr, +[](void*){}}; void(*invoke)(void*, Args...) = nullptr; }; 

que admite incluso tipos no móviles a través de la emplace_as .

ejemplo en vivo

Tenga en cuenta que tiene que evaluar () en un contexto rvalue (es decir, después de un std::move ), como un destructivo silencioso () parecía grosero.

Esta implementación no usa SBO, ya que si lo hiciera, exigiría que el tipo almacenado sea movible, y sería más trabajo (para mí) arrancar.