问题描述:

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?

网友答案:

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.

网友答案:

You can do a limited form of overloading, for some argument types, like so:

void func_int(int);
void func_long(long);
void func_longlong(long long);

#define FUNC(X) \ 
  (sizeof(X) <= sizeof(int) ? func_int(X) \
  : sizeof(X) == sizeof(long) ? func_long(X) \
  : func_longlong(X))

This will allow you to use FUNC(i) and have it call different functions. It's limited, because you can only distinguish types by their size. That means if sizeof(int) == sizeof(long) then you will never call func_long, and if sizeof(long) == sizeof(long long) then you will never call func_longlong. Also, you can't overload for other types, such as double, if sizeof(double) is the same as one of the integer types you're testing for.

It can be used to overload for e.g. float, double or long double, where you might have different implementations of a function that calculate more or less precisely depending on the precision (i.e. number of bits) in the argument type.

网友答案:

I found a method that appears to work, however I still get a couple warnings at compile time...

Working code:

#include <stdio.h>

#define print(x)                                                                        \
__builtin_choose_expr(__builtin_types_compatible_p(typeof(x), int   ), print_int(x)   , \
__builtin_choose_expr(__builtin_types_compatible_p(typeof(x), char[]), print_string(x), \
(void)0))

void print_int(int i) {
    printf("int: %d\n", i);
}

void print_string(char* s) {
    printf("char*: %s\n", s);
}

int main(int argc, char* argv[]) {

    print(1);
    print("this");

    return 0;
}

output:

int: 1
char*: thing

Compiler warnings:

gcc overload.c -o main
overload.c: In function 'main':
overload.c:19: warning: passing argument 1 of 'print_string' makes pointer from integer without a cast
overload.c:20: warning: passing argument 1 of 'print_int' makes integer from pointer without a cast
相关阅读:
Top