Often times while working with 3rd party libraries, I find myself needing to write glue "code" to handle prototypes of functions that have changed across versions.
Taking the Linux kernel, for example: here's a common scenario - let's say we have function int my_function(int param) and at some point we need to add an optional void *data. Instead of breaking the API, the new parameter will be added this way:
int __my_function(int param, void *data);
static inline my_function(int param) {
return __my_function(param, NULL);
}
This is nice: it hides the new parameter and API breakage from those who don't need it. Existing code can continue using my_function with the old prototype.
However, this is not always the case (neither in Linux nor in other libraries) and I end up with such pieces, to handle all possible versions I encounter:
#if LIBRARY_VERSION > 5
my_function(1, 2, 3);
#elif LIBRARY_VERSION > 4
my_function(1, 2);
#else
my_function(1);
#endif
So I was thinking, for simple prototype changes (reordering of parameters, addition / removal of "default" parameters, etc) it'd be nice to have the compiler do it automatically.
I want this done automatically (without needing to specify exact versions) because sometimes it's excessive work to pinpoint the exact library/kernel version in which the change was introduced. So if I don't care of the new parameter and I can just use e.g 0, I want the compiler to use 0 whenever it needs to.
The farthest I've got is this:
#include <stdio.h>
#if 1
int f(int a, int b) {
return a + b;
}
#else
int f(int a) {
return a + 5;
}
#endif
int main(void) {
int ret = 0;
int (*p)() = (int(*)())f;
if (__builtin_types_compatible_p(typeof(f), int(int, int)))
ret = p(3, 4);
else if (__builtin_types_compatible_p(typeof(f), int(int)))
ret = p(1);
else
printf("no matching call\n");
printf("ret: %d\n", ret);
return 0;
}
This works - GCC selects the appropriate call in compile type, but it has 2 problems:
- By calling via a "typeless" function pointer, we lose type checking. So
p("a", "b")is legit and gives garbage results. - It seems like standard type promotion doesn't happen (again, probably since we make the call via a typeless pointer)
(And there might be other problems I'm missing here, but these are the most crucial points I believe)
I believe it can be taken ahead with some macros magic: if call parameters are separated, a macro can generate code that finds the appropriate prototype, then tests all given arguments for type compatibility one-by-one. But I think it'll be much more complex to use so I'm seeking a simpler solution.
Any ideas how to get this working with proper type checking + promotions? I assume it can't be done without using compiler extensions, so my question focuses on modern GCCs, targeting Linux.
EDIT: After macroifying it + adding Acorn's idea for the cast, I'm left with this:
#define START_COMPAT_CALL(f, ret_type, params, args, ret_value) if (__builtin_types_compatible_p(typeof(f), ret_type params)) (ret_value) = ( ( ret_type(*) params ) (f) ) args
#define ELSE_COMPAT_CALL(f, ret_type, params, args, ret_value) else START_COMPAT_CALL(f, ret_type, params, args, ret_value)
#define END_COMPAT_CALL() else printf("no matching call!\n")
int main(void) {
int ret = 0;
START_COMPAT_CALL(f, int, (int, int), (999, 9999), ret);
ELSE_COMPAT_CALL(f, int, (int), (1), ret);
ELSE_COMPAT_CALL(f, int, (char, char), (5, 9999), ret);
ELSE_COMPAT_CALL(f, int, (float, int), (3, 5), ret);
ELSE_COMPAT_CALL(f, int, (float, char), (3, 5), ret);
END_COMPAT_CALL();
printf("ret: %d\n", ret);
return 0;
}
This works regarding the 2 points I've noted - it gives type checking warnings and it performs promotion correctly. But it also emits warnings for all "unselected" call sites: warning: function called through a non-compatible type. I tried wrapping the call with __builtin_choose_expr but I no luck :/