Some C API interfaces use callbacks. Often these callbacks have a user-defined parameter of type void *
which can be used as a class instance pointer in C++. Usually in order to call the member function you need to write a static class function which would cast the void *
parameter to an instance pointer and call the member function. I wanted to write a wrapper that would instantiate a static function which takes void *
as a parameter and the rest of arguments is exactly like in the member function passed to wrapper.
template<
typename Result,
typename Function,
auto Func
>
struct instance_call_helper;
template<
typename Result,
typename Base,
typename... Args,
auto Func
>
struct instance_call_helper<
Result,
Result(Base::*)(Args...),
Func
> {
static Result call(void * ref, Args ... args) {
Base * instance = reinterpret_cast<Base *>(ref);
return (instance->*Func)(std::forward<Args>(args)...);
}
};
template <
typename Base
> struct instance_type_helper;
template <
typename Result, typename Base, typename... Args
>
struct instance_type_helper<Result(Base::*)(Args...)> {
using result_t = Result;
};
template<auto Func>
#if __cpp_concepts >= 201507
requires std::is_member_function_pointer<decltype(Func)>::value
#endif
class instance_call {
using functor_t = decltype(Func);
using type_helper = instance_type_helper<functor_t>;
public:
using helper = instance_call_helper<
typename type_helper::result_t,
functor_t,
Func
>;
};
Intended usage:
class Bar {
private:
int m_a1;
public:
Bar(int a1):
m_a1(a1) {}
int baz(int a2) {
return m_a1 + a2;
}
};
int foo() {
Bar b { 3 };
// Pointer we are going to pass somewhere as a callback
// f has signature int (*)(void*, int)
auto * f = &instance_call<&Bar::baz>::helper::call;
// Intended way of calling it — returns 5
return f(&b, 2);
}
1 Answer 1
After using the code for a while I noticed the following changes could be used:
I don't need
instance_type_helper
class. Result type will be taken out of the function type. Thus,instance_call_helper
turns into following:template< typename Function, Function F > struct instance_call_helper; template< typename Result, typename Base, typename... Args, Result(Base::* Func)(Args...) > struct instance_call_helper< Result(Base::*)(Args...), Func >
In order to use this code with GCC 6, one can check the value of
__cpp_deduction_guides
macro:#if __cpp_deduction_guides >= 201606 template< auto F > #else template< typename Function, Function F > #endif
For use with C code (it's not portable, as noticed in the comments), some more advanced typechecking can be used for arguments, like
std::is_fundamental
orstd::is_pod
instance_call<&Bar::baz>::helper::call
is a member function, as such C does not know how to call it. Now on a lot of platforms the calling convention for static member functions is the same as the calling convention for C functions; but there is no guarantee of this in any standard and thus UB. \$\endgroup\$extern "C" <type> func(void*)
\$\endgroup\$void *
for additional user data becausestd::function
andstd::bind
can't be used due to space constraints. \$\endgroup\$noexcept
or convert exceptions into form suitable for particular C library. \$\endgroup\$