ПРИМЕЧАНИЕ. Меня не интересует использование выражения оператора GCC!
Я реализую заданную структуру данных с помощью Red Black Tree, и чтобы она работала для нескольких типов, я использую макросы.
Это определение узла:
typedef struct {
bool colour;
void* val;
void* parent;
void* left;
void* right;
}* set;
Я хочу создать макрофункцию set_contains(s, t), которая будет «возвращать» истинное выражение, если t находится в наборе s. Под «возвратом» я имею в виду, что я могу использовать его как таковой:
if (set_contains(my_set, 6)) {
...
}
Например, следующий макрос «возвращает» истинное выражение, если x — нечетное число:
#define isOdd(x) \
/* notice the lack of semicolon! */ \
(bool)((x)&1)
Мой макрос set_contains выглядит следующим образом:
#define set_contains(s, t) \
do { \
set it = s; \
while(it && *(typeof((t))*)it->val != (t)) { \
if ((t) < *(typeof((t))*)it->val) it = it->left; \
else it = it->right; \
} \
/* This is what I want to "return" */ \
(bool)it; \
} while(0)
Как мне поставить (bool)it в конце макроса, чтобы можно было использовать его целиком как выражение (например, макрос isOdd)? do while необходим, так как мне нужно определить временную переменную (it) для перебора набора. Конечно, я не могу использовать it после while, поскольку в этой области он не определен. Еще раз, я хочу добиться этого без использования выражения оператора GCC.
@paddy Вы не можете передавать типы в функции.
Вы не можете сделать это с помощью стандартного C. Причина, по которой было изобретено выражение оператора C GNU, заключается в том, что не существует стандартного способа сделать это. Однако вы можете использовать _Generic для вызова определенной функции для одного конкретного типа, но тогда вам нужно будет поддерживать список разрешенных типов.
Вероятно, вам потребуется эмулировать общие функции сортировки и поиска, передать в макрос функцию сравнения и использовать ее. Но вам также мешает обертка do { … } while (0). Это не возвращает значение, поэтому макрос вряд ли будет работать; вам нужно будет реализовать функцию. Если, возможно, вы не отмените свое решение избегать выражений операторов GCC.
@Lundin, однако вы можете использовать макрос для создания функции.





Одних только макросов для этой цели недостаточно; вероятно, наименее болезненное решение — создать несколько функций, поддерживающих тип, и использовать _Generic для выбора правильной:
#define set_contains(set, val) _Generic((val), \
int: set_contains_int, \
double: set_contains_double, \
char *: set_contains_string, \
/* any additional types */ \
)(set, val)
...
bool set_contains_int(set s, int val) { ... }
bool set_contains_double(set s, double val) { ... }
bool set_contains_string(set s, char *val) { ... }
...
if (set_contains(set, 6))
// do something
Директива _Generic выбирает правильную функцию в зависимости от типа val. Обратной стороной является то, что вы столкнетесь с большим количеством дублированной логики.
Альтернативно вы можете создать функцию сравнения, которую вы передаете в качестве обратного вызова:
int cmp_int( const void *l, const void *r )
{
const int *il = l;
const int *ir = r;
if ( *l < *r )
return -1;
else if ( *l > *r )
return 1;
return 0;
}
bool set_contains(set s, const void *val, int (*cmp)(const void *, const void *))
{
...
while( it && cmp(it->val, val) != 0 )
{
if ( cmp(it->val, val) < 0 )
it = it->left;
else
it = it->right;
}
return it != NULL;
}
который будет называться
int val = 6;
if (set_contains(set, &val, cmp_int))
...
но это означает, что вы не можете вызывать set_contains напрямую, используя val в качестве литерала (что-то вроде set_contains(set, 6, cmp_int) не будет работать). Для этого вам все равно придется создавать интерфейсы с учетом типов, например:
bool set_contains_int(set s, int val)
{
return set_contains(s, &val, cmp_int);
}
Для подобных вещей я предпочитаю второй метод (меньше дублирования усилий, IMO), но оба они довольно грубы по сравнению с полиморфизмом истинного типа.
Что плохого в использовании функции?