Figure out function parameter count at compile time

I have a C library (with C headers) which exists in two different versions.

One of them has a function that looks like this:

int test(char * a, char * b, char * c, bool d, int e);

And the other version looks like this:

int test(char * a, char * b, char * c, bool d)

(for which e is not given as function parameter but it's hard-coded in the function itself).

The library or its headers do not define / include any way to check for the library version so I can't just use an #if or #ifdef to check for a version number.

Is there any way I can write a C program that can be compiled with both versions of this library, depending on which one is installed when the program is compiled? That way contributors that want to compile my program are free to use either version of the library and the tool would be able to be compiled with either.

So, to clarify, I'm looking for something like this (or similar):

#if HAS_ARGUMENT_COUNT(test, 5)
    test("a", "b", "c", true, 20);
#elif HAS_ARGUMENT_COUNT(test, 4)
    test("a", "b", "c", true);
#else
    #error "wrong argument count"
#endif

Is there any way to do that in C? I was unable to figure out a way.

The library would be libogc ( https://github.com/devkitPro/libogc ) which changed its definition of if_config a while ago, and I'd like to make my program work with both the old and the new version. I was unable to find any version identifier in the library. At the moment I'm using a modified version of GCC 8.3.


Solution 1:

This should be done at the configure stage, using an Autoconf (or CMake, or whatever) test step -- basically, attempting to compile a small program which uses the five-parameter signature, and seeing if it compiles successfully -- to determine which version of the library is in use. That can be used to set a preprocessor macro which you can use in an #if block in your code.

Solution 2:

I think there's no way to do this at the preprocesing stage (at least not without some external scripts). On the other hand, there is a way to detect a function's signature at compiling time if you're using C11: _Generic. But remember: you can't use this in a macro like #if because primary expressions aren't evaluated at the preprocessing stage, so you can't dynamically choose to call the function with signature 1 or 2 in that stage.

#define WEIRD_LIB_FUNC_TYPE(T) _Generic(&(T), \
    int (*)(char *, char *, char *, bool, int): 1, \
    int (*)(char *, char *, char *, bool): 2, \
    default: 0)

printf("test's signature: %d\n", WEIRD_LIB_FUNC_TYPE(test));
// will print 1 if 'test' expects the extra argument, or 2 otherwise

I'm sorry if this does not answer your question. If you really can't detect the version from the "stock" library header file, there are workarounds where you can #ifdef something that's only present in a specific version of that library.

This is just a horrible library design.

Update: after reading the comments, I should clarify for future readers that it isn't possible in the preprocessing stage but it is possible at compile time still. You'd just have to conditionally cast the function call based on my snippet above.

typedef int (*TYPE_A)(char *, char *, char *, bool, int);
typedef int (*TYPE_B)(char *, char *, char *, bool);

int newtest(char *a, char *b, char *c, bool d, int e) {
    void (*func)(void) = (void (*)(void))&test;
    if (_Generic(&test, TYPE_A: 1, TYPE_B: 2, default: 0) == 1) {
        return ((TYPE_A)func)(a, b, c, d, e);
    }
    return ((TYPE_B)func)(a, b, c, d);
}

This indeed works although it might be controversial to cast a function this way. The upside is, as @pizzapants184 said, the condition will be optimized away because the _Generic call will be evaluated at compile-time.

Solution 3:

I don't see any way to do that with standard C, if you are compiling with gcc a very very ugly way can be using gcc aux-info in a command and passing the number of parameters with -D:

#!/bin/sh

gcc -aux-info output.info demo.c
COUNT=`grep "extern int foo" output.info | tr -dc "," | wc -m`
rm output.info
gcc -o demo demo.c -DCOUNT="$COUNT + 1"
./demo

This snippet

#include <stdio.h>

int foo(int a, int b, int c);

#ifndef COUNT
#define COUNT 0
#endif

int main(void)
{
    printf("foo has %d parameters\n", COUNT);
    return 0;
}

outputs

foo has 3 parameters

Solution 4:

Attempting to support compiling code with multiple versions of a static library serves no useful purpose. Update your code to use the latest release and stop making life more difficult than it needs to be.