Существует известная проблема с пустыми аргументами для вариационных макросов в C99.
пример:
#define FOO(...) printf(__VA_ARGS__)
#define BAR(fmt, ...) printf(fmt, __VA_ARGS__)
FOO("this works fine");
BAR("this breaks!");
Использование BAR()
вышеупомянутого действительно неверно в соответствии со стандартом C99, так как оно расширится до:
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);
Это автоматически добавляет новую fmt
строку в мои операторы ведения журнала BAR (), предполагая, что это всегда C-строка в двойных кавычках. Он НЕ печатает символ новой строки как отдельную функцию printf (), что выгодно, если ведение журнала буферизуется строкой и поступает из нескольких источников асинхронно.
BAR
а неFOO
в первую очередь?__VA_OPT__
ключевом слове. Это уже было «принято» C ++, поэтому я ожидаю, что C последует его примеру. (не знаю, означает ли это, что он был быстро отслежен в C ++ 17 или хотя он установлен для C ++ 20)Ответы:
Можно избежать использования
,##__VA_ARGS__
расширения GCC, если вы готовы принять какой-то жестко заданный верхний предел количества аргументов, которые вы можете передать своему вариационному макросу, как описано в ответе Ричарда Хансена на этот вопрос . Однако, если вы не хотите иметь какое-либо такое ограничение, насколько мне известно, это невозможно, используя только функции препроцессора, указанные в C99; Вы должны использовать какое-то расширение языка. clang и icc приняли это расширение GCC, а MSVC - нет.Еще в 2001 году я написал расширение GCC для стандартизации (и связанное расширение, которое позволяет вам использовать имя, отличное от
__VA_ARGS__
параметра rest) в документе N976 , но оно не получило никакого ответа от комитета; Я даже не знаю, читал ли кто-нибудь это. В 2016 году его снова предложили в N2023 , и я призываю всех, кто знает, как это предложение сообщит нам в комментариях.источник
comp.std.c
но я не смог найти ни одного в группах Google только сейчас; это, конечно, никогда не получало никакого внимания со стороны реального комитета (или, если это так, никто никогда не говорил мне об этом).Есть способ подсчета аргументов, который вы можете использовать.
Вот один стандартный способ реализации второго
BAR()
примера в вопросе jwd:Этот же трюк используется для:
__VA_ARGS__
объяснение
Стратегия состоит в том, чтобы разделить
__VA_ARGS__
на первый аргумент и остальные (если таковые имеются). Это позволяет вставлять вещи после первого аргумента, но перед вторым (если есть).FIRST()
Этот макрос просто расширяется до первого аргумента, отбрасывая остальные.
Реализация проста. В
throwaway
аргументе гарантирует , чтоFIRST_HELPER()
получает два аргумента, который требуется , поскольку...
потребности по крайней мере один. С одним аргументом он расширяется следующим образом:FIRST(firstarg)
FIRST_HELPER(firstarg, throwaway)
firstarg
С двумя или более он расширяется следующим образом:
FIRST(firstarg, secondarg, thirdarg)
FIRST_HELPER(firstarg, secondarg, thirdarg, throwaway)
firstarg
REST()
Этот макрос распространяется на все, кроме первого аргумента (включая запятую после первого аргумента, если существует более одного аргумента).
Реализация этого макроса намного сложнее. Общая стратегия состоит в том, чтобы подсчитать количество аргументов (один или более чем один), а затем расширить до либо
REST_HELPER_ONE()
(если дан только один аргумент), либоREST_HELPER_TWOORMORE()
(если дано два или более аргумента).REST_HELPER_ONE()
просто расширяется до нуля - после первого аргумента нет никаких аргументов, поэтому оставшиеся аргументы - это пустое множество.REST_HELPER_TWOORMORE()
также прост - он расширяется до запятой, за которой следует все, кроме первого аргумента.Аргументы подсчитываются с использованием
NUM()
макроса. Этот макрос раскрывается,ONE
если указан только один аргумент,TWOORMORE
если задано от двух до девяти аргументов, и прерывается, если дано 10 или более аргументов (поскольку он расширяется до 10-го аргумента).NUM()
Макрос используетSELECT_10TH()
макрос для определения количества аргументов. Как следует из названия,SELECT_10TH()
просто расширяется до 10-го аргумента. Из-за многоточияSELECT_10TH()
необходимо передать как минимум 11 аргументов (стандарт гласит, что для многоточия должен быть хотя бы один аргумент). Вот почемуNUM()
передаетсяthrowaway
как последний аргумент (без него передача одного аргументаNUM()
привела бы к передаче только 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 вы можете добавить новую строку, например:
Я считаю, что он игнорирует любые дополнительные аргументы, на которые нет ссылок в строке формата. Таким образом, вы могли бы даже сойти с рук:
Я не могу поверить, что C99 был одобрен без стандартного способа сделать это. AFAICT проблема существует и в C ++ 11.
источник
Есть способ справиться с этим конкретным случаем, используя что-то вроде Boost.Preprocessor . Вы можете использовать BOOST_PP_VARIADIC_SIZE, чтобы проверить размер списка аргументов, а затем условно развернуть его до другого макроса. Единственный недостаток этого заключается в том, что он не может различить 0 и 1 аргумент, и причина этого становится понятной, если учесть следующее:
Пустой список аргументов макроса фактически состоит из одного аргумента, который оказывается пустым.
В этом случае нам повезло, так как у желаемого макроса всегда есть хотя бы 1 аргумент, мы можем реализовать его как два макроса «перегрузки»:
А затем еще один макрос для переключения между ними, например:
или
В зависимости от того, что вы найдете более читабельным (я предпочитаю первый, поскольку он дает вам общую форму для перегрузки макросов по числу аргументов).
Это также возможно сделать с помощью одного макроса, открыв и изменив список переменных аргументов, но он гораздо менее читабелен и очень специфичен для этой проблемы:
Кроме того, почему нет BOOST_PP_ARRAY_ENUM_TRAILING? Это сделало бы это решение намного менее ужасным.
Изменить: Хорошо, вот BOOST_PP_ARRAY_ENUM_TRAILING и версия, которая его использует (это теперь мое любимое решение):
источник
BOOST_PP_VARIADIC_SIZE()
используется тот же прием подсчета аргументов, который я задокументировал в своем ответе, и имеет то же ограничение (оно сломается, если вы передадите более определенного количества аргументов).Очень простой макрос, который я использую для отладочной печати:
Независимо от того, сколько аргументов передано в DBG, предупреждение c99 отсутствует.
Хитрость заключается в
__DBG_INT
добавлении фиктивного параметра, поэтому у него...
всегда будет хотя бы один аргумент, и c99 удовлетворен.источник
Недавно я столкнулся с подобной проблемой, и я верю, что есть решение.
Основная идея заключается в том, что есть способ написать макрос
NUM_ARGS
для подсчета количества аргументов, которые задаются в виде вариационного макроса. Вы можете использовать вариациюNUM_ARGS
для сборкиNUM_ARGS_CEILING2
, которая может сказать вам, задан ли макросу с переменным числом аргументов 1 аргумент или 2 или более аргументов. Затем вы можете написать свойBar
макрос так, чтобы он использовалNUM_ARGS_CEILING2
иCONCAT
отправлять свои аргументы одному из двух вспомогательных макросов: один, который ожидает ровно 1 аргумент, и другой, который ожидает переменное число аргументов больше 1.Вот пример, где я использую этот трюк для написания макроса
UNIMPLEMENTED
, который очень похож наBAR
:ШАГ 1:
ШАГ 1.5:
Шаг 2:
ШАГ 3:
Где CONCAT реализован обычным способом. В качестве быстрой подсказки, если вышеупомянутое кажется запутанным: цель CONCAT заключается в расширении до другого макроса «вызов».
Обратите внимание, что сам NUM_ARGS не используется. Я просто включил его, чтобы проиллюстрировать основной трюк здесь. См. Блог Jens Gustedt P99 для хорошего рассмотрения этого.
Две заметки:
NUM_ARGS ограничен числом аргументов, которые он обрабатывает. Моя может обрабатывать только до 20, хотя число совершенно произвольно.
NUM_ARGS, как показано, имеет ловушку в том, что он возвращает 1, когда дано 0 аргументов. Суть в том, что NUM_ARGS технически считает [запятые + 1], а не аргументы. В данном конкретном случае это действительно работает в наших интересах. _UNIMPLEMENTED1 прекрасно справится с пустым токеном и избавит нас от необходимости писать _UNIMPLEMENTED0. У Гастедта есть обходной путь для этого, хотя я не использовал его, и я не уверен, сработает ли это для того, что мы здесь делаем.
источник
NUM_ARGS
но не используете его. 2. Какова цельUNIMPLEMENTED
? 3. Вы никогда не решите примерную проблему в вопросе. 4. Пройдя шаг за шагом, покажите, как это работает, и объясните роль каждого вспомогательного макроса. 5. Обсуждение 0 аргументов отвлекает; ФП спрашивал о соответствии стандартам, и 0 аргументов запрещены (C99 6.10.3p4). 6. Шаг 1.5? Почему бы не шаг 2? 7. «Шаги» подразумевают действия, которые происходят последовательно; это всего лишь код.CONCAT()
- не думайте, что читатели знают, как это работает.Это упрощенная версия, которую я использую. Это основано на замечательных методах других ответов здесь, так много опор для них:
Вот и все.
Как и в других решениях, это ограничено количеством аргументов макроса. Чтобы поддерживать больше, добавьте больше параметров
_SELECT
и большеN
аргументов. ИменаSUFFIX
аргументов обратного отсчета (а не вверх) служат напоминанием о том, что основанный на подсчете аргумент предоставляется в обратном порядке.Это решение обрабатывает 0 аргументов, как будто это 1 аргумент. Таким образом,
BAR()
номинально «работает», потому что расширяется до того_SELECT(_BAR,,N,N,N,N,1)()
, что расширяется до того_BAR_1()()
, что расширяется доprintf("\n")
.Если вы хотите, вы можете проявить творческий подход с использованием
_SELECT
и предоставить разные макросы для разного количества аргументов. Например, здесь у нас есть макрос LOG, который принимает аргумент 'level' перед форматом. Если формат отсутствует, он регистрирует «(нет сообщения)», если есть только 1 аргумент, он регистрирует его через «% s», в противном случае он будет обрабатывать аргумент формата как строку формата printf для оставшихся аргументов.источник
В вашей ситуации (по крайней мере 1 аргумент присутствует, а не 0) вы можете определить
BAR
какBAR(...)
, использовать Jens GustedtHAS_COMMA(...)
для обнаружения запятой, а затем отправлятьBAR0(Fmt)
илиBAR1(Fmt,...)
соответственно.Это:
компилируется
-pedantic
без предупреждения.источник
C (gcc) , 762 байта
Попробуйте онлайн!
Предполагает:
A
~G
(можно переименовать в hard_collide)источник
no arg contain comma
Ограничение может быть обойдено путем проверки мульти после еще нескольких проходов, но всеno bracket
еще тамСтандартным решением является использование
FOO
вместоBAR
. Есть несколько странных случаев переупорядочения аргументов, которые он, вероятно, не может сделать для вас (хотя, держу пари, кто-то может придумать хитроумные хаки, чтобы разобрать и__VA_ARGS__
условно собрать в зависимости от количества аргументов в нем!), Но в общем случае с использованиемFOO
«обычно» просто работает.источник