Search code examples
c++c++11void-pointersstd-functionstdbind

std::bind to void* to std::function


I'm trying to make an event system The user of this system have to execute this to add an event for input for e.g:

addEventHandler(Event::KEYBOARD_INPUT, reinterpret_cast<void*>(&std::bind(&Game::On_Input, this)));

the function passed gonna be called when the event is fired, these functions are stored in std::vectors till now there is no problem.But there is different function signatures for different events.. and since std::function is a type itself I can't let this function accept all events I have tried to cast the std::bind to void* then cast it back to std::function but it wont work since impl is undefined (0xCCCCCCCC)

The current code:

void EventManager::addEventHandler(Event event, void* p_Fun)
{
    if (event == Event::KEYBOARD_INPUT) {
        m_InputCallbacks.push_back(*reinterpret_cast<std::function<void(Keycode, unsigned int)>*>(p_Fun));
    }
    /*else if(...){ // other events other push backs in other vectors

    }*/
}

and the function stored called this way :

void EventManager::HandleEvents(SDL_Event& Event)
{
    while (PollEvent(&Event) != 0) {
        if (Event.type == KEYDOWN || Event.type == KEYUP) {
            //On_Input(Event.key.keysym.sym, Event.type);
            for (auto inputCallbackFunc : m_InputCallbacks) {
                inputCallbackFunc(Event.key.keysym.sym, Event.type);//CRASH HERE!
            }
        }
    }
}

Please note that this version only handles input I don't know how many events gonna be added in the future (Collision, multiplayer connections, etc..) that's why i'd like to automate the proccess throught addEventHandler function which will check the following: 1) whats the event type and cast the void function pointer to that event call back function signature 2) store them in the appropriate std::vector

So all in all how to make AddEventHandler accept every std::bind being passed to it that can be stored and called later?


Solution

  • std::function provides all the type erasure you need to store a std::vector of a single type that has the signature that you will be calling it with. There is no need to be casting to void*

    To keep it simple, something like this is what you want.

    #include <iostream>
    #include <vector>
    #include <functional>
    #include <type_traits>
    using namespace std;
    
    enum Event
    {
        KEYBOARD_INPUT,
        MOUSE_INPUT
    };
    
    
    std::vector< std::function<void(int,int)> > keyboard_handlers;
    std::vector< std::function<void(int,int,int)> > mouse_handlers;
    
    
    
    template <Event EventType,typename Func,
         typename std::enable_if<EventType==KEYBOARD_INPUT,bool>::type=true>
    void addEventHandler(Func&& func)
    {
    keyboard_handlers.push_back(
            std::function<void(int,int)>(std::forward<Func>(func)));
    }
    
    template <Event EventType,typename Func,
         typename std::enable_if<EventType==MOUSE_INPUT,bool>::type=true>
    void addEventHandler(Func&& func)
    {
    mouse_handlers.push_back(
            std::function<void(int,int,int)>(std::forward<Func>(func)));
    }
    
    void call_keyboard_handlers(int a,int b)
    {
        for (auto inputCallbackFunc : keyboard_handlers) {
            inputCallbackFunc(a,b);
        }
    }
    
    
    void keyboard_callback_1(std::string name, int a , int b)
    {
        std::cout << "keyboard_callback_1 " << name << " " << a << " " << b << std::endl;
    }
    
    void keyboard_callback_2(int a , int b)
    {
        std::cout << "keyboard_callback_2 "  << a << " " << b << std::endl;
    }
    
    struct keyboard_callback_3_type
    {
        void operator()(int a,int b)
        {
                std::cout << "keyboard_callback_3 "  << a << " " << b << std::endl;
        }
    };
    
    int main() {
        //With an extra bound in string argument.
        addEventHandler<KEYBOARD_INPUT>(std::bind(&keyboard_callback_1,"somestring",std::placeholders::_1,std::placeholders::_2));
        //From a plain function pointer 
        addEventHandler<KEYBOARD_INPUT>(&keyboard_callback_2);
    
        //From a memberfunction pointer
        keyboard_callback_3_type keyboard_callback_3;
        addEventHandler<KEYBOARD_INPUT>(std::bind(&keyboard_callback_3_type::operator(),&keyboard_callback_3,std::placeholders::_1,std::placeholders::_2));
    
        //From a non capturing lambda
        addEventHandler<KEYBOARD_INPUT>([](int a,int b){ std::cout << "lambda_callback " << a << " " <<  b <<std::endl;});
    
        std::string capture = "Captured";
        //From a capturing lambda
        addEventHandler<KEYBOARD_INPUT>([&](int a,int b){ std::cout << "lambda_callback " << capture << " " << a << " " <<  b <<std::endl;});
    
    
        call_keyboard_handlers(10,20);
    
        return 0;
    }
    

    Demo

    Note, if you want to keep the function name as addEventHandler you will have to enable different variants of the function based on the event type (as in the example), or name them differently (ie addKeyboardEventHandler, addMouseEventHandler etc..)