c++ - Ist es möglich, generisches Lambda als Nicht-Template-Argument zu übergeben?




c++14 generic-lambda (3)

Ich habe ein Spielzeugbeispiel, das ich architektonisch ändern möchte, um die EmitterT des Processor von EmitterT zu entfernen:

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

Motivation

Ich möchte einen Teil, der für die Nutzung der Verarbeitungsergebnisse verantwortlich ist, von der Verarbeitung selbst entkoppeln. Die Emitter Klassenstruktur ist mir gegeben, daher muss ich überladene Funktionen unterstützen.

Ich möchte eine Lambda-Funktion an einen Prozessor übergeben, der sie verwendet. Ein bisschen wie ein Rückrufmechanismus, aber es muss ein generisches Lambda sein, um Überlastungen zu unterstützen.

Was ich versucht habe:

Das Beispiel, das ich geschrieben habe, funktioniert, hängt jedoch vom Emitter Typ als Vorlagenparameter ab. Ich mag es nicht, wenn sich der Processor basierend auf dem Emitter ändert. Es ist auch ansteckend, ich habe eine echte Processor und Emitter verbreiten sich wie const oder schlechter.

Nachdem ich https://stackoverflow.com/a/17233649/1133179 gelesen habe, habe ich versucht, mit der folgenden Struktur als Mitglied zu spielen:

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

Ich kann jedoch keine Möglichkeit finden, die Implementierung von Emitter nach Processor zu verschieben, wenn es als normaler Parameter verwendet wird. Es hat mit einer Forward-Deklaration und einer Referenz EmitterC& , unterstützt jedoch nur eine Emitter-Definition. Die einzige Möglichkeit, die mir EmitterC bestand darin, Lambda zu EmitterC und in EmitterC virtuelle Überladungen für jeden Typ EmitterC ich in Emitter erwarte, und ihn als Basisklasse zu verwenden.

Gibt es eine Möglichkeit, das (generische) Lambda als Parameter zu übergeben, damit der Processor nicht vom Emitter abhängt?

Ich bin auf C ++ 14 beschränkt, aber ich interessiere mich auch für modernere Standards, wenn sie besser unterstützt werden.


Ist es möglich, generisches Lambda als Nicht-Template-Argument zu übergeben?

Es ist nicht möglich, eine Nicht-Template-Funktion zu deklarieren, die ein Lambda als Argument akzeptiert. Die Art eines Lambdas ist anonym: Es hat keinen Namen. Es ist nicht möglich, eine Funktionsdeklaration zu schreiben, die ein Argument eines anonymen Typs akzeptiert.

Der Typ des Lambdas kann abgeleitet werden , weshalb Lambdas in Funktionsschablonen übergeben werden können, deren Argumenttypen abgeleitet werden.

Während dies die Frage beantwortet, bietet es keine Lösung. Ich denke nicht, dass eine Lösung einfach sein wird.


Diese einfachste Lösung besteht darin, Emitter einem zu process Parameter zu machen:

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

};

Wenn es sich jedoch um ein Mitglied des Processor und Sie die möglichen Funktionssignaturen auflisten können, können Sie eine Art von Löschung verwenden. std::function oder die vorgeschlagene std::function_ref nicht, da sie nur eine einzige Funktionssignatur zulassen. Wir können jedoch unsere eigene Funktion overloaded_function_ref schreiben:

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

Live-Beispiel

Dies erfordert C ++ 17 für die using /* base */::operator()... , dies kann jedoch in C ++ 14 emuliert werden. [P0195] Artikel, in dem dieses Feature vorgestellt wurde: [P0195] , oder vielleicht kann das match Boost HOF massiert werden, um dies zu tun. Dies ist auch nur eine Funktionsreferenz und keine Besitzfunktion.

Dann können wir schreiben:

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


Wenn Sie bereit sind, hohe Laufzeitkosten für minimale Einschränkungen zu zahlen, können Sie std::function mit std::any (für C ++ 14 verwenden Sie 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 und std::function besitzen jeweils gelöschte Wrapper. Möglicherweise verfügen Sie über Heap-Zuordnungen, oder Sie passen in die Optimierung kleiner Objekte. Sie haben virtuelle Funktionsaufrufe (oder gleichwertige).

Compiler-Explorer-Link





generic-lambda