Стандартная альтернатива трюку ## __ VA_ARGS__ в GCC? - PullRequest
137 голосов
/ 08 апреля 2011

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

Ответы [ 10 ]

107 голосов
/ 24 июня 2012

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

Вот один стандартный способ реализации второго 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-го аргумента).

Макрос NUM() использует макрос SELECT_10TH() для определения количества аргументов. Как следует из названия, SELECT_10TH() просто расширяется до 10-го аргумента. Из-за многоточия SELECT_10TH() необходимо передать как минимум 11 аргументов (стандарт гласит, что для многоточия должен быть хотя бы один аргумент). Вот почему NUM() передает throwaway в качестве последнего аргумента (без него передача одного аргумента в NUM() приведет к тому, что в SELECT_10TH() будет передано только 10 аргументов, что нарушит стандарт).

Выбор 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
58 голосов
/ 08 апреля 2011

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

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

15 голосов
/ 30 декабря 2011

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

10 голосов
/ 19 апреля 2013

Существует способ обработать этот конкретный случай, используя что-то вроде 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 голосов
/ 22 июня 2012

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

Ключевая идея заключается в том, что есть способ написать макрос 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 не используется. Я просто включил его, чтобы проиллюстрировать основной трюк здесь. См. Блог Jens Gustedt P99 , где можно найти хорошее решение.

Две ноты:

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

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

4 голосов
/ 20 декабря 2018

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

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

1 голос
/ 18 марта 2018

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

#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
*/
0 голосов
/ 16 марта 2019

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)
0 голосов
/ 11 ноября 2018

В вашей ситуации (по крайней мере, 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 без предупреждения.

0 голосов
/ 08 апреля 2011

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

...