Стандартная альтернатива трюку ## VA ARGS GCC?

есть известный С пустыми args для variadic макросов в 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 (), что выгодно, если ведение журнала буферизовано и поступает из нескольких источников асинхронно.

7 ответов


можно избежать использования GCC ,##__VA_ARGS__ расширение если вы готовы принять некоторые жестко ограничение на количество аргументов можно передать с переменным числом аргументов макроса, как описано в ответ Ричарда Хансена на этот вопрос. Однако, если вы не хотите иметь такого ограничения, насколько мне известно, невозможно использовать только функции препроцессора, указанные в C99; вы должны использовать некоторое расширение языка. clang и icc приняли этот GCC расширение, но MSVC не имеет.

еще в 2001 году я написал расширение GCC для стандартизации (и связанное с ним расширение, которое позволяет использовать имя, отличное от __VA_ARGS__ для остальных-параметр) в документ 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__ в первый аргумент и остальные (если есть). Это позволяет вставлять материал после первого аргумента, но до второго (если есть).

FIRST()

этот макрос просто расширяется до первого аргумента, отбрасывая остальные.

реализация проста. The throwaway аргумент гарантирует, что FIRST_HELPER() получает два аргументы, которые требуются, потому что ... нужен хотя бы один. С одним аргументом он расширяется следующим образом:

  1. FIRST(firstarg)
  2. FIRST_HELPER(firstarg, throwaway)
  3. firstarg

с двумя или более, он расширяется следующим образом:

  1. FIRST(firstarg, secondarg, thirdarg)
  2. FIRST_HELPER(firstarg, secondarg, thirdarg, throwaway)
  3. 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_.

расширение с одним аргументом выглядит следующим образом:

  1. REST(firstarg)
  2. REST_HELPER(NUM(firstarg), firstarg)
  3. REST_HELPER2(SELECT_10TH(firstarg, TWOORMORE, TWOORMORE, TWOORMORE, TWOORMORE, TWOORMORE, TWOORMORE, TWOORMORE, TWOORMORE, ONE, throwaway), firstarg)
  4. REST_HELPER2(ONE, firstarg)
  5. REST_HELPER_ONE(firstarg)
  6. (пусто)

расширение с двумя или более аргументами идет как следует:

  1. REST(firstarg, secondarg, thirdarg)
  2. REST_HELPER(NUM(firstarg, secondarg, thirdarg), firstarg, secondarg, thirdarg)
  3. REST_HELPER2(SELECT_10TH(firstarg, secondarg, thirdarg, TWOORMORE, TWOORMORE, TWOORMORE, TWOORMORE, TWOORMORE, TWOORMORE, TWOORMORE, TWOORMORE, ONE, throwaway), firstarg, secondarg, thirdarg)
  4. REST_HELPER2(TWOORMORE, firstarg, secondarg, thirdarg)
  5. REST_HELPER_TWOORMORE(firstarg, secondarg, thirdarg)
  6. , 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_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? Это сделало бы это решение гораздо менее ужасно.

Edit: хорошо, вот 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 не используется. Я просто включил его, чтобы проиллюстрировать основной трюк. См.Йенс Gustedt это за хорошее обращение с ним.

два замечания:

  • NUM_ARGS ограничен в количестве аргументов, которые он обрабатывает. Шахта может обрабатывать только до 20, хотя число совершенно произвольное.

  • NUM_ARGS, как показано, имеет ловушку в том, что он возвращает 1 при заданных 0 аргументах. Суть в том, что num_args технически подсчитывает [запятые + 1], а не args. В этом частный случай, он фактически работает к нашему преимущество. _UNIMPLEMENTED1 будет обрабатывать пустой токен просто отлично и это избавляет нас от необходимости писать _UNIMPLEMENTED0. Gustedt имеет обходной путь для этого, хотя я не использовал его, и я не конечно, если это сработает для того, что мы здесь делаем.


это упрощенная версия, которую я использую. Он основан на великих техниках других ответов здесь, так много реквизита к ним:

#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), чтобы служить напоминанием о том, что count-based


стандартное решение-использовать FOO вместо BAR. Есть несколько странных случаев переупорядочивания аргументов, которые, вероятно, не могут сделать для вас (хотя я уверен, что кто-то может придумать умные хаки, чтобы разобрать и собрать __VA_ARGS__ условно на основе количества аргументов в нем!), но в целом, используя FOO "обычно" просто работает.