Search code examples
cfunctiongeneric-programmingoverloading

Function overloading in C without using _Generic


I wish to accomplish function overloading in C, but I am attempting to run my code on a Unix server that does not have C11 support therefore the _Generic keyword is not available.

(Upgrading the server so it has a newer version of GCC is not an option).

Are there any alternatives to using _Generic to simulate effective function overloading in C?


Solution

  • The GCC manual explicitly shows a GNU99 (-std=gnu99) workaround since at least version 3.1.1.

    There are limitations, of course: all variants must have the same return type, and all function variants must make syntactic sense. The latter is often the cause of various compile errors (invalid types for function variant parameters). That can be avoided by declaring the functions without parameter prototypes; however, one must then remember that default type promotions will then take place (float are promoted to double, and all integer types smaller than int are promoted to int or unsigned int). Consider this example program:

    #define  _GNU_SOURCE /* for asprintf() */
    #include <stdlib.h>
    #include <stdio.h>
    
    typedef struct {
        double  x;
        double  y;
        double  z;
        double  d;
    } plane;
    
    static const char *foo_char_array();
    static const char *foo_int();
    static const char *foo_long();
    static const char *foo_double();
    static const char *foo_float();
    static const char *foo_short();
    static const char *foo_plane();
    
    #define foo(x) \
        ( __builtin_choose_expr( __builtin_types_compatible_p(typeof(x), int),     foo_int(x),        \
          __builtin_choose_expr( __builtin_types_compatible_p(typeof(x), long),    foo_long(x),       \
          __builtin_choose_expr( __builtin_types_compatible_p(typeof(x), short),   foo_short(x),      \
          __builtin_choose_expr( __builtin_types_compatible_p(typeof(x), float),   foo_float(x),      \
          __builtin_choose_expr( __builtin_types_compatible_p(typeof(x), double),  foo_double(x),     \
          __builtin_choose_expr( __builtin_types_compatible_p(typeof(x), plane),   foo_plane(x),      \
          __builtin_choose_expr( __builtin_types_compatible_p(typeof(x), char []), foo_char_array(x), \
          (void)0 ))))))) )
    
    
    int main(void)
    {
        double d = 1.0;
        float  f = 2.0f;
        short  s = 3;
        long   n = 4L;
        plane  p = { 5.0, 6.0, 7.0, 8.0 };
    
        printf("foo(9) = %s\n", foo(9));
        printf("foo(10L) = %s\n", foo(10L));
        printf("foo(11.0f) = %s\n", foo(11.0f));
        printf("foo(12.0) = %s\n", foo(12.0));
        printf("foo(\"bar\") = %s\n", foo("bar"));
        printf("foo(d) = %s\n", foo(d));
        printf("foo(f) = %s\n", foo(f));
        printf("foo(s) = %s\n", foo(s));
        printf("foo(n) = %s\n", foo(n));
        printf("foo(p) = %s\n", foo(p));
        return EXIT_SUCCESS;
    }
    
    static const char *foo_char_array(char x[]) { return "char []"; }
    static const char *foo_int(int x) { static char buffer[40]; snprintf(buffer, sizeof buffer, "(int)%d", x); return (const char *)buffer; }
    static const char *foo_long(long x) { static char buffer[40]; snprintf(buffer, sizeof buffer, "(long)%ld", x); return (const char *)buffer; }
    static const char *foo_float(double x) { static char buffer[40]; snprintf(buffer, sizeof buffer, "%af", x); return (const char *)buffer; }
    static const char *foo_double(double x) { static char buffer[40]; snprintf(buffer, sizeof buffer, "%a", x); return (const char *)buffer; }
    static const char *foo_short(int x) { static char buffer[40]; snprintf(buffer, sizeof buffer, "(short)%d", x); return (const char *)buffer; }
    static const char *foo_plane(plane p) { static char buffer[120]; snprintf(buffer, sizeof buffer, "(plane){ .x=%g, .y=%g, .z=%g, .d=%g }", p.x, p.y, p.z, p.d); return (const char *)buffer; }
    

    You do not need to determine the type based on a single parameter; you can do e.g. __builtin_types_compatible_p(typeof(x), double) && __builtin_types_compatible_p(typeof(y), double) to verify both x and y are of type double.

    When compiled and run, the above program will output

    foo(9) = (int)9
    foo(10L) = (long)10
    foo(11.0f) = 0x1.6p+3f
    foo(12.0) = 0x1.8p+3
    foo("bar") = char []
    foo(d) = 0x1p+0
    foo(f) = 0x1p+1f
    foo(s) = (short)3
    foo(n) = (long)4
    foo(p) = (plane){ .x=5, .y=6, .z=7, .d=8 }
    

    tested on 32-bit x86 Linux (ILP32), as well as on x86-64 (LP64). And yes, the above program will leak memory, since it never free()s the dynamically allocated strings returned by the foo_..() function variants.