Afshin
Afshin

Reputation: 9173

Generic C++ callback map, Is there any better way?

I'm trying to create a generalized a message handling a my code. Each message is identified by a integer id. Since all message handlers have similar deceleration and I like to handle each message quickly, I use a std::map to connect and find corresponding message handler for specific message ids. Then I call this handler and pass message to it. There are several was to do this and here is an example:

const std::map<int, void(*)(void*)> g_handlers = {
    {1, h1},
    {2, h2}
};

...
// message
int message_id = 2;
int data = 3;
// handle message
g_handlers[message_id](&data);

But there are few big limitation for this method:

  1. Since there are different messages, we need to generalize them by passing them as void* parameter. In this way, every message handler syntax will be void (*)(void*) and then we will be able to use it as value of map.
  2. There is no type checking for this message. If someone incorrectly add message handler of message id 1 for message id 2, we may not find this bug quickly.

I wanted to try something new, so I was trying to find a way to solve these problems and I have finally reached a working code. Here is the code:

class handler_base {
    public:
    template <typename U>
    void operator()(U* arg) {
        run(arg, typeid(U));
    }

    private:
    virtual void run(void* arg, const std::type_info& info) {}
};

template<typename T>
class handler : public handler_base {
    public:
    using type = T;
    handler(void (*f)(T*)) :func(f) {
    }

    private:
    void run(void* arg, const std::type_info& info) {
        assert(info.hash_code() == typeid(T).hash_code());
        func(static_cast<T*>(arg));
    }
    void (*func)(T*);
};

int main()
{
    // 2 different types of handlers
    handler h1(+[](double* v){ std::cout << "double called " << *v << "\n"; });
    handler h2(+[](int* v){ std::cout << "int called " << *v << "\n"; });

    const std::map<int, handler_base&> myhandler = {
        {1, h1},
        {2, h2}
    };

    double d = 1.5;
    int i = 3;

    myhandler.at(1)(&d);
    //myhandler.at(1)(&i);  // Error: failed assert due to type check
    //myhandler.at(2)(&d); // Error: failed assert due to type check
    myhandler.at(2)(&i);  
}

Now here are my question:

  1. Is using & as map value valid when map is const? I know it is not when map itself is not const but I wonder if it correct in this case or not.
  2. Is there any way simpler way to do this? providing different callback message handler syntax using same container with type checking?
  3. What do you think about this idea generally? Is it a good idea to add this complexity for type checking and heterogeneous callbacks? I personally always go for this rule of "simplicity is the best" and I normally select first approach (using generalized void(*)(void*) for callback), but I like to know what do you think about it.

Upvotes: 0

Views: 882

Answers (1)

Khal Buyo
Khal Buyo

Reputation: 317

I think you can completely skip the base class. You just store the function pointer directly as some function pointer for the round trip conversion. I also made it accept many parameters:

#include <unordered_map>
#include <iostream>
#include <cassert>

struct Handler
{
    template <typename T>
    Handler(T fn)
        : f((void(*)())(fn))
        , info(typeid(T))
    {
    }

    template <typename... Args>
    void operator()(Args&&... args)
    {
        using Fn = void(Args...);
        assert(info.hash_code() == typeid(Fn*).hash_code());
        return ((Fn*)(f))(std::forward<Args>(args)...);
    }
    void (*f)();
    const std::type_info& info;
};


int main()
{
    std::unordered_map<int, Handler> cbmap;
    cbmap.emplace(1, +[](int a, double b){std::cout << "1" << a << " " << b << "\n";});
    cbmap.emplace(2, +[](double a){std::cout << "2" << a << "\n";});
    cbmap.emplace(3, +[](double& a){std::cout << "3 " << a << "\n";});

    double x = 42.0;

    cbmap.at(1)(42,4.2);
    cbmap.at(2)(4.2);
    cbmap.at(3)(x);
}

Upvotes: 2

Related Questions