Препроцесор C ++ __VA_ARGS__ кількість аргументів


99

Просте запитання, на яке я не зміг знайти відповіді в мережі. Як у макросах різних аргументів як знайти кількість аргументів? Я добре з препроцесором підвищення, якщо він має рішення.

Якщо це має значення, я намагаюся перетворити змінну кількість макроаргументів для підвищення послідовності, списку або масиву препроцесора для подальшої обробки.


Щоб було зрозуміло - ви запитуєте про варіативні макроси, а не про макроси, які використовуються для створення варіативних функцій C?

2
аргументи однотипні? якщо так, і якщо тип відомий, існує стандартне рішення C через складні літерали; якщо це невідомо, ви можете використовувати __typeof__його для роботи принаймні над деякими компіляторами
Крістоф

1
Оскільки дискусія стосується послідовності препроцесора Boost тощо, вона повинна бути C ++ (саме тому я перезаписав Q - але не зміг назви питання) ... На жаль; Я це виправлю.
Джонатан Леффлер

@JonathanLeffler Щоправда, Boost - це бібліотека C ++. Однак Boost.Preprocessor можна використовувати з C. AFAIK, нічого, що він використовується, не є специфічним для C ++.
Джастін

Відповіді:


90

Це насправді залежить від компілятора і не підтримується жодним стандартом.

Однак тут у вас є макрореалізація , яка враховує кількість:

#define PP_NARG(...) \
         PP_NARG_(__VA_ARGS__,PP_RSEQ_N())
#define PP_NARG_(...) \
         PP_ARG_N(__VA_ARGS__)
#define PP_ARG_N( \
          _1, _2, _3, _4, _5, _6, _7, _8, _9,_10, \
         _11,_12,_13,_14,_15,_16,_17,_18,_19,_20, \
         _21,_22,_23,_24,_25,_26,_27,_28,_29,_30, \
         _31,_32,_33,_34,_35,_36,_37,_38,_39,_40, \
         _41,_42,_43,_44,_45,_46,_47,_48,_49,_50, \
         _51,_52,_53,_54,_55,_56,_57,_58,_59,_60, \
         _61,_62,_63,N,...) N
#define PP_RSEQ_N() \
         63,62,61,60,                   \
         59,58,57,56,55,54,53,52,51,50, \
         49,48,47,46,45,44,43,42,41,40, \
         39,38,37,36,35,34,33,32,31,30, \
         29,28,27,26,25,24,23,22,21,20, \
         19,18,17,16,15,14,13,12,11,10, \
         9,8,7,6,5,4,3,2,1,0

/* Some test cases */


PP_NARG(A) -> 1
PP_NARG(A,B) -> 2
PP_NARG(A,B,C) -> 3
PP_NARG(A,B,C,D) -> 4
PP_NARG(A,B,C,D,E) -> 5
PP_NARG(1,2,3,4,5,6,7,8,9,0,
         1,2,3,4,5,6,7,8,9,0,
         1,2,3,4,5,6,7,8,9,0,
         1,2,3,4,5,6,7,8,9,0,
         1,2,3,4,5,6,7,8,9,0,
         1,2,3,4,5,6,7,8,9,0,
         1,2,3) -> 63

.... але зараз це стандартне значення C ++ 0x і повинно було бути давніше, тому що це дозволяє чудовий спосіб захистити варадієві функції від пошкоджених викликів (тобто ви можете передавати значення після варадійних елементів. Це насправді спосіб отримання рахунку, який я використовував, але я гадаю, що розмір може також працювати ..
osirisgothra

Відповідь посилається на інший сайт. Також посилання, схоже, не вказує на правильну відповідь. І навіть якщо мені вдалося знайти заплановану відповідь, вона здається поганою, оскільки в неї вбудовується твердо кодований "-1", який буде складено. Є кращі методи.
ceztko

2
Дякую! це працювало у Visual Studio 2013 для мене: #define EXPAND(x) x #define PP_ARG_N(_1,_2,_3,_4,_5,_6,_7,_8,_9,N,...) N #define PP_NARG(...) EXPAND(PP_ARG_N(__VA_ARGS__, 9,8,7,6,5,4,3,2,1,0))`` `
mchiasson

1
PP_NARG()не повертається 0. Рішення GET_ARG_COUNT()& Y_TUPLE_SIZE()працюють.
PSkocik

1
" PP_NARG()не повертає 0" ... не обов'язково є проблемою. Можна сказати, що PP_NARG() слід повернути 1 з тієї ж причини, що PP_NARG(,)повернеться. 2. Виявлення 0 дійсно може бути зручним у деяких випадках, але рішення здаються або менш загальними (вимагає, щоб перший маркер був простір; який може бути, а може і не бути нормальним) залежно від того, для чого ви його використовуєте) або конкретної реалізації (наприклад, вимагає фокус для видалення та вставки гну gnu).
H Walters

100

Зазвичай я використовую цей макрос, щоб знайти ряд параметрів:

#define NUMARGS(...)  (sizeof((int[]){__VA_ARGS__})/sizeof(int))

Повний приклад:

#include <stdio.h>
#include <string.h>
#include <stdarg.h>

#define NUMARGS(...)  (sizeof((int[]){__VA_ARGS__})/sizeof(int))
#define SUM(...)  (sum(NUMARGS(__VA_ARGS__), __VA_ARGS__))

void sum(int numargs, ...);

int main(int argc, char *argv[]) {

    SUM(1);
    SUM(1, 2);
    SUM(1, 2, 3);
    SUM(1, 2, 3, 4);

    return 1;
}

void sum(int numargs, ...) {
    int     total = 0;
    va_list ap;

    printf("sum() called with %d params:", numargs);
    va_start(ap, numargs);
    while (numargs--)
        total += va_arg(ap, int);
    va_end(ap);

    printf(" %d\n", total);

    return;
}

Це повністю дійсний код C99. Однак у нього є один недолік - ви не можете викликати макрос SUM()без парам, але GCC має рішення для цього - дивіться тут .

Тож у випадку GCC вам потрібно визначити макроси так:

#define       NUMARGS(...)  (sizeof((int[]){0, ##__VA_ARGS__})/sizeof(int)-1)
#define       SUM(...)  sum(NUMARGS(__VA_ARGS__), ##__VA_ARGS__)

і він буде працювати навіть з порожнім списком параметрів


4
UM, він не працює для ОП, йому потрібен розмір для BOOST_PP, який працює на час компіляції.
Kornel Kisielewicz

5
Розумний! Це також працює, коли sizeof(int) != sizeof(void *)?
Адам Лісс

3
@Kornel Як і будь-який макрос, він оцінюється під час компіляції. Я не маю уявлення про Boost, але все одно Boost не потрібен.
qrdl

4
@Adam Оскільки я звернувся {__VA_ARGS__}до цього int[], це просто int[], незалежно від фактичного змісту__VA_ARGS__
qrdl

3
Елегантне рішення! Працює у VS2017. У ##VS2017 він не потрібний, оскільки порожній __VA_ARGS__автоматично видалить будь-яку попередню кому.
poby

37

Якщо ви використовуєте C ++ 11 і вам потрібне значення як константа часу компіляції C ++, дуже елегантним рішенням є таке:

#include <tuple>

#define MACRO(...) \
    std::cout << "num args: " \
    << std::tuple_size<decltype(std::make_tuple(__VA_ARGS__))>::value \
    << std::endl;

Зверніть увагу: підрахунок відбувається повністю під час компіляції, і це значення може використовуватися, коли потрібне ціле число часу компіляції, наприклад, як параметр шаблону для std :: array.


2
Чудове рішення! І на відміну від sizeof((int[]){__VA_ARGS__})/sizeof(int)запропонованого вище, він працює навіть тоді, коли аргументи не можна подати всім int.
Вім

Домовились. Чудове рішення! ++.
davernator

Не працює з шаблонами, тобто NUMARGS (сума <1,2>); дивіться godbolt.org/z/_AAxmL
jorgbrown

1
Я ... я думаю, що це насправді може бути на користь цього, @jorgbrown, принаймні в більшості випадків, коли це з'явиться. Оскільки він покладається на компілятор замість препроцесора для підрахунку, він наводить кількість аргументів, як їх бачив компілятор, які, ймовірно, будуть відповідати тому, що очікує більшість програмістів. Це буде викликати проблеми , якщо ви очікуєте , що приймати препроцесору жадібність до уваги, хоча.
Час Джастіна -

Чудова відповідь. Ви можете помістити його в макрос#define NUM_ARGS(...) std::tuple_size<decltype(std::make_tuple(__VA_ARGS__))>::value
Річард Уайтхед

23

Для зручності ось реалізація, яка працює від 0 до 70 аргументів і працює у Visual Studio, GCC та Clang . Я вірю, що він буде працювати у Visual Studio 2010 та пізніших версіях, але протестували його лише у VS2013.

#ifdef _MSC_VER // Microsoft compilers

#   define GET_ARG_COUNT(...)  INTERNAL_EXPAND_ARGS_PRIVATE(INTERNAL_ARGS_AUGMENTER(__VA_ARGS__))

#   define INTERNAL_ARGS_AUGMENTER(...) unused, __VA_ARGS__
#   define INTERNAL_EXPAND(x) x
#   define INTERNAL_EXPAND_ARGS_PRIVATE(...) INTERNAL_EXPAND(INTERNAL_GET_ARG_COUNT_PRIVATE(__VA_ARGS__, 69, 68, 67, 66, 65, 64, 63, 62, 61, 60, 59, 58, 57, 56, 55, 54, 53, 52, 51, 50, 49, 48, 47, 46, 45, 44, 43, 42, 41, 40, 39, 38, 37, 36, 35, 34, 33, 32, 31, 30, 29, 28, 27, 26, 25, 24, 23, 22, 21, 20, 19, 18, 17, 16, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0))
#   define INTERNAL_GET_ARG_COUNT_PRIVATE(_1_, _2_, _3_, _4_, _5_, _6_, _7_, _8_, _9_, _10_, _11_, _12_, _13_, _14_, _15_, _16_, _17_, _18_, _19_, _20_, _21_, _22_, _23_, _24_, _25_, _26_, _27_, _28_, _29_, _30_, _31_, _32_, _33_, _34_, _35_, _36, _37, _38, _39, _40, _41, _42, _43, _44, _45, _46, _47, _48, _49, _50, _51, _52, _53, _54, _55, _56, _57, _58, _59, _60, _61, _62, _63, _64, _65, _66, _67, _68, _69, _70, count, ...) count

#else // Non-Microsoft compilers

#   define GET_ARG_COUNT(...) INTERNAL_GET_ARG_COUNT_PRIVATE(0, ## __VA_ARGS__, 70, 69, 68, 67, 66, 65, 64, 63, 62, 61, 60, 59, 58, 57, 56, 55, 54, 53, 52, 51, 50, 49, 48, 47, 46, 45, 44, 43, 42, 41, 40, 39, 38, 37, 36, 35, 34, 33, 32, 31, 30, 29, 28, 27, 26, 25, 24, 23, 22, 21, 20, 19, 18, 17, 16, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0)
#   define INTERNAL_GET_ARG_COUNT_PRIVATE(_0, _1_, _2_, _3_, _4_, _5_, _6_, _7_, _8_, _9_, _10_, _11_, _12_, _13_, _14_, _15_, _16_, _17_, _18_, _19_, _20_, _21_, _22_, _23_, _24_, _25_, _26_, _27_, _28_, _29_, _30_, _31_, _32_, _33_, _34_, _35_, _36, _37, _38, _39, _40, _41, _42, _43, _44, _45, _46, _47, _48, _49, _50, _51, _52, _53, _54, _55, _56, _57, _58, _59, _60, _61, _62, _63, _64, _65, _66, _67, _68, _69, _70, count, ...) count

#endif

static_assert(GET_ARG_COUNT() == 0, "GET_ARG_COUNT() failed for 0 arguments");
static_assert(GET_ARG_COUNT(1) == 1, "GET_ARG_COUNT() failed for 1 argument");
static_assert(GET_ARG_COUNT(1,2) == 2, "GET_ARG_COUNT() failed for 2 arguments");
static_assert(GET_ARG_COUNT(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70) == 70, "GET_ARG_COUNT() failed for 70 arguments");

ІМХО варіант Microsoft не вдається за нульовими аргументами.
Vroomfondel

@Vroomfondel Варіант Microsoft працює для нульових аргументів. Найперший static_assert у прикладі вище - це специфічний тест для випадку нульового аргументу, і я щойно скомпілював і запустив його на Visual Studio 2017 v15.8.9.
Кріс Клайн

Цікаво - використання варіанту Microsoft на компіляторі, що не належить Microsoft, не працює - чи знаєте ви, що M-препроцесор робить інакше, що змушує код працювати протилежно? До речі, я спробував C, а не C ++;
Vroomfondel

Я вважаю, що це тому, що MSVC трохи приємніше щодо "нульової довжини __VA_ARGS__" (що в C ++ технічно є (майже універсальним, фактично стандартним) розширенням компілятора до C ++ 20). Більшість (? Всі) компілятори дозволяють нульову довжину, але дросель на задній коми , якщо список є порожнім (і перевантаженнями в ##якості проти __VA_OPT__, щоб видалити кому в даному випадку); Версія MSVC про продовження просто не вдавитися коми (але буде задихатися на перевантажений ##). Порівняйте MSVC unused, __VA_ARGS__з не MSVC 0, ## __VA_ARGS__; і правильніше, і те, що вони різні.
Час Джастіна -

Я не впевнений, чи це те саме в C, але @Vroomfondel, оскільки я втратив закладку до останньої чернетки.
Час Джастіна -

11

Є кілька C ++ 11 рішень для пошуку кількості аргументів під час компіляції, але я здивований, коли ніхто не запропонував нічого такого простого, як:

#define VA_COUNT(...) detail::va_count(__VA_ARGS__)

namespace detail
{
    template<typename ...Args>
    constexpr std::size_t va_count(Args&&...) { return sizeof...(Args); }
}

Це також не вимагає включення <tuple>заголовка.


1
"але чому б просто не використовувати різноманітний шаблон та sizeof ... замість цього (як у моїй власній відповіді)" c ++ став монстром. Він має занадто багато функцій, і багато з них, як і різні шаблони, рідко використовуються. Ви читаєте про це, пишете кілька прикладів, а потім забуваєте. Тому важко придумати правильну ідею в потрібний час. Оскільки ваше рішення здається кращим варіантом, ніж моє, я дозволю природній селекції працювати, і я видалю своє рішення.
zdf

1
@ZDF зрозуміло, але мені трапляється постійно використовувати різні шаблони. Мої програми стали набагато надійнішими після C ++ 11, і це одна з головних причин. Не потрібно видаляти свою відповідь, я думаю.
monkey0506

1
Це не буде працювати з smth, як VA_COUNT(&,^,%). Крім того, якщо ви рахуєте через функцію, я не бачу сенсу в створенні макросу.
Qwertiy

Це рішення залишається питанням: параметри VA_COUNT - це всі ідентифікатори, які ще не визначені як змінна або щось подібне, і це спричиняє помилку "Змінна *** не визначена". Чи можна це виправити?
ipid

7

це працює з 0 аргументами з gcc / llvm. [посилання німі]

/*
 * we need a comma at the start for ##_VA_ARGS__ to consume then
 * the arguments are pushed out in such a way that 'cnt' ends up with
 * the right count.  
 */
#define COUNT_ARGS(...) COUNT_ARGS_(,##__VA_ARGS__,6,5,4,3,2,1,0)
#define COUNT_ARGS_(z,a,b,c,d,e,f,cnt,...) cnt

#define C_ASSERT(test) \
    switch(0) {\
      case 0:\
      case test:;\
    }

int main() {
   C_ASSERT(0 ==  COUNT_ARGS());
   C_ASSERT(1 ==  COUNT_ARGS(a));
   C_ASSERT(2 ==  COUNT_ARGS(a,b));
   C_ASSERT(3 ==  COUNT_ARGS(a,b,c));
   C_ASSERT(4 ==  COUNT_ARGS(a,b,c,d));
   C_ASSERT(5 ==  COUNT_ARGS(a,b,c,d,e));
   C_ASSERT(6 ==  COUNT_ARGS(a,b,c,d,e,f));
   return 0;
}

Visual Studio, здається, ігнорує оператора ##, який використовується для споживання порожнього аргументу. Ви, напевно, можете обійти це чимось подібним

#define CNT_ COUNT_ARGS
#define PASTE(x,y) PASTE_(x,y)
#define PASTE_(x,y) x ## y
#define CNT(...) PASTE(ARGVS,PASTE(CNT_(__VA_ARGS__),CNT_(1,##__VA_ARGS__)))
//you know its 0 if its 11 or 01
#define ARGVS11 0
#define ARGVS01 0
#define ARGVS12 1
#define ARGVS23 2
#define ARGVS34 3

Я перевірив це для Visual Studio 2008, і він не працював на 0 аргументів COUNT_ARGS () = 1.
user720594

Посилання здається розірваною.
Jan Smrčina

фіксований зв’язок. VS повинен робити щось інше, як зазвичай :). Я не думаю, що незабаром вони будуть повністю підтримувати C99.
користувач1187902

2
Е, якщо ##__VA_ARGS__їсти кому раніше, якщо __VA_ARGS__вона порожня, це розширення GCC. Це не стандартна поведінка.
Фонд позову Моніки

6

З розширенням msvc:

#define Y_TUPLE_SIZE(...) Y_TUPLE_SIZE_II((Y_TUPLE_SIZE_PREFIX_ ## __VA_ARGS__ ## _Y_TUPLE_SIZE_POSTFIX,32,31,30,29,28,27,26,25,24,23,22,21,20,19,18,17,16,15,14,13,12,11,10,9,8,7,6,5,4,3,2,1,0))
#define Y_TUPLE_SIZE_II(__args) Y_TUPLE_SIZE_I __args

#define Y_TUPLE_SIZE_PREFIX__Y_TUPLE_SIZE_POSTFIX ,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,0

#define Y_TUPLE_SIZE_I(__p0,__p1,__p2,__p3,__p4,__p5,__p6,__p7,__p8,__p9,__p10,__p11,__p12,__p13,__p14,__p15,__p16,__p17,__p18,__p19,__p20,__p21,__p22,__p23,__p24,__p25,__p26,__p27,__p28,__p29,__p30,__p31,__n,...) __n

Працює за 0 - 32 аргументи. Цю межу можна легко продовжити.

EDIT: спрощена версія (працює у VS2015 14.0.25431.01, оновлення 3 та gcc 7.4.0) до 100 аргументів для копіювання та вставки:

#define COUNTOF(...) _COUNTOF_CAT( _COUNTOF_A, ( 0, ##__VA_ARGS__, 100,\
    99, 98, 97, 96, 95, 94, 93, 92, 91, 90,\
    89, 88, 87, 86, 85, 84, 83, 82, 81, 80,\
    79, 78, 77, 76, 75, 74, 73, 72, 71, 70,\
    69, 68, 67, 66, 65, 64, 63, 62, 61, 60,\
    59, 58, 57, 56, 55, 54, 53, 52, 51, 50,\
    49, 48, 47, 46, 45, 44, 43, 42, 41, 40,\
    39, 38, 37, 36, 35, 34, 33, 32, 31, 30,\
    29, 28, 27, 26, 25, 24, 23, 22, 21, 20,\
    19, 18, 17, 16, 15, 14, 13, 12, 11, 10,\
    9, 8, 7, 6, 5, 4, 3, 2, 1, 0 ) )
#define _COUNTOF_CAT( a, b ) a b
#define _COUNTOF_A( a0, a1, a2, a3, a4, a5, a6, a7, a8, a9,\
    a10, a11, a12, a13, a14, a15, a16, a17, a18, a19,\
    a20, a21, a22, a23, a24, a25, a26, a27, a28, a29,\
    a30, a31, a32, a33, a34, a35, a36, a37, a38, a39,\
    a40, a41, a42, a43, a44, a45, a46, a47, a48, a49,\
    a50, a51, a52, a53, a54, a55, a56, a57, a58, a59,\
    a60, a61, a62, a63, a64, a65, a66, a67, a68, a69,\
    a70, a71, a72, a73, a74, a75, a76, a77, a78, a79,\
    a80, a81, a82, a83, a84, a85, a86, a87, a88, a89,\
    a90, a91, a92, a93, a94, a95, a96, a97, a98, a99,\
    a100, n, ... ) n

4
це тільки я чи це щось порушує правила запаху коду ..?
osirisgothra

Він працює для мене з VC ++, щонайменше, до принаймні VS2012, а також GCC та clang, як і в моєму базовому тестуванні.
ThreeBit

@osirisgothra, чому саме пахне?
ceztko

Хоча цей макрос має широку підтримку компіляторів, він не працює з макроаргументами такого рядка, як Y_TUPLE_SIZE("Hello"), що робить його досить нездійсненним. Я згоден з @osirisgothra.
ceztko

1
Цей макрос може працювати для вас, але має серйозні дефекти. Я провів багато досліджень і виявив більш чисті підходи, які працюють у GCC та VS. Ви можете їх знайти у моїй відповіді на подібне запитання.
ceztko

3

Я припускаю, що кожен аргумент до VA_ARGS буде відокремлено комою. Якщо так, я думаю, що це має працювати як досить чистий спосіб зробити це.

#include <cstring>

constexpr int CountOccurances(const char* str, char c) {
    return str[0] == char(0) ? 0 : (str[0] == c) + CountOccurances(str+1, c);
}

#define NUMARGS(...) (CountOccurances(#__VA_ARGS__, ',') + 1)

int main(){
    static_assert(NUMARGS(hello, world) == 2, ":(")  ;
    return 0;
}

Працював для мене на Godbolt для Clang 4 та GCC 5.1. Це буде обчислено під час компіляції, але не буде оцінено для препроцесора. Тож якщо ви намагаєтесь зробити щось на кшталт створення FOR_EACH , то це не вийде.


Ця відповідь занижена. Це буде працювати навіть за NUMARGS(hello, world = 2, ohmy42, !@#$%^&*()-+=)!!! Кожен рядок аргументу не може містити інших символів, як-от ','хоч
pterodragon

Потрібно підлаштуватиint count = NUMARGS( foo(1, 2) );
парен

Це не буде працювати, як очікувалося, при лямбдах, функціональних викликах або будь-якому іншому, що може містити додаткові коми в параметрах.
Нанде

2

тут простий спосіб підрахунку 0 або більше аргументів VA_ARGS , мій приклад передбачає максимум 5 змінних, але ви можете додати більше, якщо хочете.

#define VA_ARGS_NUM_PRIV(P1, P2, P3, P4, P5, P6, Pn, ...) Pn
#define VA_ARGS_NUM(...) VA_ARGS_NUM_PRIV(-1, ##__VA_ARGS__, 5, 4, 3, 2, 1, 0)


VA_ARGS_NUM()      ==> 0
VA_ARGS_NUM(19)    ==> 1
VA_ARGS_NUM(9, 10) ==> 2
         ...

На жаль, підхід використовується неправильно, коли VA_ARGS_NUMвикористовується з макросом: якщо у мене є #define TEST(тобто порожній TEST) і VA_ARGS_NUM(TEST)не повертається 0 (нуль) при використанні в #if:(
AntonK

@AntonK Чи можете ви опублікувати те, що ви точно зробили, будь ласка?
elhadi dp ıpɐɥ ן ǝ

0

Ви можете впорядкувати та рахувати жетони:

int countArgs(char *args)
{
  int result = 0;
  int i = 0;

  while(isspace(args[i])) ++i;
  if(args[i]) ++result;

  while(args[i]) {
    if(args[i]==',') ++result;
    else if(args[i]=='\'') i+=2;
    else if(args[i]=='\"') {
      while(args[i]) {
        if(args[i+1]=='\"' && args[i]!='\\') {
          ++i;
          break;
        }
        ++i;
      }
    }
    ++i;
  }

  return result;
}

#define MACRO(...) \
{ \
  int count = countArgs(#__VA_ARGS__); \
  printf("NUM ARGS: %d\n",count); \
}

2
Ви просто подивилися на редагування, яке очікує на цю відповідь. Здається, у вас можуть бути два облікові записи. Якщо ви дотримуєтесь такої, ви зможете редагувати власні публікації, не посилаючись на затвердження.
Дж. Річард Снейп

0

Попередній процесор Boost фактично має це як у Boost 1,49, як BOOST_PP_VARIADIC_SIZE(...). Він працює до 64-го розміру.

Під кришкою це в основному те саме, що відповідь Корнеля Кіселевича .


@CarloWood Дійсно. У препроцесора насправді немає поняття "нульові аргументи". Ми вважаємо "нульовими аргументами" "один порожній аргумент" у препроцесорі. Але це можна виправити за допомогою C ++ 20 __VA_OPT__або розширень компілятора для ##__VA_ARGS__видалення попередньої коми, наприклад: godbolt.org/z/X7OvnK
Джастін

0

Я знайшов відповіді тут ще неповні.

Найближча портативна реалізація, яку я знайшов звідси, це: препроцесор C ++ __VA_ARGS__ кількість аргументів

Але це не працює з нульовими аргументами в GCC без принаймні -std=gnu++11параметра командного рядка.

Тому я вирішив об'єднати це рішення з цим: https://gustedt.wordpress.com/2010/06/08/detect-empty-macro-arguments/

#define UTILITY_PP_CONCAT_(v1, v2) v1 ## v2
#define UTILITY_PP_CONCAT(v1, v2) UTILITY_PP_CONCAT_(v1, v2)

#define UTILITY_PP_CONCAT5_(_0, _1, _2, _3, _4) _0 ## _1 ## _2 ## _3 ## _4

#define UTILITY_PP_IDENTITY_(x) x
#define UTILITY_PP_IDENTITY(x) UTILITY_PP_IDENTITY_(x)

#define UTILITY_PP_VA_ARGS_(...) __VA_ARGS__
#define UTILITY_PP_VA_ARGS(...) UTILITY_PP_VA_ARGS_(__VA_ARGS__)

#define UTILITY_PP_IDENTITY_VA_ARGS_(x, ...) x, __VA_ARGS__
#define UTILITY_PP_IDENTITY_VA_ARGS(x, ...) UTILITY_PP_IDENTITY_VA_ARGS_(x, __VA_ARGS__)

#define UTILITY_PP_IIF_0(x, ...) __VA_ARGS__
#define UTILITY_PP_IIF_1(x, ...) x
#define UTILITY_PP_IIF(c) UTILITY_PP_CONCAT_(UTILITY_PP_IIF_, c)

#define UTILITY_PP_HAS_COMMA(...) UTILITY_PP_IDENTITY(UTILITY_PP_VA_ARGS_TAIL(__VA_ARGS__, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0))
#define UTILITY_PP_IS_EMPTY_TRIGGER_PARENTHESIS_(...) ,

#define UTILITY_PP_IS_EMPTY(...) UTILITY_PP_IS_EMPTY_( \
    /* test if there is just one argument, eventually an empty one */ \
    UTILITY_PP_HAS_COMMA(__VA_ARGS__),                                \
    /* test if _TRIGGER_PARENTHESIS_ together with the argument adds a comma */ \
    UTILITY_PP_HAS_COMMA(UTILITY_PP_IS_EMPTY_TRIGGER_PARENTHESIS_ __VA_ARGS__), \
    /* test if the argument together with a parenthesis adds a comma */ \
    UTILITY_PP_HAS_COMMA(__VA_ARGS__ ()),                             \
    /* test if placing it between _TRIGGER_PARENTHESIS_ and the parenthesis adds a comma */ \
    UTILITY_PP_HAS_COMMA(UTILITY_PP_IS_EMPTY_TRIGGER_PARENTHESIS_ __VA_ARGS__ ()))

#define UTILITY_PP_IS_EMPTY_(_0, _1, _2, _3) UTILITY_PP_HAS_COMMA(UTILITY_PP_CONCAT5_(UTILITY_PP_IS_EMPTY_IS_EMPTY_CASE_, _0, _1, _2, _3))
#define UTILITY_PP_IS_EMPTY_IS_EMPTY_CASE_0001 ,

#define UTILITY_PP_VA_ARGS_SIZE(...) UTILITY_PP_IIF(UTILITY_PP_IS_EMPTY(__VA_ARGS__))(0, UTILITY_PP_VA_ARGS_SIZE_(__VA_ARGS__, UTILITY_PP_VA_ARGS_SEQ64()))
#define UTILITY_PP_VA_ARGS_SIZE_(...) UTILITY_PP_IDENTITY(UTILITY_PP_VA_ARGS_TAIL(__VA_ARGS__))

#define UTILITY_PP_VA_ARGS_TAIL(_0,_1,_2,_3,_4,_5,_6,_7,_8,_9,_10,_11,_12,_13,_14, x, ...) x
#define UTILITY_PP_VA_ARGS_SEQ64() 15,14,13,12,11,10,9,8,7,6,5,4,3,2,1,0

#define EATER0(...)
#define EATER1(...) ,
#define EATER2(...) (/*empty*/)
#define EATER3(...) (/*empty*/),
#define EATER4(...) EATER1
#define EATER5(...) EATER2
#define MAC0() ()
#define MAC1(x) ()
#define MACV(...) ()
#define MAC2(x,y) whatever

static_assert(UTILITY_PP_VA_ARGS_SIZE() == 0, "1");
static_assert(UTILITY_PP_VA_ARGS_SIZE(/*comment*/) == 0, "2");
static_assert(UTILITY_PP_VA_ARGS_SIZE(a) == 1, "3");
static_assert(UTILITY_PP_VA_ARGS_SIZE(a, b) == 2, "4");
static_assert(UTILITY_PP_VA_ARGS_SIZE(a, b, c) == 3, "5");
static_assert(UTILITY_PP_VA_ARGS_SIZE(a, b, c, d) == 4, "6");
static_assert(UTILITY_PP_VA_ARGS_SIZE(a, b, c, d, e) == 5, "7");
static_assert(UTILITY_PP_VA_ARGS_SIZE((void)) == 1, "8");
static_assert(UTILITY_PP_VA_ARGS_SIZE((void), b, c, d) == 4, "9");
static_assert(UTILITY_PP_VA_ARGS_SIZE(UTILITY_PP_IS_EMPTY_TRIGGER_PARENTHESIS_) == 1, "10");
static_assert(UTILITY_PP_VA_ARGS_SIZE(EATER0) == 1, "11");
static_assert(UTILITY_PP_VA_ARGS_SIZE(EATER1) == 1, "12");
static_assert(UTILITY_PP_VA_ARGS_SIZE(EATER2) == 1, "13");
static_assert(UTILITY_PP_VA_ARGS_SIZE(EATER3) == 1, "14");
static_assert(UTILITY_PP_VA_ARGS_SIZE(EATER4) == 1, "15");
static_assert(UTILITY_PP_VA_ARGS_SIZE(MAC0) == 1, "16");
// a warning in msvc
static_assert(UTILITY_PP_VA_ARGS_SIZE(MAC1) == 1, "17");
static_assert(UTILITY_PP_VA_ARGS_SIZE(MACV) == 1, "18");
// This one will fail because MAC2 is not called correctly
//static_assert(UTILITY_PP_VA_ARGS_SIZE(MAC2) == 1, "19");

https://godbolt.org/z/3idaKd

  • c++11, msvc 2015, gcc 4.7.1,clang 3.0
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.