Вы пытаетесь реализовать полиморфизм в C. На этом пути лежит безумие, не поддерживаемый код и новые языки программирования.
Вместо этого я настоятельно рекомендую рефакторинг вашего кода, чтобы использовать лучший метод работы со смешанными данными , union
или struct
или указатели или любое из решений здесь . Это будет меньше работать в долгосрочной перспективе и приведет к более быстрому и более обслуживаемому коду.
Или вы можете переключиться на C ++ и использовать шаблоны .
Или вы можете использовать чья-то реализация вроде GLib's GArray
. Это система умных макросов и функций, обеспечивающих легкий доступ к любому типу данных в массиве. Он с открытым исходным кодом, поэтому вы можете изучить его реализацию , сочетание макросов и умных функций. Он имеет множество функций, таких как автоматическое изменение размера c и сборка мусора. И он очень зрелый и хорошо протестированный.
GArray запоминает свой тип, поэтому нет необходимости постоянно повторять его.
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);
Базовый простой массив C может быть доступ к нему осуществляется как data
поле GArray
struct . Он набирается gchar *
, поэтому его необходимо преобразовать.
double *doubles_array = (double *)doubles->data;
printf("%f", doubles_array[0]);
Если мы продолжим ваш путь, неопределенность в отношении типа заражает каждую функцию «generi c», и вы начинаете писать параллельно реализации в любом случае.
Например, давайте напишем функцию, которая добавляет два индекса вместе. Что-то, что должно быть простым.
Во-первых, давайте сделаем это условно.
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);
}
Используя преимущества конкатенации токенов , мы можем поставить перед этим умный макрос выбрать правильную функцию для нас.
#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);
}
Макрос умный, но, вероятно, не стоит дополнительной сложности. Пока вы последовательны в именовании, программист может выбирать между формами _int
и _double
. Но он есть, если хотите.
Теперь давайте посмотрим на это с помощью функции "one".
// 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);
}
Здесь мы видим заражение. Нам нужно возвращаемое значение, но мы не знаем тип, поэтому мы должны вернуть указатель void. Это означает, что нам нужно выделить память правильного типа. И нам нужно получить доступ к массиву с правильным типом, большим количеством избыточности, большим количеством типов преобразования. И затем вызывающий должен связываться с кучей типов:
Что за беспорядок.
Мы можем убрать часть избыточности с помощью макросов.
#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 возможно, уменьшит избыточность с помощью еще большего количества макросов, например ответ Патрика , но мальчик быстро превращается в макро-ад. В определенный момент вы больше не кодируете в C, поскольку вы быстро расширяете пользовательский язык, реализованный с помощью стеков макросов.
Очень умная идея Клиффорда об использовании размеров вместо типов поможет не работает здесь. Чтобы действительно что-то делать со значениями, нам нужно знать их типы.
Еще раз, я не могу express достаточно сильно определить, насколько велик полиморфизм смолы в C.