una tipos pasos paso parametros parametro para funciones funcion ejemplos declara como asignar c++ lambda move-semantics c++14

c++ - tipos - Pasar un objeto de cierre no copiable a std:: function parámetro



pasos para asignar un parametro en c++ (3)

En C ++ 14, una expresión lambda puede capturar variables moviéndose desde ellas utilizando inicializadores de captura. Sin embargo, esto hace que el objeto de cierre resultante no se pueda copiar. Si tengo una función existente que toma un argumento std::function (que no puedo cambiar), no puedo pasar el objeto de cierre, porque el constructor de std::function requiere que el functor dado sea 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; }); }

Esto da los siguientes errores:

/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; ^

¿Hay una solución razonable?

Probando con Ubuntu clang versión 3.5-1 ~ exp1 (troncal)


Hay este enfoque:

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

donde hacemos un puntero compartido a nuestros datos, luego hacemos un lambda copiable que captura ese puntero compartido, luego envolvemos ese lambda copiable en una std::function de la firma solicitada.

En su caso anterior, usted simplemente:

doit( make_copyable_function<void()>( [p = std::move(p)] () { std::cout << *p << std::endl; } ) );

Una versión ligeramente más avanzada difiere el borrado de tipo y agrega una capa de reenvío perfecto para reducir la sobrecarga:

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

lo que no requiere que usted pase la firma, y ​​puede ser un poco más eficiente en algunos casos, pero utiliza técnicas más oscuras.

En C ++ 14 con esto se puede hacer aún más breve:

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

Eliminar la necesidad del tipo de ayudante por completo.


Si la vida útil del objeto de cierre no es un problema, puede pasarlo en un contenedor de referencia:

int main() { std::unique_ptr<int> p(new int(5)); auto f = [p = std::move(p)]{ std::cout << *p << std::endl; }; doit(std::cref(f)); }

Obviamente, esto no se aplica a todos los escenarios, pero está bien para su programa de ejemplo.

EDITAR: Echando un vistazo a N3797 (borrador de trabajo de C ++ 14) § 20.9.11.2.1 [func.wrap.func.con] p7, el requisito CopyConstructible todavía está allí. Me pregunto si hay una razón técnica que no se puede aflojar a MoveConstructible , o si el comité simplemente no lo logró.

EDIT: Respondiendo a mi propia pregunta: la std::function es CopyConstructible , por lo que el functor envuelto también necesita ser CopyConstructible .


Si sabe que realmente no va a copiar su objeto de función, entonces puede envolverlo en un tipo que haga que el compilador piense que se puede copiar:

struct ThrowOnCopy { ThrowOnCopy() = default; ThrowOnCopy(const ThrowOnCopy&) { throw std::logic_error("Oops!"); } ThrowOnCopy(ThrowOnCopy&&) = default; ThrowOnCopy& operator=(ThrowOnCopy&&) = default; }; template<typename T> struct FakeCopyable : ThrowOnCopy { FakeCopyable(T&& t) : target(std::forward<T>(t)) { } FakeCopyable(FakeCopyable&&) = default; FakeCopyable(const FakeCopyable& other) : ThrowOnCopy(other), // this will throw target(std::move(const_cast<T&>(other.target))) // never reached { } template<typename... Args> auto operator()(Args&&... a) { return target(std::forward<Args>(a)...); } T target; }; template<typename T> FakeCopyable<T> fake_copyable(T&& t) { return { std::forward<T>(t) }; } // ... doit( fake_copyable([p = std::move(p)] () { std::cout << *p << std::endl; }) );

La plantilla de función fake_copyable crea un contenedor que es CopyConstructible acuerdo con el compilador (y <type_traits> ) pero no se puede copiar en tiempo de ejecución.

Si almacena un FakeCopyable<X> en una std::function y luego termina copiando la std::function obtendrá un std::logic_error lanzado, pero si solo mueve la std::function todo funcionará bien.

El target(std::move(const_cast<T&>(other.target))) parece preocupante, pero ese inicializador nunca se ejecutará, porque el inicializador de clase base se lanzará primero. Así que el preocupado const_cast nunca sucede realmente, solo mantiene contento al compilador.