Working with function pointer families

In C++, the number and types of parameters and the return type of a function all contribute to the uniqueness of the function pointer type. A pointer to a function that takes one int argument and returns nothing is a distinct type from a pointer to a function that takes one double argument and returns nothing. In most cases, when you're using a function pointer you know exactly the function pointer type — perhaps you're writing a graphing library and know that you're accepting functions f(x) = y. In this case you know the function pointer type will be double (*func)(double).

Here, you want to discriminate based on the function pointer type since a function f(x, y) = z would be graphed differently from the f(x) = y type. In other cases, you may just know that the end result is a double and not really care how many arguments or even what type of argument they are. This is a situation a friend of mine recently asked me about. At the beginning I didn't know whether he was using C or C++, only that he did not want to use variadic functions. It's possible to use variadic functions for this purpose, but then the functions you're passing must be constructed specifically for this application. It would be much better to accept any already existing function without the need to rewrite it.

Since I didn't know whether he was using C++ and had access to templates, I suggested that he may be able to do it if he can nail down the exact calling convention. This friend works with CUDA a bit, and I figured it may have been something specifically for this application where it may be possible to know the calling convention and manually implement it through inline assembler. Obviously this is very brittle, and who wants to work with inline assembler? :p

Luckily, he access to C++ and templates. "C++ templates better than inline assembler," I hear you cry. Darn tootin'! :p Anyway, he did not have access to C++11 variadic templates, but what he wanted to accomplish is still possible. The basic idea is to write the function as an overloaded template. Different overloads would be used depending on the number of arguments to the passed function. For the sake of discussion, let's call our function foo. We want foo to be able to accept a function pointer and the a list of arguments to that function. We can construct foo like this:

// 0-arity case, the passed function takes no arguments
template<typename Func>
void foo(Func func) {
    double v = func();
    // ... other code

// urnary function
template<typename Func, typename A1>
void foo(Func func, A1 arg1) {
    double v = func(arg1);
    // ...

// binary function
template<typename Func, typename A1, typename A2>
void foo(Func func, A1 arg1, A2 arg2) {
    double v = func(arg1, arg2);
    // ...

// and so on

All of these functions require the result type of Func to be implicitly castable/assignable to double. If the result of the passed function is not needed, then it can be made to impose no requirement on the possible Func types.

This method, while needing an overload per function arity you're willing to accept, does have the benefit of working with types that are not actually function pointers. Here, Func could just as easily be a class which overloads the parenthesis operator appropriately. Or, it could be a lambda or the std::function wrapper. Just in case that's unacceptable, you can do something like:

// arbitrary return type
template<typename R, typename A1, typename A2>
void foo(R (*func)(A1, A2), A1 arg1, A2 arg2) {
    double r = func(arg1, arg2);
    // ...

// or force the return type to be double, instead of just assignable to double
template<typename A1, typename A2>
void foo(double (*func)(A1, A2), A1 arg1, A2 arg2) {
    double r = func(arg1, arg2);
    // ...

This whole shebang could be simplified with the use of variadic templates. Without them, one must write an overload per acceptable function arity. With variadic templates, this all collapses to:

template<typename Func, typename... Args>
void foo(Func func, Args... args) {
    double v = func(args...);
    // ... <- not to be confused with the above ellipses :p

Disclaimer: I typed this all in in one shot without testing any of the functions. There may be compile errors. I'm still developing the actual "add a post" feature to my site, so most likely there will be fun markup glitches or who knows what else. Not sure about the code highlighting theme either... I tend to use light themes myself. If you see any errors, typographical or otherwise, please feel free to shoot me an email (listed at the bottom).

originally posted 2014-02-08