Come creare una funzione std :: da un’espressione lambda in movimento?

Sto provando a creare una std::function da un’espressione lambda che cattura lo spostamento. Nota che posso creare un’espressione lambda in grado di catturare le mosse senza problemi; è solo quando provo a racchiuderlo in una std::function che ho un errore.

Per esempio:

 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); 

Spiegherò perché voglio scrivere qualcosa del genere. Ho scritto una libreria UI che, simile a jQuery o JavaFX, consente all’utente di gestire eventi mouse / tastiera passando std::function s a metodi con nomi come on_mouse_down() , on_mouse_drag() , push_undo_action() , ecc.

Ovviamente, la std::function che voglio passare dovrebbe idealmente usare un’espressione lambda che cattura lo spostamento, altrimenti ho bisogno di ricorrere al brutto idioma “release / acquire-in-lambda” che stavo usando quando C ++ 11 era il standard:

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

Nota che chiamare baz due volte sarebbe un errore nel codice sopra. Tuttavia, nel mio codice, questa chiusura è garantita per essere chiamata esattamente una volta.

A proposito, nel mio vero codice, non sto passando uno std::unique_ptr , ma qualcosa di più interessante.

Infine, sto usando Xcode6-Beta4 che usa la seguente versione di 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);

Richiede: F deve essere CopyConstructible . f deve essere Callable per i tipi di argomenti ArgTypes e return type R Il costruttore di copie e il distruttore di A non generano eccezioni.

§20.9.11.2.1 [func.wrap.func.con]

Nota che operator = è definito in termini di questo costruttore e swap , quindi si applicano le stesse restrizioni:

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

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

§20.9.11.2.1 [func.wrap.func.con]

Quindi per rispondere alla tua domanda: Sì, è ansible build una std::function da un lambda che cattura lo spostamento (poiché questo specifica solo come cattura il lambda), ma non è ansible build una std::function da una mossa -solo (ad esempio un lambda che cattura la mossa che muove-cattura qualcosa che non è copiabile).

Poiché std::function Deve cancellare il costruttore della copia dell’object invocabile memorizzato, non puoi costruirlo da un tipo di spostamento. Il tuo lambda, poiché acquisisce un tipo di spostamento solo per valore, è un tipo di solo spostamento. Quindi … non puoi risolvere il tuo problema. std::function non può memorizzare il tuo lambda.

Almeno non direttamente.

Questo è C ++, ci aggiriamo semplicemente intorno al 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) }; } 

ora che quanto sopra è fatto, possiamo risolvere il tuo 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 semantica di una funzione shared_function è leggermente diversa rispetto ad altre funzioni, in quanto una copia di esso condivide lo stesso stato (incluso quando trasformato in una std::function come originale.

Possiamo anche scrivere una funzione fire-once di sola mossa:

 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; }; 

che supporta anche tipi non mobili tramite il emplace_as .

esempio dal vivo

Nota che devi valutare () in un contesto di rvalue (cioè, dopo un std::move ), come un silenzioso distruttivo () sembrava maleducato.

Questa implementazione non usa SBO, perché se lo facesse richiederebbe che il tipo memorizzato fosse mobile, e sarebbe più lavoro (per me) da avviare.