3.6 Макросы с переменным числом аргументов ¶
Макрос может быть объявлен для приема переменного числа аргументов так же, как и функция. Синтаксис определения макроса аналогичен синтаксису функции. Вот пример:
#define eprintf(...) fprintf (stderr, __VA_ARGS__)
Этот тип макроса называется макросом с переменным числом аргументов. При вызове макроса все токены в списке аргументов после последнего именованного аргумента (в данном макросе их нет), включая запятые, становятся переменным аргументом. Эта последовательность токенов заменяет идентификатор __VA_ARGS__ в теле макроса везде, где он встречается. Таким образом, мы получаем такое развёртывание:
eprintf ("%s:%d: ", input_file, lineno)
→ fprintf (stderr, "%s:%d: ", input_file, lineno)
Переменной аргумент полностью макро-расширяется перед вставкой в макро-развертывание, как и обычный аргумент. Вы можете использовать операторы ‘#’ и ‘##’ для строкового представления переменного аргумента или для склеивания его ведущего или хвостового токена с другим токеном. (Но см. ниже важное исключение для ‘##’.)
Если ваш макрос сложный, вы можете захотеть более описательное имя для переменного аргумента, чем __VA_ARGS__. CPP позволяет это как расширение. Вы можете написать имя аргумента непосредственно перед ‘...’; это имя используется для переменного аргумента. Макрос eprintf выше можно записать
#define eprintf(args...) fprintf (stderr, args)
используя это расширение. Вы не можете использовать __VA_ARGS__ и это расширение в одном макросе.
Вы можете иметь именованные аргументы, а также переменные аргументы в макросе с переменным числом аргументов. Мы могли бы определить eprintf так:
#define eprintf(format, ...) fprintf (stderr, format, __VA_ARGS__)
Эта формулировка выглядит более описательной, но исторически она была менее гибкой: вам нужно было предоставить как минимум один аргумент после строки формата. В стандартном C вы не могли опустить запятую, разделяющую именованный аргумент и переменные аргументы. (Обратите внимание, что это ограничение было снято в C++2a, и никогда не существовало в GNU C; см. ниже.)
Кроме того, если вы оставили переменный аргумент пустым, у вас возникла бы синтаксическая ошибка, потому что после строки формата была бы лишняя запятая.
eprintf("success!\n", );
→ fprintf(stderr, "success!\n", );
Это было исправлено в C++2a, и GNU CPP также имеет пару расширений, которые решают эту проблему.
Во-первых, в GNU CPP, и в C++ начиная с C++2a, вам разрешено полностью пропустить переменный аргумент:
eprintf ("success!\n")
→ fprintf(stderr, "success!\n", );
Во-вторых, C++2a вводит макрос-функцию __VA_OPT__. Этот макрос может появляться только в определении макроса с переменным числом аргументов. Если у переменного аргумента есть какие-либо токены, то вызов __VA_OPT__ расширяется до своего аргумента; но если у переменного аргумента нет ни одного токена, то __VA_OPT__ расширяется до ничего:
#define eprintf(format, ...) \ fprintf (stderr, format __VA_OPT__(,) __VA_ARGS__)
__VA_OPT__ также доступен в GNU C и GNU C++.
Исторически, GNU CPP также имел другое расширение для обработки запятой в конце: оператор склеивания ‘##’ имеет специальное значение, когда помещается между запятой и переменным аргументом. Несмотря на введение __VA_OPT__, это расширение поддерживается в GNU CPP для обратной совместимости. Если вы напишите
#define eprintf(format, ...) fprintf (stderr, format, ##__VA_ARGS__)
и переменный аргумент пропущен при использовании макроса eprintf, тогда запятая перед ‘##’ будет удалена. Это не происходит, если вы передаете пустой аргумент, а также если токен, предшествующий ‘##’, это не запятая.
eprintf ("success!\n")
→ fprintf(stderr, "success!\n");
Вышеизложенное объяснение неоднозначно в случае, когда единственным параметром макроса является параметр переменного аргумента, так как бессмысленно пытаться различать, является ли отсутствие аргументов вообще пустым аргументом или отсутствующим аргументом. CPP сохраняет запятую, когда соответствует определённому стандарту C. В противном случае запятая отбрасывается в качестве расширения стандарта.
Стандарт C предписывает, что идентификатор __VA_ARGS__ может появляться только в списке замены макроса с переменным числом аргументов. Он не может использоваться как имя макроса, имя аргумента макроса или внутри другого типа макроса. Он также может быть запрещён в открытом тексте; стандарт неоднозначен. Рекомендуется избегать его использования, кроме его определённого назначения.
Аналогично, C++ запрещает __VA_OPT__ где угодно, кроме списка замены макроса с переменным числом аргументов.
Макросы с переменным числом аргументов стали стандартной частью языка C с C99. GNU CPP ранее поддерживал их с именованным переменным аргументом (‘args...’, а не ‘...’ и __VA_ARGS__), что по-прежнему поддерживается для обратной совместимости.
© Free Software Foundation
Licensed under the GNU Free Documentation License, Version 1.3.
https://gcc.gnu.org/onlinedocs/gcc-11.4.0/cpp/Variadic-Macros.html