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);
}
\$\endgroup\$
  • \$\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\$ – Martin York Nov 12 '17 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\$ – Martin York Nov 12 '17 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\$ – John Doe Nov 12 '17 at 12:20
  • 2
    \$\begingroup\$ These aren't varargs, it's template parameter packs. \$\endgroup\$ – John Doe Nov 12 '17 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\$ – VTT Nov 13 '17 at 8:22
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

\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.