Счетчик аргументов макроса Variadic не работает должным образом

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

Для простоты он работает только до 3 параметров. Проблема в том, что когда макрос используется с менее чем 3 параметрами, он не работает и вызывает ошибку «ожидаемое выражение».

#define EXPAND( x ) x
#define PP_NARG(...) EXPAND(PP_ARG_N(__VA_ARGS__, PP_RSEQ_N()))
#define PP_ARG_N(_1, _2, _3, N,...) N
#define PP_RSEQ_N() 3,2,1,0

void main()
{
    printf("\nTEST PP_NARG: %i", PP_NARG());        //Doesn't work (in this case it shouldn't work, so it's correct)
    printf("\nTEST PP_NARG: %i", PP_NARG(0));       //Doesn't work
    printf("\nTEST PP_NARG: %i", PP_NARG(0,0));     //Doesn't work
    printf("\nTEST PP_NARG: %i", PP_NARG(0,0,0));   //Works
}

Сохраняя только рабочую строку, она правильно компилируется и печатает «TEST PP_NARG: 3».

Я считаю, что проблема может заключаться в том, что PP_RSEQ_N() по какой-то причине расширяется только до «3», а не «3,2,1,0», поскольку даже если PP_RSEQ_N() определен как этот

#define PP_RSEQ_N() 10,9,8,7,6,5,4,3,2,1,0

он по-прежнему не работает с менее чем 3 параметрами.

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


person user4696876    schedule 20.08.2017    source источник
comment
Классический пример злоупотребления макросами.   -  person 0___________    schedule 21.08.2017
comment
Вот еще одна попытка расширения вариативного макроса MSVC++   -  person Bo Persson    schedule 21.08.2017


Ответы (2)


В вашей реализации PP_RSEQ_N() является аргументом PP_ARG_N. В качестве аргумента он расширяется только на этапе замены аргумента предварительной обработки, но это происходит только непосредственно перед заменой аргумента в его списке замены (при условии, что в списке замены он не волокнистый и не участвует в пасте).

Поскольку PP_ARG_N имеет только четвертый аргумент N в своем списке замены, PP_RSEQ_N() расширится только в том случае, если вы передадите три аргумента. (Есть второе сканирование во время фазы повторное сканирование и замена, которое применяется после аргумента замена... но это здесь не имеет значения, так как PP_RSEQ_N() упоминается в вызове).

Избавьтесь от этого макроса и просто поместите его в PP_NARG вот так:

#define EXPAND( x ) x
#define PP_NARG(...) EXPAND(PP_ARG_N(__VA_ARGS__, 3,2,1,0))
#define PP_ARG_N(_1, _2, _3, N,...) N

... и все "работает" нормально:

PP_NARG() заменяется на 1
PP_NARG(x) заменяется на 1
PP_NARG(x,y) заменяется на 2

Обратите внимание, однако, что PP_NARG() не дает вам 0. Возможно, это действительно правильно; препроцессору это не передает нулевые аргументы. Он передает один аргумент, который просто пуст. Это то же самое, что #define X(A) OPEN A CLOSE/X() дает OPEN CLOSE. Если по какой-то причине вы хотите, чтобы это расширилось до 0, могут быть какие-то хитрости, чтобы это произошло, но для этого ответа я сосредоточусь только на том, чтобы помочь вам преодолеть этот один горб.

person H Walters    schedule 20.08.2017
comment
Это не только Microsoft — это то, что указано в спецификации C: когда макрос с аргументами распознается, аргументы макроса собираются на основе потока токенов прямого ввода без расширения макроса (пока). Макросы в аргументах будут раскрыты ПОСЛЕ сбора аргументов, до их подстановки в тело. - person Chris Dodd; 21.08.2017

Реализация PP_ARG_N(), которая также может различать вызовы с параметрами и без них, можно найти здесь (престижность Скотт Моррисон). Ответ для вашей маленькой программы:

TEST PP_NARG: 0
TEST PP_NARG: 1
TEST PP_NARG: 2
TEST PP_NARG: 3
person lgnom    schedule 21.07.2021