There isn't any way to do this in ISO C. Libraries like libffcall and libffi exist for dynamically building a C argument list and calling it.
If we stick to standard C, all calls are statically determined, so your only option is to switch among different call expressions, like:
switch (nargs) {
case 0: return f();
case 1: return f(a[0]);
case 2: return f(a[0], a[1]);
...
}
Here, if the f pointer is under-declared, it saves you some casting.
You can use a union:
struct fun {
int nargs;
union {
int (*ptr0)(void);
int (*ptr1)(valtype);
int (*ptr2)(valtype, valtype);
...
} fn;
}
Then:
switch (f->nargs) {
case 0: return f->fn.ptr0();
case 1: return f->fn.ptr1(arg[0]);
...
}
Now there is a modicum of type checking. When you create the "struct fun" object, you may be able to take the address of a function that is compatible with one of the ptr's, and assing it to the correct union member without having to use a cast. If you record the number of arguments correctly, it won't be misused.
Your idea to use a union of function pointers also allows a much broader range of function parameter types to be used. When you call a function that used a K&R declaration, the arguments are subject to default promotion e.g. short to int, so you can't call "int f(short)" if it is only declared "int f()". Your idea doesn't have this problem.
Here, the union is being used as a space-saving structure. We only need to store one pointer, but it can be of different types. We access the same one that we most recently stored.
Most of the code surrounding this won't contain assignments into the union, so it won't impact optimization.
In an interpreted language I wrote this kind of union is initialized when a function object comes to life, and then not mutated again.
The union is necessary because a struct would blow up the size of the object significantly (and then it wouldn't fit into the GC heap cell size, requiring an additional piece of malloced memory).
Compilers that allow type punning through a union have to basically consider the access to a member of the union to have an effect on any other member. It's conceivable that there are edge cases where that consideration could hamper the optimization of a code which uses unions without perpetrating any sort of type punning. Hmm, like what?
Say we have a really contrived function that works with two pointers A and B to the same union type. The compiler cannot prove that A and B are distinct. The function evaluates A->x = expr, and also B->y in several places. Since A and B might be the same pointer, the assignment has to be regarded as clobbering B->y, which interferes with CSE of B->y and register caching.
As of C99, a possible solution here would be to declare the pointers restrict; then the compiler assumes they don't overlap: A->x has nothing to do with B->y.
If a struct is used, then x and y are different members and have nothing to do with each other for that reason, needless to say, even if A and B are the same pointer.
Given that this is C, can the compiler prove in any case that two pointers are distinct? When you're dealing with a structure, it could still be the case that A = B + sizeof(structure.x) (+/- padding).
Yes, I know this: I'm relying POSIX's guarantees and not what ISO C mandates (also truth be told I usually do this in Objective-C, but that doesn't actually change anything significantly). Unfortunately I get a function pointer and need forward essentially anything, so I'm not going to have any sort of type safety at all…
What guarantees does POSIX make about calling functions that are not in ISO C? I'm curious.
POSIX generaly has very little to say about C matters; for the most part it defers to ISO C by normative reference.
Off the top of my head, because POSIX specifies dlopen and dlsym, that pretty much requires function pointers have to have a common representation convertible between void * and back.
If we stick to standard C, all calls are statically determined, so your only option is to switch among different call expressions, like:
Here, if the f pointer is under-declared, it saves you some casting.You can use a union:
Then: Now there is a modicum of type checking. When you create the "struct fun" object, you may be able to take the address of a function that is compatible with one of the ptr's, and assing it to the correct union member without having to use a cast. If you record the number of arguments correctly, it won't be misused.