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


person jwd    schedule 07.04.2011    source источник
comment
Зачем вообще использовать BAR вместо FOO?   -  person GManNickG    schedule 08.04.2011
comment
@GMan: я добавил пример в конце   -  person jwd    schedule 28.04.2011
comment
В этом случае вы можете просто создать макрос с несколькими операторами с printf("\n"); в конце.   -  person GManNickG    schedule 28.04.2011
comment
@GMan: Прочтите последнее предложение (:   -  person jwd    schedule 29.04.2011
comment
Эта функция была предложена для включения в C2x.   -  person Leushenko    schedule 22.03.2016
comment
@Leushenko Вы случайно не знаете, в каком состоянии находится это предложение на данный момент (чуть больше года спустя)?   -  person zwol    schedule 09.08.2017
comment
@zwol последняя версия, представленная в WG14, выглядит так:, в котором используется новый синтаксис на основе ключевого слова __VA_OPT__. Этот уже принят в C ++, поэтому Я ожидаю, что C последует этому примеру. (не знаю, означает ли это, что он был ускорен в C ++ 17 или установлен для C ++ 20)   -  person Leushenko    schedule 09.08.2017


Ответы (11)


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

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

person zwol    schedule 08.04.2011
comment
Судя по моей неспособности найти решение в сети и отсутствию здесь ответов, думаю, вы правы): - person jwd; 11.04.2011
comment
Вы имеете в виду n976? Я выполнил поиск в остальной части документы для ответа, но так и не нашли его. Этого даже не было в повестке дня следующей встречи.. Единственным другим ударом по этой теме был комментарий №4 Норвегии в n868 до ратификации C99 (опять же без дальнейшего обсуждения). - person Richard Hansen; 09.02.2012
comment
Да, именно вторую половину того. Возможно, там было обсуждение comp.std.c, но я не смог найти ни одного в группах Google прямо сейчас; это определенно никогда не привлекало внимания со стороны настоящего комитета (а если и привлекало, то мне об этом никто никогда не говорил). - person zwol; 09.02.2012
comment
Согласно этому ответу на связанный вопрос, существует стандартный способ подсчета аргументов макроса. Но я ничего не понимаю! - person Aaron McDaid; 30.10.2013
comment
У вас есть доказательства того, почему это невозможно в стандартном C? Мне интересно по теоретическим причинам. См. мой вопрос по этой теме. - person augurar; 31.01.2014
comment
Боюсь, у меня нет доказательств, и я больше не тот человек, который пытается их придумать. Я написал половину препроцессора GCC, но это было более десяти лет назад, и даже тогда я бы никогда не подумал о трюке с подсчетом аргументов, описанном ниже. - person zwol; 31.01.2014
comment
Это расширение работает с компиляторами clang и intel icc, а также с gcc. - person ACyclic; 17.12.2015

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

Вот один из стандартных способов реализовать второй пример 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() приведет к передаче только 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
person Richard Hansen    schedule 23.06.2012
comment
Обратите внимание, что это не удастся, если вы вызовете BAR с 10 или более аргументами, и хотя его относительно легко расширить до большего количества аргументов, он всегда будет иметь верхнюю границу количества аргументов, с которыми он может справиться. - person Chris Dodd; 20.04.2013
comment
@ChrisDodd: Верно. К сожалению, не существует способа избежать ограничения количества аргументов, не полагаясь на специфичные для компилятора расширения. Кроме того, я не знаю, как надежно проверить, слишком ли много аргументов (чтобы можно было распечатать полезное сообщение об ошибке компилятора, а не странный сбой). - person Richard Hansen; 20.04.2013

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

person Marsh Ray    schedule 29.12.2011
comment
проблема с этим дополнительным 0 в том, что он фактически попадет в код, если вызовет функцию vararg. Найдите решение, предоставленное Ричардом Хансеном - person Pavel P; 07.07.2012
comment
@Pavel прав во втором примере, но первый отлично работает. +1. - person kirbyfan64sos; 13.12.2014

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

or

#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__)))) \
    /**/
person DRayX    schedule 19.04.2013
comment
Приятно узнать о Boost.Preprocessor, +1. Обратите внимание, что BOOST_PP_VARIADIC_SIZE() использует тот же трюк с подсчетом аргументов, который я описал в своем ответе, и имеет такое же ограничение (он сломается, если вы передадите более определенного количества аргументов). - person Richard Hansen; 20.04.2013
comment
Да, я видел, что ваш подход был таким же, как и в Boost, но решение для повышения очень хорошо поддерживается и имеет множество других действительно полезных функций для использования при разработке более сложных макросов. Рекурсия особенно хороша (и используется за кулисами в последнем подходе, который использует BOOST_PP_ARRAY_ENUM). - person DRayX; 20.04.2013
comment
Ответ Boost, который действительно применим к тегу c! Ура! - person Justin; 30.10.2014

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

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

person SimonW    schedule 20.12.2018
comment
Небольшое предупреждение об этом коде, как написано: двойное подчеркивание в __DBG_INT считается чем-то, что приводит к неопределенному поведению. Это то, что маловероятно вызовет проблемы, но это полезно знать при написании вещей с нуля или рефакторинге - ситуациях, когда легко выбрать другое соглашение, например DBG_INT_ или DBG__INT. - person chadjoan; 09.11.2020
comment
Соответствующие фрагменты из стандарта C11 (N1570), 7.1.3 Зарезервированные идентификаторы: 1. Все идентификаторы, начинающиеся с символа подчеркивания и заглавной буквы или другого символа подчеркивания, всегда зарезервированы для любого использования. 2. Никакие другие идентификаторы не зарезервированы. Если программа объявляет или определяет идентификатор в контексте, в котором он зарезервирован (кроме разрешенного 7.1.4), или определяет зарезервированный идентификатор как имя макроса, поведение не определено. (Примечание: это также исключает что-то вроде _DBG_INT.) - person chadjoan; 09.11.2020
comment
Не знал, спасибо. Считается ли это неопределенным поведением также для C99? - person SimonW; 10.11.2020
comment
Пожалуйста; Я надеюсь, что это помогает. И да, это также UB в C99. Это тот же раздел (7.1.3, p1 и p2) в C99 / N1256. Удачи! - person chadjoan; 12.11.2020

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

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

Обратите внимание, что сам NUM_ARGS не используется. Я просто включил его, чтобы проиллюстрировать здесь основной трюк. См. блог Йенса Густедта о P99, где он подробно описан.

Два примечания:

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

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

person User123abc    schedule 22.06.2012
comment
+1 за использование трюка с подсчетом аргументов, -1 за то, что уследить за ним очень сложно - person Richard Hansen; 24.06.2012
comment
Добавленные вами комментарии были улучшением, но по-прежнему остается ряд проблем: 1. Вы обсуждаете и определяете NUM_ARGS, но не используете его. 2. Какова цель UNIMPLEMENTED? 3. Вы никогда не решите пример проблемы в вопросе. 4. Пошаговое изучение расширения покажет, как оно работает, и объяснит роль каждого вспомогательного макроса. 5. Обсуждение 0 аргументов отвлекает; OP спрашивал о соответствии стандартам, и 0 аргументов запрещено (C99 6.10.3p4). 6. Шаг 1.5? Почему не шаг 2? 7. Шаги подразумевают действия, которые происходят последовательно; это просто код. - person Richard Hansen; 24.06.2012
comment
8. Вы ссылаетесь на весь блог, а не на соответствующий пост. Я не смог найти сообщение, о котором вы говорите. 9. Последний абзац неудобен: этот метод неясен; вот почему никто раньше не публиковал правильное решение. Кроме того, если он работает и соответствует стандарту, ответ Зака ​​должен быть неправильным. 10. Вы должны дать определение CONCAT() - не думайте, что читатели знают, как это работает. - person Richard Hansen; 24.06.2012
comment
(Пожалуйста, не интерпретируйте этот отзыв как атаку - я действительно хотел проголосовать за ваш ответ, но не чувствовал себя комфортно, если его не упростили для понимания. Если вы можете улучшить ясность своего ответа, я проголосуйте за свой и удалите мой.) - person Richard Hansen; 24.06.2012
comment
Нет, это был честный вопрос. Я знаю, что эти макросы могут быть сложными. Поскольку я уже понял свой пост, я не знаю, какие его части сбивают с толку. Ваш подход отличается в полезном отношении, поэтому я думаю, вам не следует его удалять. (Это более сложно в реализации, но это избавляет пользователя от необходимости определять вспомогательные функции, такие как _UNIMPLEMENTED1 и _UNIMPLEMENTED2, каждый раз, когда они хотят его использовать). - person User123abc; 24.06.2012
comment
Я бы никогда не подумал об этом подходе, и я написал примерно половину текущего препроцессора GCC! Тем не менее, я все еще говорю, что не существует стандартного способа получить этот эффект, потому что и ваша техника, и методы Ричарда накладывают верхний предел на количество аргументов макроса. - person zwol; 24.03.2013

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

#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, который принимает аргумент «уровень» перед форматом. Если формат отсутствует, он регистрирует «(нет сообщения)», если есть только 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
*/
person ɲeuroburɳ    schedule 18.03.2018
comment
Это по-прежнему вызывает предупреждение при компиляции с -pedantic. - person PSkocik; 11.11.2018

если доступен c ++ 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); }
person YumeYao    schedule 02.04.2021

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

person PSkocik    schedule 11.11.2018

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)
person l4m2    schedule 16.03.2019
comment
Ограничение no arg contain comma можно обойти, проверив multi после еще нескольких проходов, но no bracket все еще существует - person l4m2; 16.03.2019

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

person R.. GitHub STOP HELPING ICE    schedule 08.04.2011
comment
Вопрос был в том, есть ли способ добиться такого поведения, соответствующий стандартам? - person Marsh Ray; 29.12.2011
comment
И в вопросе было объяснение того, что FOO давно не используется. - person Pavel Šimerda; 07.11.2014