Reputation: 9035
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?
Upvotes: 2
Views: 1080
Reputation: 11
It is somehow possible using function pointers, and nameless struct inside a union. Here comes an example in which we overload the add and mul functions. There are two unions LIBI, and LIBF containing nameless structures. LIBI contains the function pointers add and mulc which only use integer values. LIBF is the same as LIBI except that add and mul use float variables. In addition, we need to create addi, muli, addf, and mulf functions outside of these unions. Functions pointers in unions will be referred to these 4 functions. For example, add in LIBI is referred to addi because addi uses int values and add in LIBF is referred to addf as it uses only float variables. This example can also be used as a way of emulating namespace in C which is absent in the language. Unions act like the namespace in this example.
#include<stdio.h>
#include<stdlib.h>
union {
struct {
void (*add)(int *, int);
void (*mul)(int *, int);
};
}LIBI;
union {
struct {
void (*add)(float *, float);
void (*mul)(float *, float);
};
}LIBF;
void addi(int *a, int c){
*a += c;
}
void addf(float *a, float c){
*a += c;
}
void muli(int *a, int c){
*a *= c;
}
void mulf(float *a, float c){
*a *= c;
}
int main(void){
LIBI.add = addi;
LIBF.add = addf;
LIBI.mul = muli;
LIBF.mul = mulf;
int ia = 10;
int ib = 2;
float fa = 20.0f;
float fb = 2.0f;
LIBI.add(&ia,ib);
LIBF.add(&fa,fb);
printf("%d\n",ia);
printf("%f\n",fa);
LIBI.mul(&ia,ib);
LIBF.mul(&fa,fb);
printf("%d\n",ia);
printf("%f\n",fa);
return 0;
}
Upvotes: 0
Reputation: 39396
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.
Upvotes: 3
Reputation: 9035
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
Upvotes: 0
Reputation: 171373
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.
Upvotes: 4