How to add standard routine to every function in array of function pointers?

Viewed 122

I'm creating an application in C++ for an embedded system. Therefore I'm tied to some C libraries, and do not have the freedom to use a lot of std functionality.

I have a callback signature:

typedef int (*callback_fn)(int a, int b);

A list with pointers to the registered callbacks:

callback_fn my_callback_functions[10];

And a function which adds callbacks to this list:

void add_callback(int function_iterator, callback_fn fn)
{
    my_callback_functions[function_iterator] = fn;
}

The callbacks then get passed to this library, and it calls the callbacks upon some events.

Now I'd like to add a little routine to every callback, so I can reset a timer every time a call has been made:

myTimer.reset();

How can I add this to every call without changing the signature, or moving away from function pointers (which I need to interact with the library)?

I tried some things with templates but couldn't find the right way to make this work.

3 Answers

Define a struct with a static function in it. This will be one of your callbacks. The names of the structs don't matter, just that the name of the function itself must be constant. For example:

struct MyCallback1 {
  static int callback(int a, int b) {
     //..
  }
};

Now we can write our wrapper:

template<typename T>
int decorated_callback(int a, int b) {
   myTimer.reset();
   return T::callback(a, b);
}

And register it as callback:

add_callback(0, &decorated_callback<MyCallback1>);

Let's start naive, then add sophistication.

The simplest way to add your routine would be to edit the callback function. That is, if one of your callbacks is fun1(), you could add the line myTimer.reset(); to the beginning of the function body.

Except... maybe not every call to this function is supposed to reset the timer (don't forget to allow for future changes). We can be less naive and create a wrapper function that resets the timer then calls fun1(). The callbacks can call this wrapper, while other invocations use the original function.

int wrap1(int a, int b)
{
    myTimer.reset();
    return fun1(a, b);
}

Except... I'm lazy. I don't want to write a wrapper for each and every callback. I'll just hand that task off to someone else, someone who won't complain about my laziness, someone like gcc (or whatever your compiler of choice is). I'd rather write a template showing how to write this wrapper, then let the compiler generate wrappers as needed. The one thing that varies is the callback function, so that becomes the template argument.

template <callback_fn F>
int wrap(int a, int b)
{
    myTimer.reset();
    return F(a, b);
}

Now you can add your callbacks with lines like the following.

    add_callback(0, wrap<fun1>);
    add_callback(1, wrap<fun2>);

This could be generalized to handle pointer types other than callback_fn, but I'll hold off on over-engineering a solution. If someone needs even more sophistication, that person could always ask a new question that references this one.

Another approach occurred to me. In a related question, a restriction was added that the call site not be changed. This got me to thinking about why someone would impose such a restriction. One potential motivation is to reduce the mental load on the callers of add_callback(). It would be more robust if callers did not have to remember to wrap their functions. If the inserted code is for debugging, this is not a big concern. However, if the inserted code is for production – perhaps it is part of performance monitoring – it could be a big deal if someone forgot the wrapper. So I started thinking about how the compiler could help.

In general, making things automatic is a reasonable goal. The less a programmer needs to remember, the fewer opportunities arise for mistakes. Even if the OP's motivation is different, I present this approach for future readers to consider.

In addition to a wrapper function template, one could change add_callback() to be a function template. Instead of passing the desired function as a function argument, it could be a template argument.
Caveat: This does mean the callback function has to be known at compile time; an expression like condition ? fun1 : fun2 would need to be expanded into a full-blown if statement.

The first piece of this approach is the same as in my earlier approach. Create a wrapper function template.

// (Could be private if there was a class encompassing this functionality.)
template <callback_fn F>
int wrap(int a, int b)
{
    myTimer.reset();
    return F(a, b);
}

Next, change add_callback to be a template with one template parameter and only one function parameter.

// The interface is now has one template parameter and one function parameter
// instead of two function parameters.
template <callback_fn F>
void add_callback(int function_iterator)
{
    my_callback_functions[function_iterator] = wrap<F>;
}

Finally, change the call sites. Existing calls look like add_callback(0, fun1), which will now be rejected by the compiler. There is no danger of missing a call. The new syntax for adding a callback is the following.

    add_callback<fun1>(0);
    add_callback<fun2>(1);
Related