Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

template magic for wrapping C callbacks that take void* parameters?

Say I'm using a C API that lets you register callbacks that take a void* closure:

void register_callback(void (*func)(void*), void *closure);

In C++ it's nice to have stronger types than void* so I want to create a wrapper that lets me register strongly-typed C++ callbacks instead:

template <typename T, void F(T*)>
void CallbackWrapper(void *p) {
  return F(static_cast<T*>(p));
}

void MyCallback(int* param) {}

void f(void *closure) {
  register_callback(CallbackWrapper<int, MyCallback>, closure);
}

This works alright. One nice property of this solution is that it can inline my callback into the wrapper, so this wrapping scheme has zero overhead. I consider this a requirement.

But it would be nice if I could make the API look more like this:

void f2() {
  RegisterCallback(MyCallback, closure);
}

I hope I can achieve the above by inferring template parameters. But I can't quite figure out how to make it work. My attempt so far is:

template <typename T>
void RegisterCallback(void (*f)(T*), T* closure) {
  register_callback(CallbackWrapper<T, f>, closure);
}

But this doesn't work. Anyone have a magic incantation that will make f2() work above, while retaining the zero-overhead performance characteristic? I want something that will work in C++98.

like image 733
Josh Haberman Avatar asked May 17 '13 06:05

Josh Haberman


2 Answers

This template function improves the syntax marginally.

template <typename T, void F(T*)>
void RegisterCallback (T *x) {
    register_callback(CallbackWrapper<T, F>, x);
}

int x = 4;
RegisterCallback<int, MyCallback>(&x);

If you are willing to use a functor rather than a function to define your callback, then you can simplify things a bit more:

#ifdef HAS_EXCEPTIONS
# define BEGIN_TRY try {
# define END_TRY } catch (...) {}
#else
# define BEGIN_TRY
# define END_TRY
#endif

template <typename CB>
void CallbackWrapper(void *p) {
    BEGIN_TRY
    return (*static_cast<CB*>(p))();
    END_TRY
}

struct MyCallback {
    MyCallback () {}
    void operator () () {}
};

template <typename CB>
void RegisterCallback (CB &x) {
    register_callback(CallbackWrapper<CB>, &x);
}

MyCallback cb;
RegisterCallback(cb);

But, as others have mentioned, you run the risk of the code not porting correctly to a system where the C ABI and C++ ABI differ.

like image 73
jxh Avatar answered Sep 29 '22 13:09

jxh


I have discovered a better answer to this question than the other answers given to me here! (Actually it was another engineer inside Google who suggested it).

You have to repeat the function name twice, but that can be solved with a macro.

The basic pattern is:

// Func1, Func2, Func3: Template classes representing a function and its
// signature.
//
// Since the function is a template parameter, calling the function can be
// inlined at compile-time and does not require a function pointer at runtime.
// These functions are not bound to a handler data so have no data or cleanup
// handler.
template <class R, class P1, R F(P1)>
struct Func1 {
  typedef R Return;
  static R Call(P1 p1) { return F(p1); }
};

// ...

// FuncSig1, FuncSig2, FuncSig3: template classes reflecting a function
// *signature*, but without a specific function attached.
//
// These classes contain member functions that can be invoked with a
// specific function to return a Func/BoundFunc class.
template <class R, class P1>
struct FuncSig1 {
  template <R F(P1)>
  Func1<R, P1, F> GetFunc() { return Func1<R, P1, F>(); }
};

// ...

// Overloaded template function that can construct the appropriate FuncSig*
// class given a function pointer by deducing the template parameters.
template <class R, class P1>
inline FuncSig1<R, P1> MatchFunc(R (*f)(P1)) {
  (void)f;  // Only used for template parameter deduction.
  return FuncSig1<R, P1>();
}

// ...

// Function that casts the first parameter to the given type.
template <class R, class P1, R F(P1)>
R CastArgument(void *c) {
  return F(static_cast<P1>(c));
}

template <class F>
struct WrappedFunc;

template <class R, class P1, R F(P1)>
struct WrappedFunc<Func1<R, P1, F> > {
  typedef Func1<R, void*, CastArgument<R, P1, F> > Func;
};

template <class T>
generic_func_t *GetWrappedFuncPtr(T func) {
  typedef typename WrappedFunc<T>::Func Func;
  return Func().Call;
}

// User code:

#include <iostream>

typedef void (generic_func_t)(void*);

void StronglyTypedFunc(int *x) {
  std::cout << "value: " << *x << "\n";
}

int main() {
  generic_func_t *f = GetWrappedFuncPtr(
      MatchFunc(StronglyTypedFunc).GetFunc<StronglyTypedFunc>());
  int x = 5;
  f(&x);
}

This is not short or simple, but it is correct, principled, and standard-compliant!

It gets me what I want:

  • The user gets to write StronglyTypedFunc() taking a pointer to a specific thing.
  • This function can be called with a void* argument.
  • There is no virtual function overhead or indirection.
like image 28
Josh Haberman Avatar answered Sep 29 '22 11:09

Josh Haberman