Стандартная альтернатива трюку ## __ VA_ARGS__ GCC?
Существует известная проблема с пустыми аргументами для вариативных макросов в C99.
пример:
#define FOO(...) printf(__VA_ARGS__) #define BAR(fmt, ...) printf(fmt, __VA_ARGS__) FOO("this works fine"); BAR("this breaks!");
Использование BAR()
выше действительно неверно в соответствии со стандартом C99, поскольку оно будет расширяться до:
- Считает ли стандарт C, что в этом заголовке есть один или два типа struct uperms_entry?
- Определяет ли ANSI C или ISO C, что такое -5% 10?
- Диапазон возвращаемых значений основной функции
- учебники по государственным машинам
- Объявления переменной C после заголовка функции в определении
printf("this breaks!",);
Обратите внимание, что конечная запятая не работает.
Некоторые компиляторы (например: Visual Studio 2010) будут спокойно избавляться от этой конечной запятой для вас. Другие компиляторы (например, GCC) поддерживают ##
перед __VA_ARGS__
, например:
#define BAR(fmt, ...) printf(fmt, ##__VA_ARGS__)
Но есть ли способ, соответствующий стандартам, получить такое поведение? Возможно, используя несколько макросов?
Прямо сейчас версия ##
выглядит довольно хорошо (по крайней мере, на моих платформах), но я бы предпочел использовать стандартное решение.
Упреждающий: я знаю, что могу просто написать небольшую функцию. Я пытаюсь сделать это с помощью макросов.
Изменить : Вот пример (хотя и простой), почему я хотел бы использовать BAR ():
#define BAR(fmt, ...) printf(fmt "\n", ##__VA_ARGS__) BAR("here is a log message"); BAR("here is a log message with a param: %d", 42);
Это автоматически добавляет новую строку к моим операторам записи BAR (), предполагая, что fmt
всегда является двустрочной C-строкой. Он НЕ печатает новую строку как отдельный printf (), что выгодно, если ведение журнала буферизируется по строке и происходит из нескольких источников асинхронно.
- Встроенная функция C99 в формате .c
- Предварительные определения на C99 и ссылки
- Почему смешивание деклараций и кода запрещено до C99?
- Можно ли перебирать аргументы в переменных массивах?
- Почему C ++ 11 не поддерживает назначенные списки инициализаторов как C99?
- Что означает точка (.) В инициализаторе структуры?
- Сначала препроцессор C комментирует или расширяет macros?
- Как использовать составные литералы для `fprintf ()` нескольких форматированных чисел с произвольными базами?
Можно избежать использования расширения ,##__VA_ARGS__
GCC ,##__VA_ARGS__
если вы согласны принять какой-то ,##__VA_ARGS__
верхнего предела на количество аргументов, которые вы можете передать на ваш переменный макрос, как описано в ответе Ричарда Хансена на этот вопрос . Однако, если вы не хотите иметь какой-либо такой предел, насколько мне известно, это невозможно, используя только префиксные функции с C99; вы должны использовать некоторое расширение для языка. clang и icc приняли это расширение GCC, но MSVC этого не сделал.
Еще в 2001 году я написал расширение GCC для стандартизации (и связанное расширение, которое позволяет использовать другое имя, кроме __VA_ARGS__
для параметра rest) в документе N976 , но не получило никакого ответа от комитета; Я даже не знаю, прочитал ли кто-нибудь. В 2016 году это было предложено снова в N2023 , и я призываю всех, кто знает, как это предложение будет сообщать нам в комментариях.
Существует аргумент подсчета, который вы можете использовать.
Вот один стандартный способ реализации второго примера BAR()
в jwd:
#include #define BAR(...) printf(FIRST(__VA_ARGS__) "\n" REST(__VA_ARGS__)) /* expands to the first argument */ #define FIRST(...) FIRST_HELPER(__VA_ARGS__, throwaway) #define FIRST_HELPER(first, ...) first /* * if there's only one argument, expands to nothing. if there is more * than one argument, expands to a comma followed by everything but * the first argument. only supports up to 9 arguments but can be * trivially expanded. */ #define REST(...) REST_HELPER(NUM(__VA_ARGS__), __VA_ARGS__) #define REST_HELPER(qty, ...) REST_HELPER2(qty, __VA_ARGS__) #define REST_HELPER2(qty, ...) REST_HELPER_##qty(__VA_ARGS__) #define REST_HELPER_ONE(first) #define REST_HELPER_TWOORMORE(first, ...) , __VA_ARGS__ #define NUM(...) \ SELECT_10TH(__VA_ARGS__, TWOORMORE, TWOORMORE, TWOORMORE, TWOORMORE,\ TWOORMORE, TWOORMORE, TWOORMORE, TWOORMORE, ONE, throwaway) #define SELECT_10TH(a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, ...) a10 int main(int argc, char *argv[]) { BAR("first test"); BAR("second test: %s", "a string"); return 0; }
Этот же трюк используется для:
- подсчитать количество аргументов
- расширяться по-разному в зависимости от количества аргументов
- добавить в
__VA_ARGS__
объяснение
Страtagsя состоит в том, чтобы отделить __VA_ARGS__
в первом аргументе, а остальные (если есть). Это позволяет вставлять материал после первого аргумента, но до второго (если есть).
FIRST()
Этот макрос просто расширяется до первого аргумента, отбрасывая остальные.
Реализация прост. Параметр FIRST_HELPER()
гарантирует, что FIRST_HELPER()
получает два аргумента, что требуется, потому что ...
требуется хотя бы один. С одним аргументом он расширяется следующим образом:
-
FIRST(firstarg)
-
FIRST_HELPER(firstarg, throwaway)
-
firstarg
С двумя или более, он расширяется следующим образом:
-
FIRST(firstarg, secondarg, thirdarg)
-
FIRST_HELPER(firstarg, secondarg, thirdarg, throwaway)
-
firstarg
REST()
Этот макрос расширяется до всего, кроме первого аргумента (включая запятую после первого аргумента, если имеется более одного аргумента).
Реализация этого макроса намного сложнее. Общая страtagsя состоит в том, чтобы подсчитать количество аргументов (один или несколько), а затем развернуть до REST_HELPER_ONE()
(если указан только один аргумент) или REST_HELPER_TWOORMORE()
(если REST_HELPER_TWOORMORE()
два или более аргумента). REST_HELPER_ONE()
просто расширяется до нуля – после первого нет аргументов, поэтому остальные аргументы являются пустым. REST_HELPER_TWOORMORE()
также прост – он расширяется до запятой, за которой следуют все, кроме первого аргумента.
Аргументы подсчитываются с использованием макроса NUM()
. Этот макрос расширяется до ONE
если задан только один аргумент, TWOORMORE
если TWOORMORE
от двух до девяти аргументов, и ломается, если дано 10 или более аргументов (потому что оно расширяется до 10-го аргумента).
Макрос NUM()
использует макрос SELECT_10TH()
для определения количества аргументов. Как следует из его названия, SELECT_10TH()
просто расширяется до 10-го аргумента. Из-за многоточия SELECT_10TH()
необходимо передать не менее 11 аргументов (стандарт говорит, что для эллипсиса должен быть хотя бы один аргумент). Вот почему NUM()
передает throwaway
в качестве последнего аргумента (без него передача одного аргумента в NUM()
приведет к SELECT_10TH()
только 10 аргументов SELECT_10TH()
, что нарушит стандарт).
Выбор REST_HELPER_ONE()
или REST_HELPER_TWOORMORE()
выполняется путем объединения REST_HELPER_
с расширением NUM(__VA_ARGS__)
в REST_HELPER2()
. Обратите внимание, что целью REST_HELPER()
является обеспечение полного расширения NUM(__VA_ARGS__)
до объединения с REST_HELPER_
.
Расширение с одним аргументом выглядит следующим образом:
-
REST(firstarg)
-
REST_HELPER(NUM(firstarg), firstarg)
-
REST_HELPER2(SELECT_10TH(firstarg, TWOORMORE, TWOORMORE, TWOORMORE, TWOORMORE, TWOORMORE, TWOORMORE, TWOORMORE, TWOORMORE, ONE, throwaway), firstarg)
-
REST_HELPER2(ONE, firstarg)
-
REST_HELPER_ONE(firstarg)
- (Пусто)
Расширение с двумя или более аргументами выглядит следующим образом:
-
REST(firstarg, secondarg, thirdarg)
-
REST_HELPER(NUM(firstarg, secondarg, thirdarg), firstarg, secondarg, thirdarg)
-
REST_HELPER2(SELECT_10TH(firstarg, secondarg, thirdarg, TWOORMORE, TWOORMORE, TWOORMORE, TWOORMORE, TWOORMORE, TWOORMORE, TWOORMORE, TWOORMORE, ONE, throwaway), firstarg, secondarg, thirdarg)
-
REST_HELPER2(TWOORMORE, firstarg, secondarg, thirdarg)
-
REST_HELPER_TWOORMORE(firstarg, secondarg, thirdarg)
-
, secondarg, thirdarg
Не общее решение, но в случае printf вы можете добавить новую строку:
#define BAR_HELPER(fmt, ...) printf(fmt "\n%s", __VA_ARGS__) #define BAR(...) BAR_HELPER(__VA_ARGS__, "")
Я считаю, что он игнорирует любые дополнительные аргументы, которые не указаны в строке формата. Поэтому вы, возможно, даже избегаете:
#define BAR_HELPER(fmt, ...) printf(fmt "\n", __VA_ARGS__) #define BAR(...) BAR_HELPER(__VA_ARGS__, 0)
Я не могу поверить, что C99 был одобрен без стандартного способа сделать это. AFAICT проблема существует и в C ++ 11.
Есть способ обработать этот конкретный случай, используя что-то вроде Boost.Preprocessor . Вы можете использовать BOOST_PP_VARIADIC_SIZE, чтобы проверить размер списка аргументов, а затем условно перейти на другой макрос. Единственным недостатком этого является то, что он не может отличить от 0 до 1 аргумента, и причина этого становится очевидной, если вы считаете следующее:
BOOST_PP_VARIADIC_SIZE() // expands to 1 BOOST_PP_VARIADIC_SIZE(,) // expands to 2 BOOST_PP_VARIADIC_SIZE(,,) // expands to 3 BOOST_PP_VARIADIC_SIZE(a) // expands to 1 BOOST_PP_VARIADIC_SIZE(a,) // expands to 2 BOOST_PP_VARIADIC_SIZE(,b) // expands to 2 BOOST_PP_VARIADIC_SIZE(a,b) // expands to 2 BOOST_PP_VARIADIC_SIZE(a, ,c) // expands to 3
Список пустых макросов фактически состоит из одного аргумента, который оказывается пустым.
В этом случае нам повезло, так как ваш желаемый макрос всегда имеет хотя бы один аргумент, мы можем реализовать его как два макроса «перегрузки»:
#define BAR_0(fmt) printf(fmt "\n") #define BAR_1(fmt, ...) printf(fmt "\n", __VA_ARGS__)
И затем другой макрос, чтобы переключаться между ними, например:
#define BAR(...) \ BOOST_PP_CAT(BAR_, BOOST_PP_GREATER( BOOST_PP_VARIADIC_SIZE(__VA_ARGS__), 1))(__VA_ARGS__) \ /**/
или
#define BAR(...) BOOST_PP_IIF( \ BOOST_PP_GREATER(BOOST_PP_VARIADIC_SIZE(__VA_ARGS__), 1), \ BAR_1, BAR_0)(__VA_ARGS__) \ /**/
Какой бы вы ни находили более читаемым (я предпочитаю первый, поскольку он дает вам общую форму для перегрузки макросов по количеству аргументов).
Это также можно сделать с помощью одного макроса путем доступа и изменения списка переменных аргументов, но это менее читаемо и очень специфично для этой проблемы:
#define BAR(...) printf( \ BOOST_PP_VARIADIC_ELEM(0, __VA_ARGS__) "\n" \ BOOST_PP_COMMA_IF( \ BOOST_PP_GREATER(BOOST_PP_VARIADIC_SIZE(__VA_ARGS__), 1)) \ BOOST_PP_ARRAY_ENUM(BOOST_PP_ARRAY_POP_FRONT( \ BOOST_PP_VARIADIC_TO_ARRAY(__VA_ARGS__)))) \ /**/
Кроме того, почему нет BOOST_PP_ARRAY_ENUM_TRAILING? Это сделало бы это решение гораздо менее ужасным.
Изменить: Хорошо, вот BOOST_PP_ARRAY_ENUM_TRAILING и версия, которая его использует (теперь это мое любимое решение):
#define BOOST_PP_ARRAY_ENUM_TRAILING(array) \ BOOST_PP_COMMA_IF(BOOST_PP_ARRAY_SIZE(array)) BOOST_PP_ARRAY_ENUM(array) \ /**/ #define BAR(...) printf( \ BOOST_PP_VARIADIC_ELEM(0, __VA_ARGS__) "\n" \ BOOST_PP_ARRAY_ENUM_TRAILING(BOOST_PP_ARRAY_POP_FRONT( \ BOOST_PP_VARIADIC_TO_ARRAY(__VA_ARGS__)))) \ /**/
Недавно я столкнулся с подобной проблемой, и я считаю, что есть решение.
Основная идея заключается в том, что существует способ написать макрос NUM_ARGS
для подсчета количества аргументов, которые заданы переменным макросом. Вы можете использовать вариант NUM_ARGS
для создания NUM_ARGS_CEILING2
, который может рассказать вам, предоставляется ли переменному макросу 1 аргумент или 2 или более аргумента. Затем вы можете написать свой макрос Bar
чтобы он использовал NUM_ARGS_CEILING2
и CONCAT
для отправки своих аргументов в один из двух вспомогательных макросов: один, который ожидает ровно 1 аргумент, а другой, который ожидает переменное число аргументов больше 1.
Вот пример, когда я использую этот трюк для записи макроса UNIMPLEMENTED
, который очень похож на BAR
:
ШАГ 1:
/** * A variadic macro which counts the number of arguments which it is * passed. Or, more precisely, it counts the number of commas which it is * passed, plus one. * * Danger: It can't count higher than 20. If it's given 0 arguments, then it * will evaluate to 1, rather than to 0. */ #define NUM_ARGS(...) \ NUM_ARGS_COUNTER(__VA_ARGS__, 20, 19, 18, 17, 16, 15, 14, 13, \ 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1) #define NUM_ARGS_COUNTER(a1, a2, a3, a4, a5, a6, a7, \ a8, a9, a10, a11, a12, a13, \ a14, a15, a16, a17, a18, a19, a20, \ N, ...) \ N
ШАГ 1.5:
/* * A variant of NUM_ARGS that evaluates to 1 if given 1 or 0 args, or * evaluates to 2 if given more than 1 arg. Behavior is nasty and undefined if * it's given more than 20 args. */ #define NUM_ARGS_CEIL2(...) \ NUM_ARGS_COUNTER(__VA_ARGS__, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, \ 2, 2, 2, 2, 2, 2, 2, 1)
Шаг 2:
#define _UNIMPLEMENTED1(msg) \ log("My creator has forsaken me. %s:%s:%d." msg, __FILE__, \ __func__, __LINE__) #define _UNIMPLEMENTED2(msg, ...) \ log("My creator has forsaken me. %s:%s:%d." msg, __FILE__, \ __func__, __LINE__, __VA_ARGS__)
ШАГ 3:
#define UNIMPLEMENTED(...) \ CONCAT(_UNIMPLEMENTED, NUM_ARGS_CEIL2(__VA_ARGS__))(__VA_ARGS__)
Где CONCAT реализуется обычным способом. Как быстрая подсказка, если выше это кажется запутанным: цель CONCAT заключается в расширении до другого макроса «вызов».
Обратите внимание, что сам NUM_ARGS не используется. Я просто включил его, чтобы проиллюстрировать основной трюк здесь. См. Блог Jens Gustedt’s P99 для приятного лечения.
Две заметки:
-
NUM_ARGS ограничено количеством аргументов, которые он обрабатывает. Шахта может обрабатывать только до 20, хотя число абсолютно произвольно.
-
NUM_ARGS, как показано, имеет ловушку в том, что он возвращает 1 при заданных 0 аргументах. Суть его в том, что NUM_ARGS технически подсчитывает [запятые + 1], а не args. В этом конкретном случае это действительно работает в наших интересах. _UNIMPLEMENTED1 будет обрабатывать пустой токен просто отлично, и это избавит нас от необходимости писать _UNIMPLEMENTED0. У Густедта есть обходное решение для этого, хотя я его не использовал, и я не уверен, будет ли это работать для того, что мы делаем здесь.
Это упрощенная версия, которую я использую. Он основан на отличных методах других ответов здесь, поэтому многие реквизиты для них:
#define _SELECT(PREFIX,_5,_4,_3,_2,_1,SUFFIX,...) PREFIX ## _ ## SUFFIX #define _BAR_1(fmt) printf(fmt "\n") #define _BAR_N(fmt, ...) printf(fmt "\n", __VA_ARGS__); #define BAR(...) _SELECT(_BAR,__VA_ARGS__,N,N,N,N,1)(__VA_ARGS__) int main(int argc, char *argv[]) { BAR("here is a log message"); BAR("here is a log message with a param: %d", 42); return 0; }
Вот и все.
Как и в случае с другими решениями, это ограничение количества аргументов макроса. Чтобы поддерживать больше, добавьте дополнительные параметры в _SELECT
и еще N
аргументов. Имена аргументов обращаются вниз (вместо up), чтобы служить напоминанием о том, что аргумент SUFFIX
основе подсчета предоставляется в обратном порядке.
Это решение обрабатывает 0 аргументов, как если бы это 1 аргумент. Таким образом, BAR()
номинально «работает», потому что он расширяется до _SELECT(_BAR,,N,N,N,N,1)()
, который расширяется до _BAR_1()()
, который расширяется до printf("\n")
,
Если вы хотите, вы можете стать творческим с использованием _SELECT
и предоставить разные macros для различного количества аргументов. Например, здесь мы имеем макрос LOG, который принимает аргумент «level» перед форматом. Если формат отсутствует, он регистрирует «(нет сообщения)», если есть только один аргумент, он будет регистрировать его через «% s», иначе он будет обрабатывать аргумент формата как строку формата printf для остальных аргументов.
#define _LOG_1(lvl) printf("[%s] (no message)\n", #lvl) #define _LOG_2(lvl,fmt) printf("[%s] %s\n", #lvl, fmt) #define _LOG_N(lvl,fmt, ...) printf("[%s] " fmt "\n", #lvl, __VA_ARGS__) #define LOG(...) _SELECT(_LOG,__VA_ARGS__,N,N,N,2,1)(__VA_ARGS__) int main(int argc, char *argv[]) { LOG(INFO); LOG(DEBUG, "here is a log message"); LOG(WARN, "here is a log message with param: %d", 42); return 0; } /* outputs: [INFO] (no message) [DEBUG] here is a log message [WARN] here is a log message with param: 42 */
Стандартное решение – использовать FOO
вместо BAR
. Есть несколько странных случаев переупорядочения аргументов, которые, вероятно, не могут сделать для вас (хотя я уверен, кто-то может придумать умные хаки, чтобы разобрать и повторно собрать __VA_ARGS__
условно на основе количества аргументов в нем!), Но в целом с помощью FOO
” обычно “просто работает.