Нежелательный дополнительный «пустой» аргумент появляется при передаче макроса другому макросу вместо прямой передачи - PullRequest
3 голосов
/ 06 января 2020

Задача (краткая)

(следует полный минимальный код) Почему-то, когда я передаю простой список аргументов в мой макрос, все в порядке.

#pragma message STRINGIZE( ( DUMMY_WRAPPER (initial_argument, arg2, arg3)))

Во время компиляции выше, я вижу ожидаемый вывод от компилятора / препроцессора:

#pragma message: ( initial_argument { {initial_argument, arg2} , {initial_argument, arg3} }; 2)

Однако, когда я пытаюсь определить макрос с аргументами и передать , который в DUMMY_WRAPPER, я получаю «лишний» пустой аргумент. Пример:

#define ARGS initial_argument, arg2, arg3
#pragma message STRINGIZE( ( DUMMY_WRAPPER (ARGS) ))

Вывод компилятора (сравните с правильным выводом):

#pragma message: ( initial_argument { {initial_argument, arg2} , {initial_argument, arg3} , {initial_argument, } }; 3 )

Как избавиться от этого дополнительного аргумента?

Полный код (с пояснениями )

Примечание: я признаю, что это, вероятно, очень плохое злоупотребление макросами, и есть гораздо лучшие способы метапрограммирования на C ++, но я просто пытаюсь заставить это работать быстро.

Я использовал GCC / G ++ для компиляции.

Вот рабочий код + выходные данные компилятора, которые вы можете легко протестировать / поэкспериментировать с онлайн: https://godbolt.org/z/wGFbrK

#define COMMA() ,

// routines for stringizing macros, similar to BOOST_PP_STRINGIZE
#define STR1(x) #x
#define STRINGIZE(x) STR1(x)

// routine & subroutines for argument counting
#define NARG(...)                                                        \
  NARG_(__VA_ARGS__, 5, 4, 3, 2, 1, 0)
#define ARG_N(_1,_2,_3,_4,_5, N, ... ) N
#define NARG_(...) ARG_N(__VA_ARGS__)

// routines for "looped" macro expansion, for processing lists of macro arguments
#define LOOP_1(M, C, D, x) M(C, x)
#define LOOP_2(M, C, D, x, ...) M(C, x) D()                              \
  LOOP_1(M, C, D, __VA_ARGS__)
#define LOOP_3(M, C, D, x, ...) M(C, x) D()                              \
  LOOP_2(M, C, D, __VA_ARGS__)
#define LOOP_4(M, C, D, x, ...) M(C, x) D()                              \
  LOOP_3(M, C, D, __VA_ARGS__)
#define LOOP_5(M, C, D, x, ...) M(C, x) D()                              \
  LOOP_4(M, C, D, __VA_ARGS__)

// routine for concatenating things, used here to expand loop routine names, i.e. LOOP_ + 3 => LOOP_3
#define CAT(a, ...) PRIMITIVE_CAT(a, __VA_ARGS__)
#define PRIMITIVE_CAT(a, ...) a##__VA_ARGS__

// **** TOP-LEVEL ****
// (code using above routines to demonstrate the problem)

// lists the first argument, i.e. C in the loop, and some other argument
#define LIST_FIELD(arg1, a, ... ) {arg1, a}

#define DUMMY_WRAPPER(arg1, ...) DUMMY( arg1, __VA_ARGS__)

#define DUMMY( arg1, ...)                                                   \
    DUMMY_2(arg1, NARG(__VA_ARGS__), __VA_ARGS__)

#define DUMMY_2( arg1, field_count, ...)                                    \
    DUMMY_3(arg1, CAT(LOOP_, field_count), __VA_ARGS__)                     \
    field_count

#define DUMMY_3( arg1, loop, ...)                                           \
    arg1 {                                                                  \
        loop(LIST_FIELD, arg1, COMMA, __VA_ARGS__)   \
    };

#pragma message STRINGIZE( ( DUMMY_WRAPPER (initial_argument, arg2, arg3)))
#define ARGS initial_argument, arg2, arg3
#pragma message STRINGIZE( ( DUMMY_WRAPPER (ARGS) ))

(Некоторые) Исследования, которые могут быть связаны (не уверен)

https://gustedt.wordpress.com/2010/06/08/detect-empty-macro-arguments/

Не уверен, что это может объяснить что-то здесь, хотя, на мой взгляд, интуитивно нет ...

1 Ответ

3 голосов
/ 06 января 2020

Когда ваш компилятор видит это: DUMMY_WRAPPER (ARGS)

Он будет использовать ARGS в качестве первого параметра DUMMY_WRAPPER, даже если расширение ARGS содержит запятые.

Эту проблему можно решить, удалив первый параметр из DUMMY_WRAPPER и используя __VA_ARGS__:

#define DUMMY_WRAPPER(...) DUMMY(__VA_ARGS__)

ИЛИ , включив DUMMY_WRAPPER в другой макрос:

#define DUMMY_WRAPPER_2(...) DUMMY_WRAPPER(__VA_ARGS__)

Фактически, ваш код вообще не должен компилироваться, поскольку параметр макроса ... должен получить хотя бы один аргумент (который может быть пустым). DUMMY_WRAPPER(x) недействительно, но DUMMY_WRAPPER(x,) и DUMMY_WRAPPER(x,y) в порядке. (Это могло быть изменено в C ++ 20, я не уверен в этом.)

G CC и Clang отказываются компилировать код, если вы добавите -pedantic-errors.


Я бы также порекомендовал вам использовать другой подход к циклу. Тот, который вы используете, требует, чтобы вы генерировали O(n) код для обработки n элементов списка.

Можно сделать то же самое с кодом O(1), если вы немного измените синтаксис макроса:

#include <iostream>

#define STR(...) STR_(__VA_ARGS__)
#define STR_(...) #__VA_ARGS__

#define END(...) END_(__VA_ARGS__)
#define END_(...) __VA_ARGS__##_end

#define BODY(x) [x]
#define BODY_a(x) BODY(x) BODY_b
#define BODY_b(x) BODY(x) BODY_a
#define BODY_a_end
#define BODY_b_end

#define LOOP(seq) END(BODY_a seq)

int main()
{
    // Prints `[1] [2] [3]`
    std::cout << STR(LOOP( (1)(2)(3) )) << '\n';
}

Здесь LOOP может обрабатывать любое количество элементов без необходимости макроса макроса.

Это менее гибко, поскольку вы не можете передать любую информацию в l oop тело снаружи. Но этого должно быть достаточно для ваших нужд.

А вот версия, которая вставляет запятые между элементами:

#include <iostream>

#define STR(...) STR_(__VA_ARGS__)
#define STR_(...) #__VA_ARGS__

#define END(...) END_(__VA_ARGS__)
#define END_(...) __VA_ARGS__##_end

#define BODY(x) [x]
#define BODY_0(x)   BODY(x) BODY_a
#define BODY_a(x) , BODY(x) BODY_b
#define BODY_b(x) , BODY(x) BODY_a
#define BODY_0_end
#define BODY_a_end
#define BODY_b_end

#define LOOP(seq) END(BODY_0 seq)

int main()
{
    // Prints `[1] , [2] , [3]`
    std::cout << STR(LOOP( (1)(2)(3) )) << '\n';
}
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...