Guilhem Fry
Guilhem Fry

Reputation: 326

Generic function type callback in C, like javascript

It is possible to do callbacks in C, via function pointers.

Example:

typedef void (*pointforfunc1)(int a);

void func1(int a);
void func2(int a, pointforfunc1 f);

void main(){
  int nb = 5;
  pointforfunc1 fp = func1;
  func2(2, fp(nb));
  return 0;
}

/*
...
Functions' definitions
...
*/

The problem is that the call back can only be one exact type of function: a void taking an int as argument.

How to implement javascript-style generic callbacks ? Where the callback can be whatever function we want.

Upvotes: 0

Views: 1214

Answers (2)

rici
rici

Reputation: 241721

The simple answer is that you cannot do that. C was not designed for generic algorithms.

There are, however, some workarounds for particular cases.

The most common one is type-erasure, as shown in the standard library function qsort, whose prototype is:

void qsort(void *base, size_t nmemb, size_t size,
           int (*compar)(const void *, const void *));

Here the compar function is required to take two type-erased (i.e. void) pointers, while the array to be sorted has also been type-erased. (This means that qsort cannot know the size of each element of the array, so you have to tell it, using the size parameter.)

This does not let you use an arbitrary comparison function, because C does not allow you to assume that you can call func(const Type* a, const Type* b) using the prototype func(const void* a, const void* b). Technically, you would need to wrap the typed comparison function with a type-erased version to allow it to be used by qsort:

int myQsortCompare(const void* a, const void* b) {
  return myCompare((const Type*)a, (const Type*)b);
}

// ...

qsort(myArray, myArraySize, sizeof *myArray, myQsortCompare);

More recent versions of the C standard allow the use of the _Generic facility to create specific instances of a "generic" call. That can be useful under some circumstances (such as the intended use case of type-generic math functions) but it is hard to apply to a context in which an argument might be any type (or even any of a large and extensible set of types.) In particular, it doesn't provide any mechanism for the definition of a generic function.

If you tried to apply it in the qsort case, you would need to provide a specific definition of qsort for each value (and thus callback) type; the only benefit of the _Generic facility is that the client could use the same syntax to call all of these diverse implementations.

Upvotes: 1

Sergey Kalinichenko
Sergey Kalinichenko

Reputation: 726569

Being very close to low-level languages, C does not provide facilities for making closures, which are an important part of functional programming languages.

C does not allow casts between function pointers of different types:

The behavior is undefined in the following circumstances:

  • ...
  • A pointer is used to call a function whose type is not compatible with the pointed-to type

This means that if you would like to take callbacks of other types, you need to make an "adapter function" to go between the two types.

For example, if you would like to pass f(int,int) to a callback taking f(int), you need to figure out a way to pass a second parameter to the actual callback.

For example, let's say you want to pass -1 for the second parameter. Then you do the following:

typedef void (*pointforfunc1)(int a);
typedef void (*pointforfunc2)(int a, int b);
// This is the actual function
void callWithCallbackOneArg(pointforfunc1 f1);

static pointforfunc2 actualF2;

static void callF2FixedB(int a) {
    actualF2(a, -1);
}
void callWithCallbackTwoArgs(pointforfunc2 f2) {
    actualF2 = f2;
    callWithCallbackOneArg(callF2FixedB);
}

This approach has an important downside, because the use of static makes it non-reentrant.

Unfortunately, you can't help that if the actual callWithCallbackOneArg expects a single-argument function that takes an int, without any context. The standard approach is to make the function take a pass-through context as a void*:

typedef void (*pointforfunc1)(int a, void* context);
void callWithCallbackOneArg(pointforfunc1 f1, void* context);

Upvotes: 0

Related Questions