1
\$\begingroup\$

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);
}
asked Nov 12, 2017 at 10:45
\$\endgroup\$
5
  • \$\begingroup\$ None of this is portable. Remember C is a different language. The only thing a C library understands are C things. The only thing C has is functions. This 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\$ Commented Nov 12, 2017 at 12:17
  • \$\begingroup\$ When you get into var args you are way of reservation. The only thing you can portably pass to a C library (from C++) is an extern "C" <type> func(void*) \$\endgroup\$ Commented Nov 12, 2017 at 12:20
  • \$\begingroup\$ Right. However, I plan to use this on a platform where the calling convention is the same. Apart from C, I also have C++ code where callbacks use void * for additional user data because std::function and std::bind can't be used due to space constraints. \$\endgroup\$ Commented Nov 12, 2017 at 12:20
  • 2
    \$\begingroup\$ These aren't varargs, it's template parameter packs. \$\endgroup\$ Commented Nov 12, 2017 at 12:21
  • 1
    \$\begingroup\$ One thing that needs careful handling when dealing with C/C++ interop are exceptions. You should either mark callback function as noexcept or convert exceptions into form suitable for particular C library. \$\endgroup\$ Commented Nov 13, 2017 at 8:22

1 Answer 1

1
\$\begingroup\$

After using the code for a while I noticed the following changes could be used:

  1. 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
    >
    
  2. 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
    
  3. 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 or std::is_pod

answered Nov 15, 2017 at 6:28
\$\endgroup\$

Your Answer

Draft saved
Draft discarded

Sign up or log in

Sign up using Google
Sign up using Email and Password

Post as a guest

Required, but never shown

Post as a guest

Required, but never shown

By clicking "Post Your Answer", you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.