c++ lambda c++14 generic-lambda

c++ - ¿Es posible pasar lambda genérico como argumento sin plantilla?



c++14 generic-lambda (4)

¿Es posible pasar lambda genérico como argumento sin plantilla?

No es posible declarar una función que no sea de plantilla que acepte un lambda como argumento. El tipo de lambda es anónimo: no tiene nombre. No es posible escribir una declaración de función que acepte un argumento de tipo anónimo.

Se puede deducir el tipo de lambda, razón por la cual se pueden pasar lambdas a plantillas de función cuyos tipos de argumento se deducen.

Si bien esto responde a la pregunta, no ofrece una solución. No creo que una solución sea simple.

Tengo un ejemplo de juguete que me gustaría modificar arquitectónicamente para eliminar la dependencia del tipo de Processor en EmitterT :

#include <iostream> #include <utility> using namespace std; struct Emitter { void e(int) { cout << "emitting int/n";} void e(double) { cout << "emitting double/n";} void e(char*) { cout << "emitting char*/n";} void e(const char*) { cout << "emitting const char*/n";} }; template <typename EmitterT> struct Processor { Processor(EmitterT e) : e_{e} {} template <typename T> void process(T&& value) { cout << "some processing... "; e_(std::forward<T>(value)); } EmitterT e_; }; template<typename Emitter_> Processor<Emitter_> makeProcessor(Emitter_ e) { return Processor<Emitter_>(e);} int main() { Emitter em; auto p = makeProcessor([&em](auto v){em.e(v);}); p.process(1); p.process("lol"); return 0; }

Motivación

Me gustaría desacoplar a la parte responsable de utilizar los resultados del procesamiento del procesamiento mismo. La estructura de la clase Emitter me es dada, así que tengo que soportar funciones sobrecargadas.

Me gustaría pasar una función lambda a un procesador que la usará. Algo así como un mecanismo de devolución de llamada, sin embargo, debe ser un lambda genérico, para soportar sobrecargas.

Lo que probé:

El ejemplo que escribí funciona, pero depende del tipo de Emitter como parámetro de plantilla. No me gusta que el tipo de Processor cambie según el Emitter . También es contagioso, tengo una jerarquía de Processor real y el Emitter propaga como const o peor.

Después de leer https://stackoverflow.com/a/17233649/1133179 , he intentado jugar con la siguiente estructura como miembro:

struct EmitterC { template<typename T> void operator()(T value) { } };

Pero no puedo encontrar una manera de diferir la implementación de Emitter después del Processor cuando lo uso como un parámetro normal. Funcionó con una declaración directa y un EmitterC& referencia, pero solo admite una definición de Emitter. La única forma en que se me ocurrió fue abandonar lambda y realizar sobrecargas virtuales en EmitterC para cada tipo que espero en Emitter y usarlo como una clase base.

Entonces, ¿hay alguna manera de pasar el lambda (genérico) como parámetro, de modo que el tipo de Processor no dependa del Emitter ?

Estoy restringido a c ++ 14, pero también estoy interesado en estándares más modernos si tienen un mejor soporte.


En mi humilde opinión: Herencia está aquí para eso.

#include <iostream> #include <utility> using namespace std; struct BaseEmitter { virtual void e(int) =0; virtual void e(double)=0; virtual void e(char*)=0; virtual void e(const char*)=0; }; struct Emitter :public BaseEmitter { virtual void e(int) { cout << "emitting int/n";} virtual void e(double) { cout << "emitting double/n";} virtual void e(char*) { cout << "emitting char*/n";} virtual void e(const char*) { cout << "emitting const char*/n";} }; struct Processor { BaseEmitter& e_; Processor(BaseEmitter& e) : e_(e) {} template <typename T> void process(T&& value) { cout << "some processing... "; e_(std::forward<T>(value)); } }; int main() { Emitter em; auto p = Processor(em); p.process(1); p.process("lol"); return 0; }

Puede hacer una mezcla para capturar el lambda, simplemente por herencia en la interfaz:

struct bypass { virtual void operator()() = 0; }; template<typename callable> struct capture: public bypass { callable& _ref; capture(callable &ref) : _ref(ref) {;}; virtual void operator()() { _ref(); } }; struct test { bypass *_c; template<class T> test(T& callback) : _c(nullptr) { _c = new capture<decltype(callback)>(callback); }; void doit() { (*_c)(); } }; int main(int argc, char* argv[]) { auto lambda = [](){std::cout << "hello/n";}; test z=test(lambda); z.doit(); return 0; }


Esta solución más simple es hacer que Emitter un parámetro para process :

struct Processor { template <typename T, typename EmitterFn> void process(T&& value, EmitterFn emit) { cout << "some processing... "; emit(std::forward<T>(value)); } };

Sin embargo, si debe ser miembro de Processor y puede enumerar las posibles firmas de funciones, puede usar algún tipo de borrado de tipo. std::function o el std::function_ref propuesto no funcionará porque solo permiten una firma de función única, pero podemos escribir nuestra propia overloaded_function_ref :

template <typename Derived, typename Sig> class function_ref_impl; template <typename Derived, typename R, typename... Args> class function_ref_impl<Derived, R(Args...)> { using fn_t = R(*)(void const*, Args...); public: auto operator()(Args... args) const -> R { return fn(static_cast<Derived const&>(*this).object, std::forward<Args>(args)...); } protected: template <typename F, std::enable_if_t<!std::is_base_of<function_ref_impl, F>::value, int> = 0> explicit function_ref_impl(F const& f) : fn{[](void const* self, Args... args) -> R { return (*static_cast<F const*>(self))(std::forward<Args>(args)...); }} {} private: fn_t fn; }; template <typename... Sig> class overloaded_function_ref : public function_ref_impl<overloaded_function_ref<Sig...>, Sig>... { public: template <typename F, std::enable_if_t<!std::is_base_of<overloaded_function_ref, F>::value, int> = 0> overloaded_function_ref(F const& f) : function_ref_impl<overloaded_function_ref, Sig>(f)... , object{std::addressof(f)} {} // Can be done pre-C++17, but it''s not easy: using function_ref_impl<overloaded_function_ref, Sig>::operator()...; // This can be encapsulated with techniques such as the "passkey" idiom. // Variadic friend expansion isn''t a thing (`friend bases...`). void const* object; };

Ejemplo en vivo

Esto requiere C ++ 17 para using /* base */::operator()... , pero eso se puede emular en C ++ 14; vea el documento que introdujo esta característica: [P0195] , o tal vez la [P0195] Boost HOF se puede masajear para hacer esto. Esto también es solo una referencia de función y no una función propietaria.

Entonces podemos escribir:

struct Processor { template <typename T> void process(T&& value) { cout << "some processing... "; emit(std::forward<T>(value)); } using emitter_t = overloaded_function_ref< void(int), void(double), void(char*), void(char const*) >; emitter_t emit; };

Demo


Si está dispuesto a pagar un alto costo de tiempo de ejecución a cambio de restricciones mínimas, puede usar std::function con std::any (para C ++ 14, use boost::any ):

#include <iostream> #include <utility> #include <any> #include <functional> struct Processor { Processor(std::function<void(std::any)> e) : e_{e} {} template <typename T> void process(T&& value) { std::cout << "some processing... "; e_(std::forward<T>(value)); } std::function<void(std::any)> e_; }; struct Emitter { void e(int) { std::cout << "emitting int/n";} void e(double) { std::cout << "emitting double/n";} void e(char*) { std::cout << "emitting char*/n";} void e(const char*) { std::cout << "emitting const char*/n";} }; int main() { Emitter em; auto p = Processor( [&em](std::any any){ // This if-else chain isn''t that cheap, but it''s about the best // we can do. Alternatives include: // - Hashmap from `std::type_index` (possibly using a perfect hash) // to a function pointer that implements this. // - Custom `any` implementation which allows "visitation": // // any.visit<int, double, char*, char const*>([&em] (auto it) { // em.e(it); // }); if (auto* i = std::any_cast<int>(&any)) { em.e(*i); } else if (auto* d = std::any_cast<double>(&any)) { em.e(*d); } else if (auto* cstr = std::any_cast<char*>(&any)) { em.e(*cstr); } else { em.e(std::any_cast<char const*>(any)); } } ); p.process(1); p.process("lol"); return 0; }

std::any y std::function poseen envoltorios borrados de tipo ambos. Puede tener asignaciones de montón para esto, o puede encajar dentro de su optimización de objetos pequeños. Tendrá llamadas a funciones virtuales (o equivalentes).

Enlace del compilador Explorer