views:

255

answers:

1

I have a function which takes a block of data and the size of the block and a function pointer as argument. Then it iterates over the data and performes a calculation on each element of the data block. The following is the essential outline of what I am doing:

int myfunction(int* data, int size, int (*functionAsPointer)(int)){
    //walking through the data and calculating something
    for (int n = 0; n < size; n++){
        data[n] = (*function)(data[n]);
    }
}

The functions I am passing as arguments look something like this:

int mycalculation(int input){
    //doing some math with input
    //...
    return input;
} 

This is working well, but now I need to pass an additional variable to my functionpointer. Something along the lines

int mynewcalculation(int input, int someVariable){
    //e.g.
    input = input * someVariable;
    //...
    return input;
}

Is there an elegant way to achieve this and at the same time keeping my overall design idea?

+6  A: 

The usual way to make it totally generic is with a void*, which of course causes all kinds of typesafety issues:

int map_function(int* data, int size, int (*f_ptr)(int, void*), void *userdata){
    //walking through the data and calculating something
    for (int n = 0; n < size; n++){
        data[n] = (*f_ptr)(data[n], userdata);
    }
}

int simple_calculation(int input, void *userdata) {
    // ignore userdata and do some math with input
    return input;
}

int calculation_with_single_extra_arg(int input, void *userdata) {
    int input2 = * (int*) userdata;
    // do some math with input and input2
    return input;
}

int calculation_with_many_extra_args(int input, void *userdata) {
    my_data_t data = (my_data_t *) userdata;
    return input * (input * data->a + data->b) + data->c;
}

int main(int argc, char **argv) {
    int array[100];
    my_data_t userdata = { 1, 2, 3 };

    populate(array, 100);

    map_function(data, calculation_with_many_extra_args, &userdata);
}

Any given function to be passed in must have that prototype, but you can shove any data you want in through userdata. There's just absolutely no typechecking.

You could also use va_args as dbingham suggests; this isn't really much different though, since the prototype for your function to map will have to be int(int, va_list).

Edit: I favor the void* approach. The va_list doesn't add any typesafety anyway and adds more potential for user error (particularly calling va_end twice or not implementing the va_arg loop right). The void* also doesn't add any extra lines of code; it's passed cleanly through, and the user just dereferences it into (hopefully) the right type (which is probably a struct, in the general case).

Jefromi
I can't think of a clean way to do this, but the `void*` method described here isn't too bad. This is the way system functions like `ioctl()` do it. You can pass any data you need (integer, structure, NULL) through the pointer, but the function and caller both have to be careful since you won't have any type-checking to protect you.
bta
Yep, it's scary, but that's C for you! This is also the way it's done with a bunch of GLib functions (e.g. `g_hash_table_foreach`).
Jefromi
I tried something similar earlier, but that gave me hundreds of warnings of the type: "passing argument from incompatible pointer type". Can I ignore those?
Lucas
I think I've just confused myself at this point, but I think you cover both methods well enough at this point.
Daniel Bingham
@dbingham: I don't blame you - we weren't using very descriptive names for the functions, and doing this kind of stuff in C can be pretty gnarly.
Jefromi
@Lucas: were those warnings on the call of the pointed-to function? I believe it needs to expect `void*`, then cast it to its actual type.
Jefromi
Pretty sure my now-much-longer example works fine, and you can see `g_hash_table_foreach` at http://git.gnome.org/browse/glib/tree/glib/ghash.c and usages of it all over (grel.c, gscanner.c, gcache.c) - those definitely work!
Jefromi
@Jefromi: Thanks a lot! This works, I was actually doing the typecasting wrong and with all the warnings and the void* pointers, it seemed a little suicidal.
Lucas
@Lucas: Phew. Glad it works.
Jefromi
@dbingham: sorry I couldn't think of better function names. At least now I know they are called mapping functions.
Lucas
I know that Mathematica and perl both have a function called `map` which applies a function to each element of an array - that's pretty much where I took the name from.
Jefromi
If you implement it right, you should be able to do it (in C, anyway) without casts and without warnings.
caf
@caf: Perhaps you don't need it, but I like to make the cast from `void*` back to a real type explicit, just to remind that it's something to be careful with on the other end.
Jefromi