13

I have a toy example that I'd like to modify architecturally to remove type dependency of Processor on 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;
}

Motivation

I'd like to decouple part responsible for utilizing results of processing from the processing itself. The Emitter class structure is given to me, so I have to support overloaded functions.

I'd like to pass a lambda function to a processor that will use it. Kind of like a callback mechanism, however it must be a generic lambda, to support overloads.

What I've tried:

The example I wrote works, but it depends on Emitter type as a template parameter. I don't like Processor type to change based on Emitter. It's also contagious, I have a real Processor hierarchy and Emitter spread like const or worse.

After reading https://stackoverflow.com/a/17233649/1133179 I've tried playing with below struct as a member:

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

But I cannot figure out a way to defer implementation of Emitter after Processor when using it as a normal parameter. It worked out with a forward declaration and a reference EmitterC& but it supports one only Emitter definition. The only way I could come up with was to drop lambda, and make virtual overloads in EmitterC for every type I expect in Emitter and use it as a base class.

So, Is there a way to pass the (generic) lambda as a parameter, so that Processor type doesn't depend on Emitter?

I am restricted to c++14, but I am interested in more modern standards as well if the have better support.

max66
  • 65,235
  • 10
  • 71
  • 111
luk32
  • 15,812
  • 38
  • 62
  • 1
    In your code, there is no need to store emitter as a member, so you can simply accept it is a parameter as well. – SergeyA Jul 22 '19 at 15:59
  • You can make the constructor a templated function instead, and type-erase it into a `std::function` class member. – Sam Varshavchik Jul 22 '19 at 15:59
  • 3
    @SamVarshavchik: what would the signature of that `std::function` be? – Vittorio Romeo Jul 22 '19 at 16:03
  • The types of parameters the lambda takes, and its return value. – Sam Varshavchik Jul 22 '19 at 16:39
  • @SamVarshavchik But the lambda takes any type – NathanOliver Jul 22 '19 at 16:40
  • 1
    Type-erase the parameter to the lambda itself. At some point, somewhere, the type must be known, or determined someway. Nothing more can really be said about this, is is too vague, at this point. – Sam Varshavchik Jul 22 '19 at 16:43
  • I don't get it what is wrong with your current code? It is nicely decoupled. The only thing `Processor` knows about `EmitterT` it that it is inviolable with some value. There is no explicit dependency to `Emitter`. You can freely change implementation of `Emitter` and you do not have to touch `Processor`. – Marek R Jul 23 '19 at 13:47
  • @Marek `Processor` in my particular case is a templated class hierarchy having it's own responsibilities. Clients code works by providing template specializations to classes that should support processing. Adding a template parameter polluted everything and increased boilerplate in those specializations, plus it made full specializations partial specializations, which means that accessing base members need to be written as template dependent like [here](https://stackoverflow.com/questions/1120833/derived-template-class-access-to-base-class-member-data) so I got bunch of `these->` as well. – luk32 Jul 23 '19 at 14:57
  • @luk32 your question doesn't clearly specifies this problems. It would be nice to add API usage example which you wish to avoid (currently you have) and which you wish to have. – Marek R Jul 23 '19 at 15:16

4 Answers4

9

This simplest solution is to make Emitter a parameter to process:

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

};

However, if it must be a member of Processor and you can enumerate the possible function signatures, you can use some kind of type erasure. std::function or the proposed std::function_ref won't work because they only allow a single function signature, but we can write our own 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;
};

Live example

This does require C++17 for the using /* base */::operator()..., but that can be emulated in C++14; see the paper that introduced this feature: [P0195], or perhaps Boost HOF's match can be massaged to do this. This is also just a function reference and not an owning function.

Then we can write:

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

Justin
  • 24,288
  • 12
  • 92
  • 142
7

IMHO: Inheritance is here for that.

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

You can do a mix in order to capture the lambda, just by inheritance in the interface:

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);
        };
        virtual ~test()
        {
            delete _c;
        };
        void doit()
        {
            (*_c)();
        }

};



int main(int argc, char* argv[])
{
        auto lambda = [](){std::cout << "hello\n";};
        test z=test(lambda);
        z.doit();
        return 0;
}
  • This is a proposal to the motivation, not to the asked question. – Mel Viso Martinez Jul 22 '19 at 18:00
  • That is exactly what I described here: "*The only way I could come up with was to drop lambda, and make virtual overloads in EmitterC for every type I expect in Emitter and use it as a base class.*" So I've explicitly said I am not interested in this solution. I guess it's good to have it written for the completeness. – luk32 Jul 23 '19 at 10:07
  • 2
    It got some upvotes, so must have been useful to some people! – luk32 Jul 23 '19 at 10:26
  • I've added another idea, maybe this could be near to what you like? ;) – Mel Viso Martinez Jul 23 '19 at 13:29
  • `e_(std::forward(value));` do you mean `e_.e(std::forward(value));`? – Justin Jul 23 '19 at 18:05
3

If you are willing to pay a high runtime cost in exchange for minimal constraints, you can use std::function with std::any (for 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 and std::function are both owning type erased wrappers. You may have heap allocations for this, or you might fit inside their small object optimization. You will have virtual function calls (or equivalent).

Compiler Explorer link

Justin
  • 24,288
  • 12
  • 92
  • 142
2

Is it possible to pass generic lambda as non-template argument

It is not possible to declare a non-template function that accepts a lambda as an argument. The type of a lambda is anonymous: It has no name. It is not possible to write a function declaration that accepts an argument of an anonymous type.

The type of the lambda can be deduced, which is why lambdas can be passed into function templates whose argument types are deduced.

While this answers the question, it does not offer a solution. I don't think a solution is going to be simple.

eerorika
  • 232,697
  • 12
  • 197
  • 326
  • "It is not possible to declare a non-template function that accepts a lambda as an argument." Except you can have an implicit conversion to a non-template type, e.g. `std::function` or the upcoming `std::function_ref` – Justin Jul 22 '19 at 16:11
  • 1
    But that `std::function` wrapper can only be used to call one particular signature of the lambda, which is insufficient here... – Max Langhof Jul 22 '19 at 16:11
  • @MaxLanghof Hmm, yeah. Function wrapper might not be of use here. – eerorika Jul 22 '19 at 16:12
  • 2
    @MaxLanghof You could write your own which accepts multiple signatures, as long as you can enumerate the possible signatures. E.g. `overloaded_function_ref` – Justin Jul 22 '19 at 16:13
  • 1
    @Justin: sadly `std::function_ref` was delayed to C++23. – Vittorio Romeo Jul 22 '19 at 16:20
  • @VittorioRomeo Dang. I was looking forward to using it. Guess I'll have to keep using non-standard implementations for now. – Justin Jul 22 '19 at 16:22
  • @Justin Please make that an answer so we can give you proper credit for it! :) – Max Langhof Jul 22 '19 at 16:37
  • I knew that's it's tricky when I arrived at needing a virtual template. But I don't, in fact, require run-time polymorphism. So, I thought I am missing some way of declaring things because I was able to come up with an example that used forward declaration. This way the implementation of Emitter could be deferred but it supported only one Emitter, though the compiler had the same information as it would with a templated member, thus I got a feeling it might be possible, to have a generic functor and produce all the calls based on the client code without manually enumerating all the signatures. – luk32 Jul 23 '19 at 11:00