Стандартная альтернатива трюку ## 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, так как он будет расширяться до:

printf("this breaks!",);

обратите внимание на конечную запятую - не работает.

некоторые компиляторы (например: Visual Studio 2010) спокойно избавятся от этой конечной запятой для вас. Другие компиляторы (например, GCC) и поддержку положить ## перед __VA_ARGS__, например:

#define BAR(fmt, ...)  printf(fmt, ##__VA_ARGS__)

но есть совместимый со стандартами способ получить такое поведение? Возможно, с помощью нескольких макросов?

сейчас ## версия кажется довольно хорошо поддерживаемой (по крайней мере, на моих платформах), но я бы предпочел использовать совместимое со стандартами решение.

упреждающий: я знаю, что могу просто написать небольшую функцию. Я пытаюсь сделать это с помощью макросов.

Edit: вот пример (хотя и простой), почему я хотел бы использовать 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 126

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()

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

реализация проста. Элемент 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-го аргумента).

The 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 заключается в расширении до другого макроса "вызов".

обратите внимание, что сам 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 аргументов. Имена аргументов отсчитывают вниз (а не вверх), чтобы служить напоминанием о том, что счетчик на основе

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