Генерация константы с помощью макроса препроцессора - PullRequest
0 голосов
/ 06 апреля 2020

Я хотел бы создать макрос, который выплевывает существующую константу.

Существует несколько констант, и все они имеют вид PREFIX_COMPONENT_ERROR.

Пример кода:

#include <stdlib.h>

enum {
  MODULE_ERROR_COMP1_ERROR1 = 0,
  MODULE_ERROR_COMP1_ERROR2,
  MODULE_ERROR_COMP1_ERROR3,
  MODULE_ERROR_COMP2_ERROR1,
  MODULE_ERROR_COMP2_ERROR2,
  MODULE_ERROR_COMP2_ERROR3,
};

static void* some_function (const char *restrict input);

#define EMPTY(...)
#define DEFER(...) __VA_ARGS__ EMPTY()
#define OBSTRUCT(...) __VA_ARGS__ DEFER(EMPTY)()
#define EXPAND(...) __VA_ARGS__
#define PASTER(x, y) x ## _ ## y

#define MODULE_ERROR some_function ("module error")

#define ERROR_PREFIX DEFER(MODULE_ERROR)
#define GENERATE_ERROR_(prefix, component, error) PASTER(DEFER(PASTER(prefix, component)), error)
#define GENERATE_ERROR(prefix, component, error) EXPAND(GENERATE_ERROR_(prefix, component, error))

static void* some_function (const char *restrict input) {
  /*
   * Does something with the input and returns some data,
   * but for simplicity let's assume it just passes the input through.
   */
  return (input);
}

int main (int argc, char **argv) {
  /* Generate enum via PP macros, for instance: */
  for (size_t i = 0; 3 > i; ++i) {
    int error_code = 0;
    void *common_error = ERROR_PREFIX;

    if (0 == i) {
      error_code = GENERATE_ERROR (ERROR_PREFIX, COMP1, ERROR1);
    }
    else if (1 == i) {
      error_code = GENERATE_ERROR (ERROR_PREFIX, COMP2, ERROR3);
    }
    else {
      error_code = GENERATE_ERROR (ERROR_PREFIX, COMP2, ERROR2);
    }

    /* Do something with error_code and common_error. */
  }

  return (EXIT_SUCCESS);
}

Интересно, возможно ли то, что я хочу сделать, с простыми директивами PP в первую очередь.

Первая проблема заключается в том, что я не могу использовать макрос конкатенации PASTER дважды, потому что PP всегда подавляет внутренний вызов PASTER().

Преобразование этого в макрос с тремя параметрами, такой как:

#define PASTER(x, y, z) x ## _ ## y ## _ ## z

#define GENERATE_ERROR_(prefix, component, error) PASTER(prefix, component, error)

, расширит prefix, чего я не хочу и все равно столкнется с ошибкой.

Использование чего-то вроде PASTER(OBSTRUCT(prefix), component, error) также приведет к сбою при конкатенации.

Я полностью осознаю, что это БУДЕТ работать, но мне не нравится:

#define MODULE_ERROR_FUNC some_function ("module error")

#define ERROR_PREFIX MODULE_ERROR
#define GENERATE_ERROR_(prefix, component, error) PASTER(prefix, component, error)
#define GENERATE_ERROR(prefix, component, error) GENERATE_ERROR_(prefix, component, error)

/* [...] */
    void *common_error = MODULE_ERROR_FUNC;

Действительно ли нет способа разрешить расширение ERROR_PREFIX только один раз в макросе GENERATE_ERROR (т. Е. До MODULE_ERROR) и в противном случае расширить его до вызова функции (т. Е. some_function ("module error"))

1 Ответ

1 голос
/ 07 апреля 2020

Моя немедленная реакция на ваш вопрос была "ты слишком усложняешь его!" Например, что не так с:

#define GENERATE_ERROR(prefix, component, error) prefix ## _ ## component ## _ ## error

и последующим вызовом:

GENERATE_ERROR(MODULE_PREFIX, COMP1, ERROR1)

Один частичный ответ на вопрос «в чем проблема?» заключается в том, что «это неочевидно в этом примере, но когда префикс выглядит примерно так: PROJECT_LONG_SUBMODULE_ERROR, вам не следует вводить его постоянно». Это нормально, но тогда вы можете подумать:

#define GENERR(component, error) GENERATE_ERROR(PROJECT_LONG_SUBMODULE_ERROR, component, error)

, чтобы сократить длинные имена, - вызывая

GENERR(COMPONENT2, ERROR2)

, чтобы получить имя ошибки:

PROJECT_LONG_SUBMODULE_ERROR_COMPONENT2_ERROR2

Почему PROJECT_LONG_SUBMODULE_ERROR не будет расширен во время расширения GENERR (то есть, почему в этом контексте оно окрашено в синий цвет)?

Оно не окрашено в синий; Я просто не стал бы создавать макрос с таким именем, поэтому не было бы для него расширения, точно так же, как не должно быть макроса COMPONENT2 или ERROR2, который может быть расширен при вызове GENERR.


Экспериментирование

Рассмотрим исходный файл pp31.c:

#define GENERATE_ERROR(prefix, component, error) prefix ## _ ## component ## _ ## error
GENERATE_ERROR(MODULE_PREFIX, COMP1, ERROR1)    -- OK

#define GENERR(component, error) GENERATE_ERROR(PROJECT_LONG_SUBMODULE_ERROR, component, error)
GENERR(COMPONENT2, ERROR2)                      -- OK

#define PLS_ERR PROJECT_LONG_SUBMODULE_ERROR

#undef GENERR
#define GENERR(component, error) GENERATE_ERROR(PLS_ERR, component, error)
GENERR(COMPONENT2, ERROR2)                      -- Broken

#undef GENERR
#define GENERR_PREFIX(prefix, component, error) GENERATE_ERROR(prefix, component, error)
#define GENERR(component, error) GENERR_PREFIX(PLS_ERR, component, error)
GENERR(COMPONENT2, ERROR2)                      -- OK

#define PROJECT_LONG_SUBMODULE_ERROR pink_elephant
GENERR(COMPONENT2, ERROR2)                      -- Broken (too many pink elephants)

#undef PROJECT_LONG_SUBMODULE_ERROR
#define GENCOMPERR(err) GENERATE_ERROR(PLS_ERR, COMPONENT2, err)
GENCOMPERR(ERROR2)                              -- Broken

#undef GENCOMPERR
#define GENCOMPERR(err) GENERR(COMPONENT2, err)
GENCOMPERR(ERROR2)                              -- OK

#undef GENCOMPERR
#define CURR_COMP COMPONENT2
#define GENCOMPERR(err) GENERATE_ERROR(PLS_ERR, CURR_COMP, err)
GENCOMPERR(ERROR2)                              -- Broken

#undef GENCOMPERR
#define GENCOMPERR(err) GENERR_PREFIX(PLS_ERR, CURR_COMP, err)
GENCOMPERR(ERROR2)                              -- OK

Когда я запускаю cpp pp31.c (это cpp из G CC 9.3.0 работает на Ма c), я получаю еще несколько пустых строк, чем показано ниже, но непустые строки в любом случае интересны:

# 1 "pp31.c"
# 1 "<built-in>"
# 1 "<command-line>"
# 1 "pp31.c"

MODULE_PREFIX_COMP1_ERROR1 -- OK

PROJECT_LONG_SUBMODULE_ERROR_COMPONENT2_ERROR2 -- OK

PLS_ERR_COMPONENT2_ERROR2 -- Broken

PROJECT_LONG_SUBMODULE_ERROR_COMPONENT2_ERROR2 -- OK

pink_elephant_COMPONENT2_ERROR2 -- Broken (too many pink elephants)

PLS_ERR_COMPONENT2_ERROR2 -- Broken

PROJECT_LONG_SUBMODULE_ERROR_COMPONENT2_ERROR2 -- OK

PLS_ERR_CURR_COMP_ERROR2 -- Broken

PROJECT_LONG_SUBMODULE_ERROR_COMPONENT2_ERROR2 -- OK

Ключевым моментом является что (с осторожностью) у вас могут быть макросы, которые расширяются до элементов конечного объединенного имени сообщения об ошибке (отсюда PLS_ERR и CURR_COMP), но вы не можете иметь макросы для этих элементов (без «розовых слонов»).


Я оставляю за собой право судить о том, действительно ли это хороший дизайн для обработки имен сообщений об ошибках, но я думаю, что он может работать.

...