Определите переменную и «верните» выражение в одном макросе C

ПРИМЕЧАНИЕ. Меня не интересует использование выражения оператора 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 31.05.2024 13:00

@paddy Вы не можете передавать типы в функции.

Lundin 31.05.2024 13:06

Вы не можете сделать это с помощью стандартного C. Причина, по которой было изобретено выражение оператора C GNU, заключается в том, что не существует стандартного способа сделать это. Однако вы можете использовать _Generic для вызова определенной функции для одного конкретного типа, но тогда вам нужно будет поддерживать список разрешенных типов.

Lundin 31.05.2024 13:11

Вероятно, вам потребуется эмулировать общие функции сортировки и поиска, передать в макрос функцию сравнения и использовать ее. Но вам также мешает обертка do { … } while (0). Это не возвращает значение, поэтому макрос вряд ли будет работать; вам нужно будет реализовать функцию. Если, возможно, вы не отмените свое решение избегать выражений операторов GCC.

Jonathan Leffler 31.05.2024 15:16

@Lundin, однако вы можете использовать макрос для создания функции.

ikegami 31.05.2024 15:23
Стоит ли изучать PHP в 2026-2027 годах?
Стоит ли изучать PHP в 2026-2027 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
1
5
112
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

Одних только макросов для этой цели недостаточно; вероятно, наименее болезненное решение — создать несколько функций, поддерживающих тип, и использовать _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), но оба они довольно грубы по сравнению с полиморфизмом истинного типа.

Другие вопросы по теме