Another solution is to split the std::function
into a pointer to the closure and a pointer to the member function, and pass three things to the C function that wants to invoke the lambda:
- The address of a C++ function that knows how to invoke the function on the closure type-safely
- The closure pointer (unsafely cast to
void *
)
- The member function pointer (hidden inside a wrapper struct and cast to
void *
as well)
Here’s a sample implementation.
#include <functional>
#include <iostream>
template<typename Closure, typename Result, typename... Args>
struct MemberFunctionPointer
{
Result (Closure::*value)(Args...) const;
};
template<typename Closure, typename Result, typename... Args>
MemberFunctionPointer<Closure, Result, Args...>
member_function_pointer(
Result (Closure::*const value)(Args...) const)
{
return MemberFunctionPointer<Closure, Result, Args...>{value};
}
template<typename Closure, typename Result, typename... Args>
Result
call(
const void *const function,
const void *const closure,
Args... args)
{
return
((reinterpret_cast<const Closure *>(closure))
->*(reinterpret_cast<const MemberFunctionPointer<Closure, Result, Args...>*>(function)->value))
(std::forward<Args>(args)...);
}
Sample usage from the C side:
int
c_call(
int (*const caller)(const void *, const void *, int),
const void *const function,
const void *const closure,
int argument)
{
return caller (function, closure, argument);
}
Sample usage from the C++ side:
int
main()
{
int captured = 5;
auto unwrapped = [captured] (const int argument) {
return captured + argument;
};
std::function<int(int)> wrapped = unwrapped;
auto function = member_function_pointer(&decltype(unwrapped)::operator());
auto closure = wrapped.target<decltype(unwrapped)>();
auto caller = &call<decltype(unwrapped), int, int>;
std::cout
<< c_call(
caller,
reinterpret_cast<const void *>(&function),
reinterpret_cast<const void *>(closure),
10)
<< '\n';
}
The reason for the wrapper struct is that you can’t cast a member function pointer to void *
or any other object pointer type, not even with reinterpret_cast
, so instead we pass the address of the member function pointer. You can choose to place the MemberFunctionPointer
structure on the heap, e.g. with unique_ptr
, if it needs to live longer than it does in this simple example.
You can also wrap these three arguments in a single structure on the C side, rather than pass them individually:
struct IntIntFunction
{
int (*caller)(const void *, const void *, int);
const void *function;
const void *closure;
};
#define INVOKE(f, ...) ((f).caller((f).function, (f).closure, __VA_ARGS__))
int
c_call(IntIntFunction function)
{
return INVOKE(function, 10);
}