How can I pass a C++ lambda to a C-callback that expects a function pointer and a context?
The simple aporoach is to stick the lambda into a std::function<void()>
which is kept somewhere. Potentially it is allocated on the heap and merely referenced by the void*
registered with the entity taking the callback. The callback would then simply be a function like this:
extern "C" void invoke_function(void* ptr) {
(*static_cast<std::function<void()>*>(ptr))();
}
Note that std::function<S>
can hold function objects with state, e.g., lambda functions with a non-empty capture. You could register a callback like this:
register_callback(&invoke_function,
new std::function<void()>([=](){ ... }));
The most efficient way is to voidify
the lambda directly.
#include <iostream>
#include <tuple>
#include <memory>
template<typename... Args, typename Lambda>
std::pair< void(*)(void*, Args...), std::unique_ptr<void, void(*)(void*)> > voidify( Lambda&& l ) {
typedef typename std::decay<Lambda>::type Func;
std::unique_ptr<void, void(*)(void*)> data(
new Func(std::forward<Lambda>(l)),
+[](void* ptr){ delete (Func*)ptr; }
);
return {
+[](void* v, Args... args)->void {
Func* f = static_cast< Func* >(v);
(*f)(std::forward<Args>(args)...);
},
std::move(data)
};
}
void register_callback( void(*function)(void*), void * p ) {
function(p); // to test
}
void test() {
int x = 0;
auto closure = [&]()->void { ++x; };
auto voidified = voidify(closure);
register_callback( voidified.first, voidified.second.get() );
register_callback( voidified.first, voidified.second.get() );
std::cout << x << "\n";
}
int main() {
test();
}
here voidify
takes a lambda and (optionally) a list of arguments, and generates a traditional C-style callback-void*
pair. The void*
is owned by a unique_ptr
with a special deleter so its resources are properly cleaned up.
The advantage of this over a std::function
solution is efficiency -- I eliminated one level of run-time indirection. The lifetime that the callback is valid is also clear, in that it is in the std::unique_ptr<void, void(*)(void*)>
returned by voidify
.
unique_ptr<T,D>
s can be move
d into shared_ptr<T>
if you want a more complex lifetime.
The above mixes lifetime with data, and type erasure with utility. We can split it:
template<typename... Args, typename Lambda>
std::pair< void(*)(void*, Args...), std::decay_t<Lambda> > voidify( Lambda&& l ) {
typedef typename std::decay<Lambda>::type Func;
return {
+[](void* v, Args... args)->void {
Func* f = static_cast< Func* >(v);
(*f)(std::forward<Args>(args)...);
},
std::forward<Lambda>(l)
};
}
Now voidify
does not allocate. Simply store your voidify for the lifetime of the callback, passing a pointer-to-second
as your void*
along side the first
function pointer.
If you need to store this construct off the stack, converting the lambda to a std::function
may help. Or use the first variant above.
A lambda function is compatible with C-callback function as long as it doesn't have capture variables.
Force to put something new to old one with new way doesn't make sense.
How about following old-fashioned way?
typedef struct
{
int cap_num;
} Context_t;
int cap_num = 7;
Context_t* param = new Context_t;
param->cap_num = cap_num; // pass capture variable
register_callback([](void* context) -> void {
Context_t* param = (Context_t*)context;
std::cout << "cap_num=" << param->cap_num << std::endl;
}, param);