Использование макросов C / C ++ для создания сигнатуры функции - PullRequest
1 голос
/ 14 июля 2020

Я пытаюсь использовать макросы в C / C ++ для создания некоторых стандартных объявлений и определений функций.

Мне нужен макрос, похожий на:

DECLARE_FUNCTION(myFunction, int, A, int, B, char, C)

для генерации следующий код (пожалуйста, не обращайте внимания на то, что этот код кажется бессмысленным, это просто упрощенный пример)

void myFunction(int A, int B, char C) {
    myFunction_PROXY((Variant[4]){Variant(A), Variant(B), Variant(C), Variant()});
}
void myFunction_PROXY(const Variant (&args)[4]) {
    myFunction_HANDLER(args[0], args[1], args[2]);
}
void myFunction_HANDLER(int A, int B, char C) {

}

Обратите внимание, как мне нужна как подпись функции (с типами и именами), так и только имена для использоваться в конструкторах Variant (). Таким образом, я бы предположил, что мне нужно выборочно «l oop» через значение макроса VA_ARGS , чтобы получить различные комбинации аргументов и применить их по-разному.

Где я уже определен класс с именем Variant.

Исходя из моих исследований, я считаю, что это связано с некоторой комбинацией «рекурсивных макросов», однако я не могу понять, как получить желаемый результат.

Может ли кто-нибудь помочь или хотя бы указать мне на хорошее объяснение того, как работают рекурсивные макросы в C?

Спасибо

Ответы [ 3 ]

1 голос
/ 15 июля 2020

Заявление об ограничении ответственности 1:

Честно говоря, я не советую вам делать такой механизм за макросами. Они станут тяжелым бременем для обслуживания, а отдача будет минимальной. Если вы можете сделать это с другими, более поддерживаемыми абстракциями, это будет лучше для вас в долгосрочной перспективе.

Заявление об ограничении ответственности 2:

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

В этом заявлении об отказе от ответственности сказано, что это можно сделать, но не очень хорошо.

У вас есть несколько проблем, которые необходимо решить с помощью множества уловок с макросами:

  • Вы хотите, чтобы расширение содержало размер __VA_ARGS__ / 2 (во время раскрытия)
  • Вы хотите, чтобы расширение __VA_ARGS__ производило Variant(<arg 1>), Variant(<arg 3>), Variant(<arg 5>), Variant()
  • Вы хотите, чтобы расширение __VA_ARGS__ до произвести Variant[size]{args[0], args[1], args[2], ...}

Для начала я собираюсь создать помощник с именем JOIN:

#define JOIN(a, b) JOIN_H(a, b)
#define JOIN_H(a, b) a ## b

Это может показаться глупым, но на самом деле он делает гарантирует, что объединяемые макросы будут оценены перед тем, как они будут объединены - так что вызываемые макрофункции будут правильно создавать экземпляр объединения с их результатом , а не полным именем.

Получение размера __VA_ARGS__ / 2

Для получения размера __VA_ARGS__ обычно требуются два макроса:

  • Один, который передает __VA_ARGS__, N, N-1, N-2, ... во вспомогательный макрос, и
  • Другой, который извлекает N в конце.
* 105 3 * Что-то вроде:
#define COUNT_VA_ARGS(...) \
  COUNT_VA_ARGS_H(__VA_ARGS__, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0)
#define COUNT_VA_ARGS_H(_1, _2, _3, _4, _5, _6, _7, _8, _9, _10, N,...) N

Это работает, потому что первый передает все аргументы __VA_ARGS__ и считает в обратном порядке от N-го числа, а затем мы извлекаем N.

В вашем случае вам нужен __VA_ARGS__ / 2, поэтому вам нужно будет удвоить эти аргументы

#define COUNT_VA_ARGS(...) \
  COUNT_VA_ARGS_H(__VA_ARGS__, 10, 10, 9, 9, 8, 8, 7, 7, 6, 6, 5, 5, 4, 4, 3, 3, 2, 2, 1, 1, 0, 0)
#define COUNT_VA_ARGS_H(_1, _1, _2, _2, _3, _3, _4, _4, _5, _5, _6, _6, _7, _7, _8, _8, _9, _9, _10, _10, N,...) N

Заставить __VA_ARGS__ создать Wrap(<arg 1>), Wrap(<arg 3>), ...

В отличие от шаблонов Variadi c C ++, макросы не могут иметь выражения раскрытия, в которые можно заключить каждый аргумент. Чтобы смоделировать это в макросах, вам в значительной степени нужно явно указать N расширений, а затем, чтобы вызвать это, вам нужно будет объединить результат одного макроса для его вызова.

#define WRAP_VA_ARGS_0(wrap)
#define WRAP_VA_ARGS_1(wrap,x0) wrap(x0)
...
#define WRAP_VA_ARGS_10(wrap,x0,x1, ..., x10) wrap(x0), wrap(x1), ..., wrap(x10)

// Call into one of the concrete ones above
#define WRAP_VA_ARGS(wrap, __VA_ARGS__) JOIN(WRAP_VA_ARGS_, COUNT_VA_ARGS(__VA_ARGS__))(__VA_ARGS__)

Поскольку выражение на самом деле хочет каждый другой аргумент в нем, вам снова нужно будет удвоить аргументы:

#define WRAP_VA_ARGS_0(wrap)
#define WRAP_VA_ARGS_1(wrap,x0type,x0) wrap(x0)
#define WRAP_VA_ARGS_2(wrap,x0type,x0,x1type,x1) wrap(x0), wrap(x1)
...

Вызов WRAP_VA_ARGS(Variant, int, A, float, B) теперь создаст Variant(A), Variant(B)

Создание списка значений индекса

Подобно описанному выше обертыванию, вам нужно будет найти способ сгенерировать список чисел и обернуть его. Опять же, это должно быть делегировано счетной оболочке

#define WRAP_COUNT_VA_ARGS_0(wrap)
#define WRAP_COUNT_VA_ARGS_1(wrap) wrap[0]
#define WRAP_COUNT_VA_ARGS_2(wrap) wrap[0], wrap[1]
...

#define WRAP_COUNT_VA_COUNT_ARGS(wrap, ...) JOIN(WRAP_COUNT_VA_ARGS_, COUNT_VA_ARGS(__VA_ARGS))(wrap)

Вызов WRAP_COUNT_VA_COUNT_ARGS(args, int, A, float, B) должен генерировать args[0], args[1]

Собирая все вместе

Предупреждение о триггере: это будет некрасиво

#define DECLARE_FUNCTION(name, ...) \  
void name(__VA_ARGS__) {            \
    JOIN(name, _PROXY)((Variant[COUNT_VA_ARGS(__VA_ARGS__)+1]) {WRAP_VA_ARGS(Variant,__VA_ARGS__), Variant()}); \
} \
void JOIN(name, _PROXY)(const Variant (&args)[COUNT_VA_ARGS(__VA_ARGS__) + 1]) { \
    JOIN(name, _HANDLER)(WRAP_COUNT_VA_COUNT_ARGS(args, __VA_ARGS__)); \
} \
void JOIN(name, _HANDLER)(__VA_ARGS__) { \
 \
} 

Если повезет, пример DECLARE_FUNCTION(myFunction, int, A, int, B, char, C) должен дать:

void myFunction(int A, int B, char C) {
    myFunction_PROXY((Variant[3+1]{Variant(A), Variant(B), Variant(C), Variant()});
}
void myFunction_PROXY(const Variant (&args)[3+1]) { 
    myFunction_HANDLER(args[0], args[1], args[2]);
}
void myFunction_HANDLER(int A, int B, char C) {
    
}

Примечание: массив создан постоянным выражением 3 + 1, поскольку нам нужно выполнить эту арифметику c, чтобы учесть Variant() в конце вызова myFunction_PROXY

Не делать макросов. Макросы плохие, ммм?

1 голос
/ 15 июля 2020

Итерация по спискам, разделенным запятыми, с помощью препроцессора требует написания шаблонных макросов.

Обычно вы должны написать или сгенерировать не менее O(n) макросов для обработки списка до n элементов. Ответ @ Human-Compiler делает это с помощью O(n<sup>2</sup>).

Вы можете получить аналогичные макросы из Boost.Preprocessor или использовать его в качестве вдохновения.

Или вы можете использовать другой синтаксис для своего list:

DECLARE_FUNCTION(myFunction, (int,A)(int,B)(char,C))

Затем вы можете обрабатывать списки любого размера с фиксированным количеством макросов:

#define DECLARE_FUNCTION(func_, seq_) \
    void myFunction(END(PARAMS_LOOP_0 seq_)) { \
        myFunction_PROXY(
            (Variant[1 END(COUNT_LOOP_A seq_)]){END(VAR_LOOP_A seq_) Variant()}); \
    } \
    void myFunction_PROXY(const Variant (&args)[1 END(COUNT_LOOP_A seq_)]) { \
        const int x = __COUNTER__+1; \
        myFunction_HANDLER(END(ARR_LOOP_0 seq_)); \
    } \
    void myFunction_HANDLER(END(PARAMS_LOOP_0 seq_)) {}
    
#define END(...) END_(__VA_ARGS__)
#define END_(...) __VA_ARGS__##_END
    
#define PARAMS_LOOP_0(type_, name_)   PARAMS_LOOP_BODY(type_, name_) PARAMS_LOOP_A
#define PARAMS_LOOP_A(type_, name_) , PARAMS_LOOP_BODY(type_, name_) PARAMS_LOOP_B
#define PARAMS_LOOP_B(type_, name_) , PARAMS_LOOP_BODY(type_, name_) PARAMS_LOOP_A
#define PARAMS_LOOP_0_END
#define PARAMS_LOOP_A_END
#define PARAMS_LOOP_B_END
#define PARAMS_LOOP_BODY(type_, name_) type_ name_

#define COUNT_LOOP_A(...) COUNT_LOOP_BODY COUNT_LOOP_B
#define COUNT_LOOP_B(...) COUNT_LOOP_BODY COUNT_LOOP_A
#define COUNT_LOOP_A_END
#define COUNT_LOOP_B_END
#define COUNT_LOOP_BODY +1

#define VAR_LOOP_A(type_, name_) VAR_LOOP_BODY(name_) VAR_LOOP_B
#define VAR_LOOP_B(type_, name_) VAR_LOOP_BODY(name_) VAR_LOOP_A
#define VAR_LOOP_A_END
#define VAR_LOOP_B_END
#define VAR_LOOP_BODY(name_) Variant(name_), 

#define ARR_LOOP_0(...)   ARR_LOOP_BODY ARR_LOOP_A
#define ARR_LOOP_A(...) , ARR_LOOP_BODY ARR_LOOP_B
#define ARR_LOOP_B(...) , ARR_LOOP_BODY ARR_LOOP_A
#define ARR_LOOP_A_END
#define ARR_LOOP_B_END
#define ARR_LOOP_BODY args[__COUNTER__-x]

С этими макросами DECLARE_FUNCTION(myFunction, (int,A)(int,B)(char,C)) расширяется до:

void myFunction(int A, int B, char C)
{
    myFunction_PROXY((Variant[1+1+1+1]){Variant(A), Variant(B), Variant(C), Variant()});
}

void myFunction_PROXY(const Variant (&args)[1+1+1+1])
{
    const int x = 0+1;
    myFunction_HANDLER(args[1-x], args[2-x], args[3-x]);
}

void myFunction_HANDLER(int A, int B, char C) {}

Обратите внимание на использование __COUNTER__. Это не часть стандартного C ++, но основные компиляторы поддерживают его как расширение. У вас нет других вариантов получения последовательных индексов массива, кроме написания шаблонных макросов.

0 голосов
/ 14 июля 2020

Не очень понятно, что это переменная в вашем случае, вы можете делать то, что ищете, с помощью __VA_ARGS__

#define DECLARE_FUNCTION(myFunction, ...)                                     \
static void myFunction##_HANDLER(__VA_ARGS__) {                               \
                                                                              \
}                                                                             \
\
static void myFunction##_PROXY(const Variant (&args)[4]) {                    \
    myFunction##_HANDLER(args[0], args[1], args[2]);                          \
}                                                                             \
\
static void  myFunction(__VA_ARGS__) {                                        \
    myFunction##_PROXY((Variant[4]){Variant(A), Variant(B), Variant(C), Variant()}); \
}

DECLARE_FUNCTION(myFunction, int A, int B, char C)

Будет генерироваться


static void myFunction_HANDLER(int A, int B, char C) { } 
static void myFunction_PROXY(const Variant (&args)[4]) { 
    myFunction_HANDLER(args[0], args[1], args[2]);
}
static void myFunction(int A, int B, char C) {
 myFunction_PROXY((Variant[4]){Variant(A), Variant(B), Variant(C), Variant()});
}
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...