Избегайте нестандартных расширений компилятора и реализуйте его как полностью безопасный для типов макрос в чистом стандарте C (ISO 9899: 2011).
Решение
#define GENERIC_MAX(x, y) ((x) > (y) ? (x) : (y))
#define ENSURE_int(i) _Generic((i), int: (i))
#define ENSURE_float(f) _Generic((f), float: (f))
#define MAX(type, x, y) \
(type)GENERIC_MAX(ENSURE_##type(x), ENSURE_##type(y))
Использование
MAX(int, 2, 3)
Объяснение
Макрос MAX создает другой макрос на основе параметра type
. Этот управляющий макрос, если он реализован для данного типа, используется для проверки того, что оба параметра имеют правильный тип. Если type
не поддерживается, будет ошибка компилятора.
Если x или y имеет неправильный тип, в макросах ENSURE_
будет ошибка компилятора. Можно добавить больше таких макросов, если поддерживается больше типов. Я предполагал, что будут использоваться только арифметические типы (целые числа, числа с плавающей точкой, указатели и т. Д.), А не структуры или массивы и т. Д.
Если все типы верны, будет вызван макрос GENERIC_MAX. Вокруг каждого макропараметра требуются дополнительные скобки, как обычная стандартная мера предосторожности при написании макросов C.
Тогда есть обычные проблемы с неявным продвижением типов в C. Оператор ?:
балансирует 2-й и 3-й операнд друг против друга. Например, результатом GENERIC_MAX(my_char1, my_char2)
будет int
. Чтобы макрос не мог выполнять такие потенциально опасные продвижения типа, использовался финальный тип, приведенный к предполагаемому типу.
Обоснование
Мы хотим, чтобы оба параметра в макросе были одного типа. Если один из них имеет другой тип, макрос больше не является безопасным по типу, потому что такой оператор, как ?:
, приведет к неявному продвижению типа. И поскольку это так, мы также всегда должны приводить конечный результат обратно к намеченному типу, как описано выше.
Макрос с одним параметром мог бы быть написан гораздо проще. Но с двумя или более параметрами необходимо включить дополнительный параметр типа. Потому что что-то подобное, к сожалению, невозможно:
// this won't work
#define MAX(x, y) \
_Generic((x), \
int: GENERIC_MAX(x, ENSURE_int(y)) \
float: GENERIC_MAX(x, ENSURE_float(y)) \
)
Проблема в том, что если указанный выше макрос вызывается как MAX(1, 2)
с двумя int
, он все равно попытается расширить все возможные сценарии списка ассоциаций _Generic
. Таким образом, макрос ENSURE_float
также будет расширен, даже если он не относится к int
. А поскольку этот макрос намеренно содержит только тип float
, код не будет компилироваться.
Чтобы решить эту проблему, я вместо этого создал имя макроса на этапе предварительной обработки с помощью оператора ##, чтобы случайно не раскрыть ни один макрос.
Примеры
#include <stdio.h>
#define GENERIC_MAX(x, y) ((x) > (y) ? (x) : (y))
#define ENSURE_int(i) _Generic((i), int: (i))
#define ENSURE_float(f) _Generic((f), float: (f))
#define MAX(type, x, y) \
(type)GENERIC_MAX(ENSURE_##type(x), ENSURE_##type(y))
int main (void)
{
int ia = 1, ib = 2;
float fa = 3.0f, fb = 4.0f;
double da = 5.0, db = 6.0;
printf("%d\n", MAX(int, ia, ib)); // ok
printf("%f\n", MAX(float, fa, fb)); // ok
//printf("%d\n", MAX(int, ia, fa)); compiler error, one of the types is wrong
//printf("%f\n", MAX(float, fa, ib)); compiler error, one of the types is wrong
//printf("%f\n", MAX(double, fa, fb)); compiler error, the specified type is wrong
//printf("%f\n", MAX(float, da, db)); compiler error, one of the types is wrong
//printf("%d\n", MAX(unsigned int, ia, ib)); // wont get away with this either
//printf("%d\n", MAX(int32_t, ia, ib)); // wont get away with this either
return 0;
}