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


151

Существует известная проблема с пустыми аргументами для вариационных макросов в 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);

Это автоматически добавляет новую fmtстроку в мои операторы ведения журнала BAR (), предполагая, что это всегда C-строка в двойных кавычках. Он НЕ печатает символ новой строки как отдельную функцию printf (), что выгодно, если ведение журнала буферизуется строкой и поступает из нескольких источников асинхронно.


3
Почему использовать, BARа не FOOв первую очередь?
GManNickG

@GMan: я добавил пример в конце
jwd

5
@GMan: Прочитайте последнее предложение (:
JWD


2
@zwol выглядит так: последняя версия, представленная WG14 , использует новый синтаксис, основанный на __VA_OPT__ключевом слове. Это уже было «принято» C ++, поэтому я ожидаю, что C последует его примеру. (не знаю, означает ли это, что он был быстро отслежен в C ++ 17 или хотя он установлен для C ++ 20)
Леушенко

Ответы:


66

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

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


2
Судя по моей неспособности найти решение в Интернете и отсутствию ответов здесь, я думаю, вы правы):
jwd

2
N976 - это то, что вы имеете в виду? Я искал остальную часть C рабочей группы «s документов для ответа , но не нашел. Этого даже не было в повестке дня следующей встречи . Единственным другим хитом на эту тему был комментарий Норвегии № 4 в n868, еще до того, как C99 был ратифицирован (опять же, без дальнейшего обсуждения).
Ричард Хансен

4
Да, конкретно вторая половина этого. Возможно, было обсуждение, comp.std.cно я не смог найти ни одного в группах Google только сейчас; это, конечно, никогда не получало никакого внимания со стороны реального комитета (или, если это так, никто никогда не говорил мне об этом).
zwol

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

6
Это расширение работает с компиляторами clang и intel icc, а также с gcc.
ACyclic

113

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

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

1
Обратите внимание, что это не удастся, если вы вызовете BAR с 10 или более аргументами, и хотя его относительно легко расширить на большее количество аргументов, он всегда будет иметь верхнюю границу количества аргументов, с которыми он может иметь дело
Крис Додд,

2
@ChrisDodd: правильно. К сожалению, не существует способа избежать ограничения количества аргументов, не полагаясь на специфичные для компилятора расширения. Кроме того, я не знаю, как надежно проверить, есть ли слишком много аргументов (чтобы можно было напечатать полезное сообщение об ошибке компилятора, а не странный сбой).
Ричард Хансен,

17

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


проблема с этим дополнительным 0 в том, что он на самом деле окажется в коде, если вызовет функцию vararg. Проверьте решение, предоставленное Ричардом Хансеном
Павел П

@Pavel верен во втором примере, но первый прекрасно работает. +1.
kirbyfan64sos

11

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

1
Приятно узнать о Boost.Preprocessor, +1. Обратите внимание, что BOOST_PP_VARIADIC_SIZE()используется тот же прием подсчета аргументов, который я задокументировал в своем ответе, и имеет то же ограничение (оно сломается, если вы передадите более определенного количества аргументов).
Ричард Хансен

1
Да, я видел, что ваш подход был таким же, как Boost, но решение Boost очень хорошо поддерживается и имеет много других действительно полезных функций для использования при разработке более сложных макросов. Материал рекурсии особенно хорош (и используется за кулисами в последнем подходе, который использует BOOST_PP_ARRAY_ENUM).
DRayX

1
Ответ Boost, который на самом деле относится к тегу c ! Ура!
Джастин

6

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

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


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'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. У Гастедта есть обходной путь для этого, хотя я не использовал его, и я не уверен, сработает ли это для того, что мы здесь делаем.


+1 за то, что поднял трюк с подсчетом аргументов, -1 за то, что очень трудно следовать
Ричард Хансен

Комментарии, которые вы добавили, были улучшением, но есть еще ряд вопросов: 1. Вы обсуждаете и определяете, NUM_ARGSно не используете его. 2. Какова цель UNIMPLEMENTED? 3. Вы никогда не решите примерную проблему в вопросе. 4. Пройдя шаг за шагом, покажите, как это работает, и объясните роль каждого вспомогательного макроса. 5. Обсуждение 0 аргументов отвлекает; ФП спрашивал о соответствии стандартам, и 0 аргументов запрещены (C99 6.10.3p4). 6. Шаг 1.5? Почему бы не шаг 2? 7. «Шаги» подразумевают действия, которые происходят последовательно; это всего лишь код.
Ричард Хансен

8. Вы ссылаетесь на весь блог, а не на соответствующий пост. Я не смог найти пост, на который вы ссылались. 9. Последний пункт является неуклюжим: Этот метод является неясным; Вот почему никто другой не опубликовал правильное решение раньше. Кроме того, если он работает и придерживается стандарта, ответ Зака ​​должен быть неправильным. 10. Вы должны определить CONCAT()- не думайте, что читатели знают, как это работает.
Ричард Хансен

(Пожалуйста, не воспринимайте этот отзыв как атаку - я действительно хотел высказать ваш ответ, но не чувствовал себя комфортно, если его не было легче понять. Если вы улучшите ясность своего ответа, я поставь свое голосование и удали мое.)
Ричард Хансен

2
Я никогда бы не подумал об этом подходе, и я написал примерно половину текущего препроцессора GCC! Тем не менее, я все еще говорю, что «не существует стандартного способа получить этот эффект», потому что как ваши, так и методы Ричарда накладывают верхний предел на число аргументов макроса.
Звол

2

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

#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
*/

Это по-прежнему вызывает предупреждение при компиляции с -pedantic.
PSkocik

1

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

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)

no arg contain commaОграничение может быть обойдено путем проверки мульти после еще нескольких проходов, но все no bracketеще там
l4m2

-2

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


1
Вопрос был в том, "есть ли соответствующий стандартам способ получить такое поведение?"
Marsh Ray

2
И этот вопрос включал обоснование того, что сейчас не следует использовать FOO целую вечность.
Павел Шимерда
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.