1
votes

I've been writing a utility to work as a wrapper for GLFW Key Input.

I wanted to have a set of functions that would enable me to easily pass the key code and the function I wanted to be triggered upon the key being pressed or released.

I think everything here is right but I'm attempting to use it in my Game class and I'm getting the following error:

game.cpp(27): error C2664: 'void KeyManager::press(int,void *)' : cannot convert argument 2 from 'void (__thiscall Game::* )(void)' to 'void *' 1> There is no context in which this conversion is possible

Game.cpp:

void Game::testKey()
{
    std::cout << "Key Up Pressed" << std::endl;
}

void Game::init()
{
    //  KEY MANAGER:
    KeyManager::initialize();
    KeyManager::press(GLFW_KEY_UP, &Game::testKey);

KeyManager.cpp:

#include <map>
#include <vector>

#include "global.h"
#include "key_manager.h"

namespace KeyManager
{
    std::map<int, int>                      keyAction;
    std::map<int, std::vector<void(*)()>>   pressFunctions;
    std::map<int, std::vector<void(*)()>>   releaseFunctions;

    static void key_callback(GLFWwindow* window, int key, int scancode, int action, int mods)
    {
        keyAction[key] = action;

        if (keyAction[key] == GLFW_PRESS)
        {
            for (int i = 0; i != pressFunctions[key].size(); ++i)
            {
                (*pressFunctions[key].at(i))();
            }
        }

        if (keyAction[key] == GLFW_RELEASE)
        {
            for (int i = 0; i != releaseFunctions[key].size(); ++i)
            {
                (*releaseFunctions[key].at(i))();
            }
        }
    }

    void initialize()
    {
        glfwSetKeyCallback(window, key_callback);
    }

    void press(int keyCode, void(*listener)())
    {
        pressFunctions[keyCode].push_back(listener);
    }

    void release(int keyCode, void(*listener)())
    {
        releaseFunctions[keyCode].push_back(listener);
    }

    bool held(int keyCode)
    {
        if (keyAction[keyCode] == GLFW_PRESS || keyAction[keyCode] == GLFW_REPEAT)
        {
            return true;
        } else {
            return false;
        }
    }
}
1

1 Answers

4
votes

When you take the address of a non-static member function you don't get back a "normal" pointer to function but rather a pointer to member function: Member functions do not just take the explicitly specified arguments as parameters but they also take an implicit pointer to the object (what becomes this) as parameter. Thus, the type of your Game::testKey is not void(*)() but it is rather void (Game::*)(), indicating that you still need to pass in a Game objects when calling this function.

If you can control the type of the functions being called, you are best off to use either a template argument for a function object or, if you need to store multiple of these object, a std::function<Signature> with a suitable type Signature, in your case probably void(): these objects can store an object and you can pass in the desired object, e.g.:

std::function<void()> fun(std::bind(&Game::testKey, this));

The std::bind() is a factory function taking a function as it first parameter and a set of arguments specifying the arguments the function should get when it is called. In the above example, the Game::testKey function gets the first argument bound to this (you can use whatever pointer to a Game object you want to use, of course).

When you don't really need an object to send the function to, you can also make the function static which results in a function which doesn't take an implicit object. Often the object is, however, actually required and just making the function static doesn't provide enough context.

Assuming that the Game object is needed for the context, the KeyManager would use std::function<void(char)> to report about key events (I also added a char parameter to get anof which key was used):

class KeyManager
{
    std::vector<std::function<void(char)>> handlers;
public:
    void addHandler(std::function<void(char)> h) {
        handlers.push_back(h);
    }
    //...
};

Later you can then add a handler function when you have a suitable Game object:

KeyManager m;
Game           g;
m.addHandler(std::bind(&Game::keyDown, &game));

Note that the Game::keyDown actually one uses the Gane object and no extra parameter: That's OK as the function object returned from std::bind() will just ignore additional arguments.