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




c++14 generic-lambda (3)

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.


¿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.


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

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





generic-lambda