User callback functions
I don't know the user's function parameters,


A common way for dealing with this issue is to pass a generic pointer to
arbitrary data. See below.


  return type


It makes little sense to allow for arbitrary return types, as your
library would have no way of knowing what to do with the returned value.


  and number of functions that will be added.


Either you allocate the callback array statically with a fixed size, or
you use dynamic allocation. In the first case, the array size could be
decided by the user if it is a template parameter. In the second case,
you risk memory fragmentation unless all the callbacks are registered
once and for all at program startup, which is a common scenario anyway.



About the generic pointer technique

This is a very common way of defining callbacks in C: a callback
contains both a function and a void * pointer to arbitrary data, which
will be given as a parameter to the function. It is up to the user to
decide if and how he will use this pointer: he could completely ignore
it, like he could make it point to the root of a complex data tree.

Here is a simple example:

// A callback contains both a function and a pointer to arbitrary data
// that will be passed as argument to the function.
struct Callback {
    Callback(void (*f)(void *) = 0, void *d = 0)
        : function(f), data(d) {}
    void (*function)(void *);
    void *data;
};

Callback callbacks[3];

// This callback expects an int.
void print_int(void *data)
{
    int parameter = * (int *) data;
    Serial.println(parameter);
}

// This one expects a C string.
void print_string(void *data)
{
    char * parameter = (char *) data;
    Serial.println(parameter);
}

void setup()
{
    Serial.begin(9600);

    // Register callbacks.
    static int seventeen = 17;
    static int forty_two = 42;
    static char hello[] = "Hello, World!";
    callbacks[0] = Callback(print_int, &amp;seventeen);
    callbacks[1] = Callback(print_int, &amp;forty_two);
    callbacks[2] = Callback(print_string, hello);
}

void loop()
{
    // Test all the callbacks.
    for (size_t i = 0; i &lt; 3; i++)
        callbacks[i].function(callbacks[i].data);
    delay(500);
}


Notice that the two callback functions take different types of
parameters, only disguised into the same generic pointer. Notice also
that print_int is used in two different callbacks with different data.
In this example, the data does not change, but this need not be the
case: both the callbacks and the rest of the user code could change the
data pointed to by the generic pointers. Again, it is up to the user to
decide how he will use this facility to communicate between his
callbacks and the rest of his code.