Suppose we have a function accepting a C-style callback function like this:
void do_something(void (*callback)()) { ... callback(); }
As captureless C++ lambda functions can be cast to regular function pointers, the following works as expected:
auto callback=[](){std::cout<<"callback called\n";};
do_something(callback);
output: callback called
Unfortunately , if our callback code captures some variable from the context, we are out of luck
int num_callbacks=0;
···
auto callback=[&](){
std::cout<<"callback called "<<++num_callbacks<<" times \n";
};
do_something(callback);
error: cannot convert 'main()::<lambda>' to 'void (*)()'
because capturing lambda functions create a closure of the used context that needs to be carried around to the point of invocation. If we are allowed to modify do_something we can easily circumvent the problem by accepting a more powerful std::function-based callback:
void do_something(std::function<void()> callback)
{
...
callback();
}
int num_callbacks=0;
...
auto callback=[&](){
std::cout<<"callback called "<<++num_callbacks<<" times \n";
};
do_something(callback);
output: callback called 1 times
but we want to explore the challenge when this is not available (maybe because do_something is legacy C code, or because we do not want to incur the runtime penalty associated with std::function's usage of dynamic memory). Typically, C-style callback APIs accept an additional callback argument through a type-erased void*:
void do_something(void(*callback)(void*),void* callback_arg) { ... callback(callback_arg); }
and this is actually the only bit we need to force our capturing lambda function through do_something. The gist of the trick is passing the lambda function as the callback argument and providing a captureless thunk as the callback function pointer:
int num_callbacks=0;
...
auto callback=[&](){
std::cout<<"callback called "<<++num_callbacks<<" times \n";
};
auto thunk=[](void* arg){ // note thunk is captureless
(*static_cast<decltype(callback)*>(arg))();
};
do_something(thunk,&callback);
output: callback called 1 times
Note that we are not using dynamic memory nor doing any extra copying of the captured data, since callback is accessed in the point of invocation through a pointer; so, this technique can be advantageous even if modern std::functions could be used instead. The caveat is that the user code must make sure that captured data is alive when the callback is invoked (which is not the case when execution happens after scope exit if, for instance, it is carried out in a different thread).
Postcript
Tcbrindle poses the issue of lambda functions casting to function pointers with C++ linkage, where C linkage may be needed. Although this is rarely a problem in practice, it can be solved through another layer of indirection:
extern "C" void do_something(
void(*callback)(void*),void* callback_arg)
{
...
callback(callback_arg);
}
...
using callback_pair=std::pair<void(*)(void*),void*>;
extern "C" void call_thunk(void * arg)
{
callback_pair* p=static_cast<callback_pair*>(arg);
p->first(p->second);
}
...
int num_callbacks=0;
...
auto callback=[&](){
std::cout<<"callback called "<<++num_callbacks<<" times \n";
};
auto thunk=[](void* arg){ // note thunk is captureless
(*static_cast<decltype(callback)*>(arg))();
};
callback_pair p{thunk,&callback};
do_something(call_thunk,&p);
output: callback called 1 times
Great post! I was planning on writing a relatedpoat, but the clever thunk approach is even better than I had planned. It would be cool to generalize/genericize this to callback that take other arguments besides the void* user_data (as most do).
ReplyDeleteI am not sure that I understand the reasons behind this technique. If I read your code correctly, you are passing capturing lambda instance as an argument to non-capturing lambda. Why not pass the state (things you want to capture) directly, or create a little struct for them? It would be so much more readable.
ReplyDeleteAlso, wen you are interfacing with old code that uses function pointers, you have rarely the comfort of passing arbitrary arguments to your callback. Usually the signature is given and there is no way to pass additional stuff.
As an example, I am working with GLFW that defines bunch of callbacks using function pointers. One of the callbacks is defines as:
typedef void(* GLFWmousebuttonfun) (GLFWwindow *, int, int, int)
In this case you end up using globals.
Yes, you can pass the context in a little ad-hoc structure, but this probably requires more boilerplate code than the technique I present in the post; as for readability, I find this more readable (at the end, lambda functions are provided precisely to elminate context-holding structs), but your mileage may vary.
DeleteWhen your callback framework does not accept any void* (or similar type-erased arg) where you can pass additional context for the callback function, you're out of luck and have to resort to something else.
That's exactly my use case. I need to pass a non-static member function as a callback to GLUT. It accepts no callback argument, and the function itself should accept no arguments (it's void (*)(void) ).
DeleteI CANNOT make the function static, as it NEEDS access to data that differs from one object instance to another.
I also can't just refer to the object instance statically, e.g. in a global variable, because there are multiple instances and each one might use its own member function as the callback from time to time.
So, what I need is something that will create, during runtime, a "wrapper" as a function with no arguments, that will call the member function on a specific instance. Of course those will need to be two separate global functions (let's say I have two instances), because there would be no other way for them to "know" which object to work with other than to "embed" the reference to it in the function's code. (That's also why there would NEED to be 2 functions at 2 different addresses - they NEED to result in different void(*)() pointers).
I thought the combination of std::bind / std::function does exactly that. But when tried calling target<>() on it, it returned a null pointer, basically saying the function object is NOT compatible with void(*)(void).
So, how do I solve this? I know for sure that languages like Java, C# etc do exactly that - each closure (anonymous interface with implementation, and similar things for Java) creates its own copy, with its own address (reference) and data. Why does C++ simply not work the same?
This cannot do in portable C++ as far as I know. Take a look at Win32 thunking mechanisms such as very crelary described at http://www.codeguru.com/cpp/misc/misc/assemblylanguage/article.php/c12667/Thunking-in-Win32.htm
DeleteThank you for the great post.
DeleteThere is a minor typo in your code that makes the compiler unhappy.
std::function is missing parentheses after void.
void do_something(std::function callback)
{
...
callback();
}
Thank you Hamed! Fixed.
DeleteHi Joaquín,
ReplyDeleteThank you very much for the awesome post! It helped me a lot...