c++ - usar - no se puede convertir expresión lambda en el tipo delegado
¿Cómo crear una función std:: a partir de una expresión lambda que captura movimiento? (2)
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 intento envolverlo en una std::function
que me sale un error.
Por ejemplo:
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);
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<void()> baz = [q = pi.release()] {
std::unique_ptr<int> 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<int>
, 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<class F> function(F f);
template <class F, class A> function(allocator_arg_t, const A& a, F f);
Requiere:
F
debe serCopyConstructible
.f
debe serCallable
para los tipos de argumentoArgTypes
y el tipo de retornoR
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<class F> function& operator=(F&& f);
Efectos:
function(std::forward<F>(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<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) };
}
ahora que lo anterior está hecho, podemos resolver su problema.
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
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<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;
};
que admite incluso tipos no móviles a través de la emplace_as<T>
.
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.