I guess you know this, but the safer way to do this sort of thing (although it's somewhat of a nuisance) is with explicit casts:
(*(void (*)(int, int))c2)(0, 1);
Here we:
- take the generic "callback" function pointer
c2
- cast it to the correct function pointer type
void (*)(int, int)
- call it with the correct arguments
More problematic is your first callback, c. You're trying to call it with one integer argument 0, but you defined foo as accepting 0 arguments. So a more correct callback to foo as defined would be
(*(void (*)(void))c)();
Or if foo was supposed to take one argument of type int, that would be
(*(void (*)(int))c)(0);
And although as I said, the extra casts here can be a nuisance, this exercise illustrates their benefit: I didn't notice the mismatch between foo's definition and your callback c, until the compiler warned me about it after I inserted what I thought were the correct casts.
Without the casts, as other answers have explained, the code is unlikely to work. In particular, the attempt to make the callback type "variadic" (that is, with the ... in the prototype) does not help, and may very well hurt. These days, the calling conventions for variadic functions tend to be different from those for ordinary functions (a distinction which the ANSI/ISO C Standard has made since the beginning, although this marked a departure from K&R C). So if the compiler thinks that c2 points to a function of type void (*)(int, ...), and you call it with (0, 2), the compiler may very well use a different calling convention (the "varargs" one), and it might not be compatible with the non-varargs foo2, which is actually of type void (*)(int, int).
The bottom line is that it is safe to cast (convert) between function pointer types, but you must do it "on both ends"; that is, you must convert back to the correct type before calling. And "the correct type" must match both the actual function being called, and the actual arguments being passed.