You're trying to implement polymorphism in C. Down this path lies madness, unmaintainable code, and new programming languages.
Instead, I strongly recommend refactoring your code to use a better method of working with mixed data. union
or struct
or pointers or any of the solutions here. This will be less work in the long run and result in faster and more maintainable code.
Or you can switch to C++ and use templates.
Or you can use somebody else's implementation like GLib's GArray
. This is a system of clever macros and functions to allow easy access to any type of data in an array. It's Open Source so you can examine its implementation, a mix of macros and clever functions. It has many features like automatic resizing and garbage collection. And it is very mature and well tested.
A GArray remembers its type, so it isn't necessary to keep telling it.
GArray *ints = g_array_new(FALSE, FALSE, sizeof(int));
GArray *doubles = g_array_new(FALSE, FALSE, sizeof(double));
int val1 = 23;
double val2 = 42.23;
g_array_append_val(ints, val1);
g_array_append_val(doubles, val2);
The underlying plain C array can be accessed as the data
field of the GArray
struct. It's typed gchar *
so it must be recast.
double *doubles_array = (double *)doubles->data;
printf("%f", doubles_array[0]);
If we continue down your path, the uncertainty about the type infects every "generic" function and you wind up writing parallel implementations anyway.
For example, let's write a function that adds two indexes together. Something which should be simple.
First, let's do it conventionally.
int add_int(int *array, size_t idx1, size_t idx2) {
return array[idx1] + array[idx2];
}
double add_double(double *array, size_t idx1, size_t idx2) {
return array[idx1] + array[idx2];
}
int main() {
int ints[] = {5, 10, 15, 20};
int value = add_int(ints, 1, 2);
printf("%d\n", value);
}
Taking advantage of token concatenation, we can put a clever macro in front of that to choose the correct function for us.
#define add(a, t, i1, i2) (add_ ## t(a, i1, i2))
int main() {
int ints[] = {5, 10, 15, 20};
int value = add(ints, int, 1, 2);
printf("%d\n", value);
}
The macro is clever, but probably not worth the extra complexity. So long as you're consistent about the naming the programmer can choose between the _int
and _double
form themselves. But it's there if you like.
Now let's see it with "one" function.
// Using an enum gives us some type safety and code clarity.
enum Types { _int, _double };
void *add(void * array, enum Types type, size_t idx1, size_t idx2) {
// Using an enum on a switch, with -Wswitch, will warn us if we miss a type.
switch(type) {
case _int : {
int *sum = malloc(sizeof(int));
*sum = (int *){array}[idx1] + (int *){array}[idx2];
return sum;
};
case _double : {
double *sum = malloc(sizeof(double));
*sum = (double *){array}[idx1] + (double *){array}[idx2];
return sum;
};
};
}
int main() {
int ints[] = {5, 10, 15, 20};
int value = *(int *)add((void *)ints, _int, 1, 2);
printf("%d\n", value);
}
Here we see the infection. We need a return value, but we don't know the type, so we have to return a void pointer. That means we need to allocate memory of the correct type. And we need to access the array with the correct type, more redundancy, more typecasting. And then the caller has to mess with a bunch of typecasting.
What a mess.
We can clean up some of the redundancy with macros.
#define get_idx(a,t,i) ((t *){a}[i])
#define make_var(t) ((t *)malloc(sizeof(t)))
void *add(void * array, enum Types type, size_t idx1, size_t idx2) {
switch(type) {
case _int : {
int *sum = make_var(int);
*sum = get_idx(array, int, idx1) + get_idx(array, int, idx2);
return sum;
};
case _double : {
double *sum = make_var(double);
*sum = get_idx(array, double, idx1) + get_idx(array, double, idx2);
return sum;
};
};
}
You can probably reduce the redundancy with even more macros, like Patrick's answer, but boy is this rapidly turning into macro hell. At a certain point you're no longer coding in C as you are rapidly expanding custom language implemented with stacks of macros.
Clifford's very clever idea of using sizes rather than types will not work here. In order to actually do anything with the values we need to know their types.
Once again, I cannot express strongly enough how big of a tar pit polymorphism in C is.