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


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

Це автоматично додає новий рядок до моїх записів реєстрації BAR (), припускаючи, що fmtце завжди двоцитований C-рядок. Він НЕ друкує новий рядок як окремий printf (), що вигідно, якщо ведення журналу буферне і надходить з декількох джерел асинхронно.


3
Навіщо використовувати BARзамість цього FOOв першу чергу?
GManNickG

@GMan: Я додав приклад наприкінці
jwd

5
@GMan: Прочитайте останнє речення (:
jwd


2
@zwol останньої версії представленого WG14 виглядає наступним чином , який використовує новий синтаксис на основі __VA_OPT__ключових слів. Це вже було прийнято C ++, тому я очікую, що C піде за цим. (не знаю, чи означає це, що його швидко відстежували на C ++ 17 чи він встановлений для C ++ 20, хоча)
Левшенко

Відповіді:


66

Можна уникнути використання розширення GCC, ,##__VA_ARGS__якщо ви готові прийняти якусь тверду кодову верхню межу щодо кількості аргументів, які ви можете передати вашому різноманітному макросу, як описано у відповіді Річарда Хансена на це питання . Якщо ви не хочете мати такого обмеження, однак, наскільки мені відомо, неможливо використовувати лише визначені С99 функції препроцесора; ви повинні використовувати деяке розширення до мови. 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, але це було більше десяти років тому, і я ніколи не думав про хитрість підрахунку аргументів нижче.
zwol

6
Це розширення працює з компіляторами clang & intel icc, а також gcc.
Ациклік

112

Існує хитрість підрахунку аргументів, яку ви можете використовувати.

Ось один із стандартних способів реалізації другого 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 був затверджений без стандартного способу зробити це. ПЕРЕДАЧА проблема існує і в 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 сам не використовується. Я просто включив його, щоб проілюструвати тут основну хитрість. Дивіться блог 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! Тим не менш, я все ще кажу, що "не існує стандартного способу досягти цього ефекту", оскільки і ваші, і методи Річарда накладають верхню межу кількості аргументів макросу.
zwol

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

Це все ще спрацьовує попередження, коли компілюється з -педантиком.
PSkocik

1

У вашій ситуації (принаймні 1 аргумент присутній, ніколи 0), ви можете визначити BARяк BAR(...), використовуйте Jens Gustedt's 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(можна перейменувати на жорсткі колідні)

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

-2

Стандартним рішенням є використання FOOзамість BAR. Є кілька дивних випадків аргументації аргументів, які, ймовірно, не можуть зробити для вас (хоча, я думаю, хтось може придумати розумні хаки, щоб розібрати та зібрати знову, __VA_ARGS__умовно виходячи з кількості аргументів у ньому!), Але в цілому використовуючи FOO"зазвичай" просто працює.


1
Питання полягало в тому, "чи існує такий стандарт, як відповідати стандартам?"
Марш Рей

2
І питання включило обґрунтування того, що зараз не використовувати FOO.
Павло Шімерда
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.