0

I'm creating a GLFWKeyCallback and because of how simple it is I've decided to use a lambda. This callback modifies a member variable, so I have to pass this into the capture list. Here is what my code looks like so far:

glfwSetKeyCallback(window, 
        [this](GLFWwindow* window, int key, int scancode, int action, int mods)
        {
            if(action == GLFW_PRESS)
            {
                 //use a mutex
                 //Modify member variable
            }
        });

The problem is that whenever I pass this into the capture list, Visual Studio 2019 displays the following error:

no suitable conversion function from "lambda [] void (GLFWwindow *window, int key, int scancode, int action, int mods)->void" to GLFWKeyfun" exists

Have I missed something or is this code just invalid?

SuperSim135
  • 135
  • 1
  • 10
  • 1
    `glfwSetKeyCallback` doesn't take a lambda - it takes a plain old function pointer. A capture-less lambda is convertible to a function pointer; a lambda with captures is not (there's no place to store captured data in). – Igor Tandetnik Feb 02 '20 at 00:35
  • 1
    Does this answer your question? [Passing C++ method as function pointer](https://stackoverflow.com/questions/59769243/passing-c-method-as-function-pointer). My answer there shows the messy nature of using a member function where a bare function is called for. – Spencer Feb 02 '20 at 00:38
  • @Spencer Yes, it's basically what Igor said. Is there another way to modify member variables using this lambda? – SuperSim135 Feb 02 '20 at 00:40
  • 3
    you can use `glfwSetWindowUserPointer` to give the window a user-defined pointer (like whatever `this` is) and use `glfwGetWindowUserPointer` to get it back from within the callback – kmdreko Feb 02 '20 at 00:44
  • 1
    I'd just finished composing an answer demonstrating `glfwSetWindowUserPointer` when the question closed :( Still, yes, setting and retrieving `this` as the window pointer is the correct method. – N. Shead Feb 02 '20 at 00:46
  • 1
    My answer there shows a way around your problem. The point is that you need an instance of the class to alter the member variable. A lambda with captures will never work -- it's a class created on the fly with the function pointer and the capture values as data members. – Spencer Feb 02 '20 at 00:46

2 Answers2

6

The GLFW callbacks don't take lambdas, or function objects: they take plain old function pointers. A non-capturing lambda can be converted to a function pointer, but not a capturing one.

However, you can get a similar effect by using glfwSetUserPointer and glfwGetUserPointer. The lambda still can't be capturing, but you can recover the this pointer.

For example,

struct MyClass {
  GLFWwindow* window;

  MyClass(GLFWwindow* window) : window(window) {
    glfwSetWindowUserPointer(window, static_cast<void*>(this));

    glfwSetKeyCallback(window, 
      [](GLFWwindow* window, int key, int scancode, int action, int mods) {
        auto self = static_cast<MyClass*>(glfwGetWindowUserPointer(window));
        // can access member variables through `self`
      });
  }

  // make sure that either the class will last as long as GLFW will
  // or clean up the user pointer and callbacks in here
  ~MyClass() {
    // clean up
  }

  // don't be able to copy, probably, or bad things will happen
  MyClass(const MyClass&) = delete;
  MyClass& operator=(const MyClass&) = delete;
  // other things...
};
N. Shead
  • 3,828
  • 1
  • 16
  • 22
  • How would I destroy the user pointer? Also, why can't the object be copied? – SuperSim135 Feb 02 '20 at 02:01
  • 1
    @SuperSim135 to clean up just set the user pointer back to `nullptr` or whatever, and same for the callbacks. The reason the object shouldn't be copyable is that the user pointer is, effectively, a global variable — there can only be one user pointer. Having multiple of the class is therefore problematic. – N. Shead Feb 02 '20 at 02:42
  • 1
    (That said, looking back over this, I neglected to mention that you can have a separate user pointer per `GLFWwindow*` instance. The class still shouldn't be copyable, but there can be more than one instance of the class to cope with e.g. multiple windows.) – N. Shead Feb 02 '20 at 21:33
  • There is a problem with your answer and that is that it only enables access to publicly accessible members. I think my real solution is to simply use another API or simplify the structure of my program. GLFW might not be the best option. – SuperSim135 Feb 04 '20 at 00:28
  • 1
    @SuperSim135 Of course, since the lambda isn't a member of your class. If you need to access private arguments you can just add a function in your class to do the work and get the lambda to just immediately call that function, using the `self` instance you pull out of the user pointer. – N. Shead Feb 04 '20 at 00:31
1

Adding onto the other answer.

I see glfwSetWindowUserPointer brought up a lot as a solution to this issue. It works fine (and I use it myself, since I don't know of any other solution), but it comes with a caveat that I haven't seen anyone mention:

You can only store one pointer per Window using this method. If some other code sets a different pointer to your window, all of a sudden your lambda won't work anymore. I can think of two workarounds here:

  1. When you retrieve the pointer in your lambda body, set it to a static variable. This way, it will persist across calls to the lambda, even if someone else sets a different pointer. Note: the static variable won't initialize until the first call to the lambda, so you'd be best to call the lambda once, yourself, after defining it.

  2. Define a object or map of pointers. Give GLFWSetUserPointer a pointer to that map. I can't think of any way to enforce this pattern, but if you have complete control over your app, you can store multiple pointers in associated with a Window this way.

  • This does not provide an answer to the question. Once you have sufficient [reputation](https://stackoverflow.com/help/whats-reputation) you will be able to [comment on any post](https://stackoverflow.com/help/privileges/comment); instead, [provide answers that don't require clarification from the asker](https://meta.stackexchange.com/questions/214173/why-do-i-need-50-reputation-to-comment-what-can-i-do-instead). - [From Review](/review/late-answers/30777729) – Shunya Jan 12 '22 at 15:36