Search code examples
c++eventscallbackevent-handlingevent-dispatching

C++ Event Dispatcher - callback casting problem


I'm venturing into creating an EventDispatcher using C++ 17 in Visual Studio 2022. Basically I store a string (id) to identify the event and a callback (lambda, function or method of a class) in a vector through the RegisterEvent() method. Then I need to call the DispatchEvent() method passing the event as a parameter. This event receives the event id as a parameter. Internally the DispatchEvent() method loops to find the event with the corresponding id and executes the previously stored callback passing the event to the callback. When using the generic Event event, everything works perfectly. (Just comment out all the content inside the doCustomEvents() method;

PROBLEM

When creating an event derived from the generic event, in this example CustomEvent it fails to perform the casting and issues the following error:

Error C2440 : <function-style-cast>': cannot convert from 'initializer list' to 'EventProps' : LINE 41

HERE -> m_EventProps.push_back(EventProps(id, callback));

I've been studying a lot about casting, but nothing I've seen so far has been able to help me solve this problem.

Could someone please help me?

Thank you very much!

Follow the code below:

#include <iostream>
#include <string>
#include <vector>
#include <functional>

#define DEBUG(x) std::cout << x << std::endl;
#define BIND(fn) [this](auto...args) -> decltype(auto) { return this->fn(args...); }

class Event
{
public:
    std::string Id;
    Event(std::string id) : Id(id) { }
    std::string ToString() { return "Event(id=" + Id +")"; }
};

class CustomEvent : public Event
{
public:
    std::string Message;
    CustomEvent(std::string id, std::string message) : Event(id), Message(message) { }
    std::string ToString() { return "CustomEvent(id=" + Id + ", message=" + Message + ")"; }
};

struct EventProps
{
public:
    std::string Id;
    std::function<void(Event)> Callback;
    EventProps(std::string id, std::function<void(Event)> callback) : Id(id), Callback(callback) { }
};

class EventDispatcher
{
private:
    std::vector<EventProps> m_EventProps;
public:
    template<typename T>
    void RegisterEvent(std::string id, std::function<void(T)> callback)
    {
        m_EventProps.push_back(EventProps(id, callback));
    }

    void DispatchEvent(Event event)
    {
        for (int i = 0; i < m_EventProps.size(); i++)
        {
            EventProps props = m_EventProps[i];
            if(props.Id == event.Id) props.Callback(event);
        }
    }
};

void globalCallback(Event e) { DEBUG("[Global] " + e.ToString()); }
void customGlobalCallback(CustomEvent ce) { DEBUG("[Global] " + ce.ToString()); }

class Application
{
public:
    EventDispatcher dispatcher;

    Application()
    {   
        doEvents();
        doCustomEvents(); // Nothing works here
    }

    void doEvents()
    {
        dispatcher.RegisterEvent<Event>("event_0", [](Event e) { DEBUG("[Lambda] " + e.ToString()); });
        dispatcher.RegisterEvent<Event>("event_1", globalCallback);
        dispatcher.RegisterEvent<Event>("event_2", BIND(Application::OnEvent));

        dispatcher.DispatchEvent(Event("event_0"));
        dispatcher.DispatchEvent(Event("event_1"));
        dispatcher.DispatchEvent(Event("event_2"));
    }

    void doCustomEvents()
    {
        dispatcher.RegisterEvent<CustomEvent>("custom_0", [](CustomEvent e) { DEBUG("[Lambda] " + e.ToString()); });
        dispatcher.RegisterEvent<CustomEvent>("custom_1", customGlobalCallback);
        dispatcher.RegisterEvent<CustomEvent>("custom_2", BIND(Application::OnCustomEvent));

        dispatcher.DispatchEvent(CustomEvent("custom_0", "Hi custom 0"));
        dispatcher.DispatchEvent(CustomEvent("custom_1", "Hi custom 1"));
        dispatcher.DispatchEvent(CustomEvent("custom_2", "Hi custom 2"));
    }

    void OnEvent(Event e) { DEBUG("[Application::OnEvent] " + e.ToString()); }
    void OnCustomEvent(CustomEvent ce) { DEBUG("[Application::CustomEvent] " + ce.ToString()); }
};

int main()
{
    Application app;
}

Solution

  • This question is probably on the edge of what people would normally close, but MSVC is making your life harder by giving a really useless error message here. (You might want to try upgrading your compiler; recent MSVC on godbolt.org give much more helpful messages.)

    The problem is with

    dispatcher.RegisterEvent<CustomEvent>("custom_0", [](CustomEvent e) { DEBUG("[Lambda] " + e.ToString()); });
    

    and the following lines.

    dispatcher.RegisterEvent<CustomEvent> takes a second argument of type std::function<void(CustomEvent)> and then proceeds to use it to try to construct an EventProps. However what the constructor of EventProps wants is a std::function<void(Event)>. There is no way for the compiler to convert a function that only accepts CustomEvents into a function that accepts all Events.

    You need to either template EventProps itself, or adapt RegisterEvent to convert a function taking events of type T into a function taking all Events.