Using function templates with mbed's function templates (ie: EventQueue, EventQueue, etc)

04 Dec 2018

Templates in C++ are an awesome way to create functions with generic types and an even better way to overload functions. Unfortunately, a function template cannot be passed to mbed libraries that also implement this concept. A common example is the EventQueue's call function (as well as call_in, call_every). These functions are great examples of using function templates with function overloading, as well as a creative way to not duplicate overloaded code by using context. I suggest looking over EventQueue.h to understand how these concepts are being combined.

Because templates use generic types and are compiled on demand, the compiler cannot resolve their type when passing them to another function that also uses generic types. Depending on your compiler's output dialog, it will see the function as an <unresolved overloaded function type> or similar error. To overcome this, you simply have to create a typedef for the function you would like to pass in and then cast that type to the function when you pass it in.

The code below isolates the issue and is a standalone program that will compile without any dependencies. It utilizes the concepts used in EventQueue.h

#include <iostream>
using namespace std;

template <typename F, typename C0>
struct context10 {
    F f; C0 c0;

    context10(F f, C0 c0)
        : f(f), c0(c0) {}

    void operator()() {
        f(c0);
    }
};
template <typename F, typename C0, typename C1>
struct context20 {
    F f; C0 c0; C1 c1;

    context20(F f, C0 c0, C1 c1)
        : f(f), c0(c0), c1(c1) {}

    void operator()() {
        f(c0, c1);
    }
};
template <typename F, typename C0, typename C1, typename C2>
struct context30 {
    F f; C0 c0; C1 c1; C2 c2;

    context30(F f, C0 c0, C1 c1, C2 c2)
        : f(f), c0(c0), c1(c1), c2(c2) {}

    void operator()() {
        f(c0, c1, c2);
    }
};

// Function attributes
template <typename F>
static void function_call(void *p) {
    (*(F*)p)();
}

template <typename F>
int call_context(F f) {
    F *e = new F(f);
    function_call<F>(e);
    return 0;
}

template <typename F0, typename A0>
int call_context(F0 f0, A0 a0) {
    return call_context(context10<F0, A0>(f0, a0));
}

template <typename F0, typename A0, typename A1>
int call_context(F0 f0, A0 a0, A1 a1) {
    return call_context(context20<F0, A0, A1>(f0, a0, a1));
}

template <typename F0, typename A0, typename A1, typename A2>
int call_context(F0 f0, A0 a0, A1 a1, A2 a2) {
    return call_context(context30<F0, A0, A1, A2>(f0, a0, a1,a2));
}

template <typename F0, typename A0>
void call(F0 f0, A0 a0 ) {
   f0( a0 );
}
template <typename F0, typename A0, typename A1>
void call(F0 f0, A0 a0, A1 a1) {
   f0( a0, a1 );
}

template <typename F0>
void response(F0 f0, int a0, int a1) {
    f0( a0, a1 );
}

void Fz( int _a ){
    printf( "%d\n\r", _a );
}

void Fy( int _a, int _b ){
    printf( "%d\n\r", _a );
}

void Fx( void (*Func)(int,int), int _a, int _b ){
    Func(_a, _b);
}

void Fw( void (*Func)(int), int _a ){
    Func( _a );
}

template <typename A0>
void Fv( A0 a0 ){
	printf( "Fv: %d\n\r", a0 );
}

void Fv( void ){
	printf( "Fv: void\n\r" );
}

typedef void (Fv_t) (void );
typedef void (Fvp_t) ( int );
typedef void (Call_t) (Fvp_t,int);

int main() {
    int a = 1, b = 2, c = 3, d = 4, e = 5, f = 6, g = 7, h = 8, i = 9;

    Fx( &Fy, a , b );               /* Pass function to a function */
    response( Fy, b, c );           /* Pass function to a template function with defined parameters */
    call( Fy, c, d );               /* Pass function to a template function with generic parameters */
    call( Fw, Fz, d );              /* Pass function to a template function with a function as a generic parameter */
    call_context( Fw, Fz, e );      /* Use context struct to pass a function to a template function passing one parameter */
    call_context( Fx, Fy, f, g );   /* Use context struct to pass a function to a template function passing two parameters */

	call_context( (Fv_t*)Fv );              /* Use context struct to pass an overloaded function to a template function */
	call_context( (Fvp_t*)Fz, g );			/* Use context struct to pass a function to a template function passing one parameter */
	call_context( (Call_t*)call, Fz, h );	       /* Use context struct to pass a template function to a template function passing one parameter */
	/* call_context( call, Fz, h ); */               /* This will not compile as the compiler cannot resolve the type of call */

    return 0;
}