Стандартная альтернатива трюку GCC##__VA_ARGS__?
Существует известная проблема с пустыми аргументами для вариационных макросов в 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);
Это автоматически добавляет новую строку в мои операторы ведения журнала BAR (), предполагая, fmt
всегда C-строка в двойных кавычках. Он НЕ печатает новую строку как отдельную функцию printf(), что выгодно, если регистрация ведется с буферизацией строки и поступает из нескольких источников асинхронно.
12 ответов
Можно избежать использования GCC ,##__VA_ARGS__
расширение, если вы готовы принять какой-то жестко заданный верхний предел количества аргументов, которые вы можете передать в свой вариационный макрос, как описано в ответе Ричарда Хансена на этот вопрос. Однако, если вы не хотите иметь какое-либо такое ограничение, насколько мне известно, это невозможно, используя только функции препроцессора, указанные в C99; Вы должны использовать какое-то расширение языка. clang и icc приняли это расширение GCC, а MSVC - нет.
Еще в 2001 году я написал расширение GCC для стандартизации (и связанное расширение, которое позволяет вам использовать имя, отличное от __VA_ARGS__
для параметра rest) в документе N976, но он не получил никакого ответа от комитета; Я даже не знаю, читал ли кто-нибудь это. В 2016 году оно снова было предложено в N2023, и я призываю всех, кто знает, как это предложение сообщит нам в комментариях.
Есть способ подсчета аргументов, который вы можете использовать.
Вот один стандартный способ реализации второго BAR()
пример в вопросе jwd:
#include <stdio.h>
#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__
объяснение
Стратегия состоит в том, чтобы отделить __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 вы можете добавить новую строку, например:
#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.
Очень простой макрос, который я использую для отладочной печати:
#define __DBG_INT(fmt, ...) printf(fmt "%s", __VA_ARGS__);
#define DBG(...) __DBG_INT(__VA_ARGS__, "\n")
int main() {
DBG("No warning here");
DBG("and we can add as many arguments as needed. %s", "nice!");
return 0;
}
Независимо от того, сколько аргументов передано в DBG, предупреждение c99 отсутствует.
Хитрость в том, __DBG_INT
добавив фиктивный параметр так ...
всегда будет иметь хотя бы один аргумент и c99 удовлетворен.
Есть способ справиться с этим конкретным случаем, используя что-то вроде 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
Пустой список аргументов макроса на самом деле состоит из одного аргумента, который оказывается пустым.
В этом случае нам повезло, так как у желаемого макроса всегда есть хотя бы 1 аргумент, мы можем реализовать его как два макроса "перегрузки":
#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
подсчитать количество аргументов, которые задаются с помощью макроса variadic. Вы можете использовать вариацию 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 P99 для хорошего обращения с ним.
Две заметки:
NUM_ARGS ограничен числом аргументов, которые он обрабатывает. Моя может обрабатывать только до 20, хотя число абсолютно произвольно.
NUM_ARGS, как показано, имеет ловушку в том, что он возвращает 1, когда дано 0 аргументов. Суть в том, что NUM_ARGS технически считает [запятые + 1], а не аргументы. В данном конкретном случае это на самом деле работает в наших интересах. _UNIMPLEMENTED1 прекрасно справится с пустым токеном и избавит нас от необходимости писать _UNIMPLEMENTED0. У Гастедта есть обходной путь для этого, хотя я не использовал его, и я не уверен, сработает ли это для того, что мы здесь делаем.
Если вы используете
gcc 8+
,
clang 6+
или же
MSVC 2019
(источник), то вы также можете использовать (новее)
__VA_OPT__
макрос, который условно расширяется, если
__VA_ARGS__
непусто.
Таким образом, мы можем преобразовать два
FOO
а также
BAR
макросы в один:
#define FOO(s, ...) printf(s __VA_OPT__(,) __VA_ARGS__)
так что,
FOO("hello!")
расширится до
printf("hello!")
, а также
FOO("x = %d", 5)
расширится до
printf("x = %d", 5)
.
Это относительно новая функция (появившаяся в C++2a), поэтому ваш компилятор может ее еще не поддерживать.
Это упрощенная версия, которую я использую. Это основано на замечательных методах других ответов здесь, так много опор для них:
#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
аргументы. Имена аргументов обратного отсчета (а не вверх), чтобы служить напоминанием о том, что на основе подсчета SUFFIX
Аргумент предоставляется в обратном порядке.
Это решение обрабатывает 0 аргументов, как будто это 1 аргумент. Так BAR()
номинально "работает", потому что расширяется до _SELECT(_BAR,,N,N,N,N,1)()
, который расширяется до _BAR_1()()
, который расширяется до printf("\n")
,
Если вы хотите, вы можете проявить творческий подход с использованием _SELECT
и предоставить разные макросы для разного количества аргументов. Например, здесь у нас есть макрос LOG, который принимает аргумент 'level' перед форматом. Если формат отсутствует, он регистрирует "(нет сообщения)", если есть только 1 аргумент, он регистрирует его через "%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
*/
В вашей ситуации (по крайней мере, 1 аргумент присутствует, никогда не 0), вы можете определить BAR
как BAR(...)
ДЖЕНС ГУСТТ HAS_COMMA(...)
обнаружить запятую, а затем отправить BAR0(Fmt)
или же BAR1(Fmt,...)
соответственно.
Это:
#define HAS_COMMA(...) HAS_COMMA_16__(__VA_ARGS__, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0)
#define HAS_COMMA_16__(_0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, ...) _15
#define CAT_(X,Y) X##Y
#define CAT(X,Y) CAT_(X,Y)
#define BAR(.../*All*/) CAT(BAR,HAS_COMMA(__VA_ARGS__))(__VA_ARGS__)
#define BAR0(X) printf(X "\n")
#define BAR1(X,...) printf(X "\n",__VA_ARGS__)
#include <stdio.h>
int main()
{
BAR("here is a log message");
BAR("here is a log message with a param: %d", 42);
}
компилируется с -pedantic
без предупреждения.
если доступен С++ 11 или выше, и макрос предназначен для расширения до вызова функции, вы можете сделать для него оболочку, например:
#define BAR(fmt, ...) printf(fmt, __VA_ARGS__)
может быть преобразован в
#define BAR(fmt, ...) BAR_wrapper(fmt)(__VA_ARGS__)
куда
BAR_wrapper
можно определить как:
struct BAR_wrapper_t {
BAR_wrapper_t(const char* fmt) : fmt(fmt) {}
const char* fmt;
int operator()() const { return printf(fmt); }
template <typename... Args>
int operator()(Args&& args) const { return printf(fmt, std::forward<Args>(args)...); }
};
inline BAR_wrapper_t BAR_wrapper(const char* fmt) { return BAR_wrapper_t(fmt); }
C (gcc), 762 байта
#define EMPTYFIRST(x,...) A x (B)
#define A(x) x()
#define B() ,
#define EMPTY(...) C(EMPTYFIRST(__VA_ARGS__) SINGLE(__VA_ARGS__))
#define C(...) D(__VA_ARGS__)
#define D(x,...) __VA_ARGS__
#define SINGLE(...) E(__VA_ARGS__, B)
#define E(x,y,...) C(y(),)
#define NONEMPTY(...) F(EMPTY(__VA_ARGS__) D, B)
#define F(...) G(__VA_ARGS__)
#define G(x,y,...) y()
#define STRINGIFY(...) STRINGIFY2(__VA_ARGS__)
#define STRINGIFY2(...) #__VA_ARGS__
#define BAR(fmt, ...) printf(fmt "\n" NONEMPTY(__VA_ARGS__) __VA_ARGS__)
int main() {
puts(STRINGIFY(NONEMPTY()));
puts(STRINGIFY(NONEMPTY(1)));
puts(STRINGIFY(NONEMPTY(,2)));
puts(STRINGIFY(NONEMPTY(1,2)));
BAR("here is a log message");
BAR("here is a log message with a param: %d", 42);
}
Предполагает:
- Аргумент не содержит запятую или скобку
- Нет аргументов, содержащих
A
~G
(можно переименовать в hard_collide)
Стандартное решение заключается в использовании FOO
вместо BAR
, Есть несколько странных случаев переупорядочения аргументов, которые, вероятно, вам не подойдут (хотя, держу пари, кто-то может придумать хитрые хаки, чтобы разобрать и собрать) __VA_ARGS__
условно исходя из количества аргументов в нем!) но в целом использую FOO
"обычно" просто работает.