Стандартная альтернатива GCС## __ VA_ARGS__ трюк?

Существует известная проблема

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

Ответ 1

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

Еще в 2001 году я написал расширение GCC для стандартизации (и связанное расширение, которое позволяет вам использовать имя, отличное от __VA_ARGS__ для параметра rest) в документе N976, но оно не получило никакого ответа от комитета; Я даже не знаю, читал ли кто-нибудь это. В 2016 году оно снова было предложено в N2023, и я призываю всех, кто знает, как это предложение сообщит нам в комментариях.

Ответ 2

Существует аргумент подсчета аргументов, который вы можете использовать.

Вот один стандартный способ реализации второго примера 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 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() получает два аргумента, что требуется, потому что для ... требуется хотя бы одно. С одним аргументом он расширяется следующим образом:

  • 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

Ответ 3

Не общее решение, но в случае 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 проблема существует и в С++ 11.

Ответ 4

Есть способ обработать этот конкретный случай, используя что-то вроде 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__)))) \
    /**/

Ответ 5

В последнее время я столкнулся с подобной проблемой, и я считаю, что есть решение.

Основная идея заключается в том, что существует способ написать макрос 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 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 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 blog для приятного обращения с ним.

Две заметки:

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

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

Ответ 6

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

#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 творческий подход с помощью _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
*/

Ответ 7

В вашей ситуации (хотя бы один аргумент присутствует, но не 0), вы можете определить BAR как BAR(...), использовать HAS_COMMA 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 без предупреждения.

Ответ 8

Очень простой макрос, который я использую для отладочной печати:

#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 удовлетворен.

Ответ 9

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