_Generic макрос
Дженерики
В Си стандарта С11 появились дженерик макросы, которые позволяют создавать функции общего назначения. Дженерик макрос позволяет выбрать функцию в зависимости от типа аргументов, переданных в функцию. Это, в некотором роде, сродни перегрузке методов по типу аргумента во многих языках, однако, это не динамическая диспетчеризация – определение типа аргумента происходит только на этапе компиляции.
Так как это нововведение Си 11, то для работы потребуется новая версия компилятора, которая поддерживает стандарт. Для примера используется gcc версии 5.3.0 с флагом -std=c11. На Windows без проблем ставится пакет MinGW, в состав которого входят все необходимые утилиты.
Рассмотрим на простом примере: функция возвращает переданный аргумент плюс один. Сначала определим все функции, которые могут быть выполнены, со всеми типами аргументов, которые нам необходимы
int foo_int(int a) {
return a + 1;
}
float foo_float(float a) {
return a + 1.0;
}
char foo_char(char a) {
return a + 1;
}
Далее сам макрос
#define foo(X) \
_Generic((X), \
int: foo_int, \
char: foo_char, \
float: foo_float, \
default: foo_int \
)(X)
Эта система похожа на оператор switch. Вместо foo будет подставлена одна из функций, а далее (X) – вызов с этим аргументом. Дефолтное значение – когда тип определить нельзя. Если тип не определён и нет дефолтного значения, или оно не может быть использовано функцией по умолчанию, то это ошибка. Вот программа
#include <stdio.h>
int foo_int(int a) {
return a + 1;
}
float foo_float(float a) {
return a + 1.0;
}
char foo_char(char a) {
return a + 1;
}
#define foo(X) \
_Generic((X), \
int: foo_int, \
char: foo_char, \
float: foo_float, \
default: foo_int \
)(X)
int main() {
float a = foo(5.0f);
int b = foo(5);
char c = foo('a');
printf("%.3f\n", a);
printf("%d\n", b);
printf("%c\n", c);
return 0;
}
Программа выведет
6.000
6
b
Для функции с двумя аргументами немного сложнее, надо описать все комбинации. В нашем случае функция может принимать аргументы типа float и int, складывать их и возвращать целое
#include <stdio.h>
int baz_int_int(int a, int b) {
return a + b;
}
int baz_int_float(int a, float b) {
return a + (int) b;
}
int baz_float_int(float a, int b) {
return (int) a + b;
}
int baz_float_float(float a, float b) {
return (int) a + (int) b;
}
#define baz_int(X, Y) _Generic((Y),\
int: baz_int_int,\
float: baz_int_float\
)
#define baz_float(X, Y) _Generic((Y),\
int: baz_float_int,\
float: baz_float_float\
)
#define bar(X, Y) _Generic((X),\
int: baz_int(X, Y),\
float: baz_float(X, Y)\
)(X, Y)
int main() {
int d = bar(1, 2);
int e = bar(1.0f, 2.0f);
printf("%d\n", d);
printf("%d\n", e);
return 0;
}
Конечно, можно вместо трёх макросов объединить всё в один
#include <stdio.h>
int baz_int_int(int a, int b) {
return a + b;
}
int baz_int_float(int a, float b) {
return a + (int) b;
}
int baz_float_int(float a, int b) {
return (int) a + b;
}
int baz_float_float(float a, float b) {
return (int) a + (int) b;
}
#define bar(X, Y) _Generic((X),\
int: _Generic((Y),\
int: baz_int_int,\
float: baz_int_float\
),\
float: _Generic((Y),\
int: baz_float_int,\
float: baz_float_float\
)\
)(X, Y)
int main() {
int d = bar(1, 2);
int e = bar(1.0f, 2.0f);
printf("%d\n", d);
printf("%d\n", e);
return 0;
}
Вот другой пример: макрос, который выводит имя типа, переданного в него
#include <stdio.h>
#include <stdint.h>
#define typeof_name(X) _Generic ((X),\
int: "signed integer",\
unsigned: "unsigned integer",\
char: "signed char",\
unsigned char: "unsigned char",\
float: "float",\
double: "double",\
char*: "modifiable string",\
const char*: "const string",\
default: "unknown"\
)
#define print_type(X) printf("%s\n", typeof_name(X))
int main() {
print_type('a');
print_type((int64_t) 32);
print_type("string");
return 0;
}
Заметьте важные особенности – без явного указания ‘a’ будет рассматриваться как int, а строка как модифицируемая, хотя попытка её изменить приведёт к ошибке. Для точного определениятипа можно его явно привести
print_type((unsigned char)'a'); print_type((int64_t) 32); print_type((const char*)"string");
Также тип может быть аргументом макроса
#include <stdio.h>
#define is_compatible(x, T) _Generic((x), T:1, default: 0)
int main() {
int a = is_compatible(3, int);
int b = is_compatible(3, float);
printf("a = %d and b = %d", a, b);
return 0;
}
