Preprocesor C ++ __VA_ARGS__ liczba argumentów


99

Proste pytanie, na które nie mogłem znaleźć odpowiedzi w sieci. Jak znaleźć liczbę argumentów w makrach argumentów o zmiennej liczbie argumentów? Jestem w porządku z preprocesorem doładowania, jeśli ma rozwiązanie.

Jeśli to robi różnicę, próbuję przekonwertować zmienną liczbę argumentów makr, aby zwiększyć sekwencję, listę lub tablicę preprocesora w celu dalszego przetwarzania.


Dla jasności - pytasz o makra wariadyczne, a nie makra używane do tworzenia wariadycznych funkcji C?

2
czy argumenty są tego samego typu? jeśli tak, i jeśli typ jest znany, istnieje standardowe rozwiązanie w języku C za pośrednictwem literałów złożonych; jeśli jest nieznany, możesz go użyć __typeof__, aby działał przynajmniej na niektórych kompilatorach
Christoph

1
Ponieważ dyskusja dotyczy sekwencji preprocesora Boost itp., Musi to być C ++ (dlatego ponownie otagowałem Q - ale nie udało mi się zmienić tytułu pytania) ... Ups; Naprawię to.
Jonathan Leffler

@JonathanLeffler Prawda, Boost to biblioteka C ++. Jednak Boost.Preprocessor może być używany z C. AFAIK, nic, czego używa, nie jest specyficzne dla C ++.
Justin

Odpowiedzi:


90

W rzeczywistości jest to zależne od kompilatora i nie jest obsługiwane przez żaden standard.

Tutaj jednak masz implementację makra , która liczy:

#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

.... ale teraz jest standardem w C ++ 0x i powinno być już dawno temu, ponieważ pozwala na świetny sposób na ochronę funkcji waradycznych przed uszkodzonymi wywołaniami (tj. możesz przekazywać wartości po elementach waradycznych. W rzeczywistości jest to sposób uzyskiwania liczby, której używałem, ale domyślam się, że sizeof też może działać ..
osirisgothra

Odpowiedź prowadzi do innej witryny. Również link nie wydaje się wskazywać poprawnej odpowiedzi. I nawet jeśli udało mi się znaleźć zamierzoną odpowiedź, wydaje się ona kiepska, ponieważ zawiera zakodowane na stałe „-1”, które zostanie skompilowane. Są lepsze metody.
ceztko

2
Dzięki! to działało w Visual Studio 2013 dla mnie: #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()nie zwraca 0. Rozwiązania GET_ARG_COUNT()& Y_TUPLE_SIZE()działają.
PSkocik

1
PP_NARG()nie zwraca 0” ... niekoniecznie stanowi problem. Można powiedzieć, że PP_NARG() powinno zwrócić 1 z tego samego powodu, PP_NARG(,)powinno zwrócić 2. Wykrywanie 0 może być rzeczywiście przydatne w niektórych przypadkach, ale rozwiązania wydają się albo mniej ogólne (wymaganie, aby pierwszy token był możliwy do wklejenia; co może być lub nie być w porządku w zależności od tego, do czego go używasz) lub specyficzne dla implementacji (na przykład wymaganie sztuczki gnu z usuwaniem przecinków i wklejaniem).
H Walters

100

Zwykle używam tego makra, aby znaleźć kilka parametrów:

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

Pełny przykład:

#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;
}

Jest to całkowicie poprawny kod C99. Ma jednak jedną wadę - nie można wywołać makra SUM()bez parametrów, ale GCC ma na to rozwiązanie - patrz tutaj .

Więc w przypadku GCC musisz zdefiniować makra w ten sposób:

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

i będzie działać nawet z pustą listą parametrów


4
UM, to nie zadziała dla OP, potrzebuje rozmiaru dla BOOST_PP, który działa w czasie kompilacji.
Kornel Kisielewicz

5
Sprytny! Czy to też działa, kiedy sizeof(int) != sizeof(void *)?
Adam Liss

3
@Kornel Jak każde makro, jest oceniane w czasie kompilacji. Nie mam pojęcia o Boost, ale i tak Boost nie jest potrzebny.
qrdl

4
@Adam Ponieważ rzucam {__VA_ARGS__}na int[], to jest po prostu int[], niezależnie od faktycznej treści__VA_ARGS__
qrdl

3
Eleganckie rozwiązanie! Działa w VS2017. Nie ##jest potrzebny w VS2017, ponieważ pusty __VA_ARGS__automatycznie usunie poprzedni przecinek.
poby

37

Jeśli używasz C ++ 11 i potrzebujesz wartości jako stałej C ++ czasu kompilacji, bardzo eleganckim rozwiązaniem jest:

#include <tuple>

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

Uwaga: liczenie odbywa się w całości w czasie kompilacji, a wartość może być używana zawsze, gdy wymagana jest liczba całkowita w czasie kompilacji, na przykład jako parametr szablonu w std :: array.


2
Świetne rozwiązanie! I w przeciwieństwie do sizeof((int[]){__VA_ARGS__})/sizeof(int)powyższego, działa nawet wtedy, gdy nie można przesłać wszystkich argumentów int.
Wim,

Zgoda. Świetne rozwiązanie! ++.
davernator

Nie działa z szablonami, tj. NUMARGS (sum <1,2>); patrz godbolt.org/z/_AAxmL
jorgbrown

1
Myślę ... myślę, że to może być argument przemawiający za tym, @jorgbrown, przynajmniej w większości przypadków, kiedy się pojawiał. Ponieważ opiera się na kompilatorze zamiast na preprocesorze do liczenia, podaje liczbę argumentów widzianą przez kompilator, która prawdopodobnie będzie zgodna z tym, czego oczekuje większość programistów. To będzie powodować problemy, jeśli można oczekiwać, że do podjęcia preprocesora greediness pod uwagę, choć.
Justin Time - Przywróć Monikę

Doskonała odpowiedź. Możesz umieścić to w makro#define NUM_ARGS(...) std::tuple_size<decltype(std::make_tuple(__VA_ARGS__))>::value
Richard Whitehead

23

Dla wygody, oto implementacja, która działa dla 0 do 70 argumentów i działa w Visual Studio, GCC i Clang . Wierzę, że będzie działać w Visual Studio 2010 i nowszych, ale przetestowałem go tylko w 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");

IMHO wariant firmy Microsoft kończy się niepowodzeniem dla zerowych argumentów.
Vroomfondel

@Vroomfondel wariant Microsoft działa dla zerowych argumentów. Pierwszy static_assert w powyższym przykładzie to konkretny test dla przypadku bezargumentowego, który właśnie skompilowałem i uruchomiłem w programie Visual Studio 2017 w wersji 15.8.9.
Chris Kline,

Ciekawe - użycie wariantu Microsoftu na kompilatorze innym niż Microsoft nie działa - czy wiesz, co preprocesor M $ robi inaczej, że kod działa w odwrotny sposób? BTW, próbowałem C, nie C ++;
Vroomfondel

Wydaje mi się, że to dlatego, że MSVC jest nieco przyjemniejsze w kwestii „zerowej długości __VA_ARGS__” (która w C ++ jest technicznie (prawie uniwersalnym, de facto standardowym) rozszerzeniem kompilatora aż do C ++ 20). Większość (wszystkich?) Kompilatorów dopuszcza zerową długość, ale dławi się na końcowym przecinku, jeśli lista jest pusta (i przeciąża ##jako protokół __VA_OPT__, aby usunąć przecinek w tym przypadku); Wersja msvc za przedłużenia prostu nie zadławić przecinkiem (ale będzie dusić na przeciążony ##). Porównaj MSVC unused, __VA_ARGS__do nieprzestrzegania MSVC 0, ## __VA_ARGS__; żadne nie jest bardziej poprawne, problem polega na tym, że są różne.
Justin Time - Przywróć Monikę

Nie jestem pewien, czy to jest to samo w C, @Vroomfondel, ponieważ zgubiłem zakładkę do najnowszej wersji roboczej.
Justin Time - Przywróć Monikę

11

Istnieje kilka rozwiązań C ++ 11 do znajdowania liczby argumentów w czasie kompilacji, ale jestem zaskoczony, widząc, że nikt nie zasugerował czegoś tak prostego, jak:

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

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

To również nie wymaga włączenia <tuple>nagłówka.


1
„ale dlaczego nie po prostu użyć wariadycznego szablonu i sizeof… zamiast tego (jak w mojej własnej odpowiedzi)” c ++ stało się potworem. Ma zbyt wiele funkcji, a wiele z nich, takich jak szablony wariadyczne, jest rzadko używanych. Czytasz o tym, piszesz przykłady, a potem zapominasz. Dlatego trudno jest wpaść na właściwy pomysł we właściwym czasie. Ponieważ Twoje rozwiązanie wydaje się być lepszą opcją niż moje, pozwolę działać doborowi naturalnemu i usunę moje rozwiązanie.
zdf

1
@ZDF zrozumiałe, ale zdarza mi się stale używać różnych szablonów. Moje programy stały się znacznie bardziej niezawodne od czasu C ++ 11 i jest to jeden z głównych powodów. Myślę, że nie trzeba jednak usuwać odpowiedzi.
monkey0506

1
To nie zadziała z czymś takim VA_COUNT(&,^,%). Ponadto, jeśli liczysz przez funkcję, nie widzę sensu w tworzeniu makra.
Qwertiy

To rozwiązanie pozostaje kwestią sporną: parametry VA_COUNT to wszystkie identyfikatory, które nie zostały jeszcze zdefiniowane jako zmienna lub coś, co powoduje błąd „*** zmienna nie została zdefiniowana”. Czy jest jakiś sposób, aby to naprawić?
ipid

7

działa to z 0 argumentami z gcc / llvm. [linki są głupie]

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

Wydaje się, że program Visual Studio ignoruje operator ## używany do zużywania pustego argumentu. Prawdopodobnie możesz to obejść za pomocą czegoś takiego

#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

Przetestowałem to dla Visual Studio 2008 i nie zadziałało dla 0 argumentów COUNT_ARGS () = 1.
user720594

Link wygląda na uszkodzony.
Jan Smrčina

naprawiony link. VS musi robić coś innego jak zwykle :). Nie sądzę, że w najbliższym czasie będą w pełni wspierać C99.
user1187902

2
Eee, ##__VA_ARGS__zjadanie przecinka przed if __VA_ARGS__jest puste to rozszerzenie GCC. To nie jest standardowe zachowanie.
Załóż pozew Moniki

6

Z rozszerzeniem 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

Działa dla 0 - 32 argumentów. Limit ten można łatwo rozszerzyć.

EDYCJA: Uproszczona wersja (działa w VS2015 14.0.25431.01 Update 3 i gcc 7.4.0) do 100 argumentów do kopiowania i wklejania:

#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
czy to tylko ja, czy to trochę łamie zasady dotyczące zapachów kodu ..?
osirisgothra

Działa dla mnie z VC ++ do co najmniej VS2012 oraz GCC i clang w moich podstawowych testach.
ThreeBit

@osirisgothra, dlaczego dokładnie pachnie?
ceztko

Chociaż to makro obsługuje szerokie kompilatory, nie działa z argumentami makr, takimi jak ciąg znaków Y_TUPLE_SIZE("Hello"), co sprawia, że ​​jest to całkiem niewykonalne. Zgadzam się z @osirisgothra.
ceztko

1
To makro może działać dla Ciebie, ale ma poważne wady. Zrobiłem wiele badań i znalazłem czystsze podejścia, które działają w GCC i VS. Znajdziesz je w mojej odpowiedzi na podobne pytanie.
ceztko

3

Zakładam, że każdy argument do VA_ARGS będzie oddzielony przecinkami. Jeśli tak, myślę, że powinno to działać jako całkiem czysty sposób na zrobienie tego.

#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;
}

Pracował dla mnie na godbolt dla Clang 4 i GCC 5.1. Spowoduje to obliczenie w czasie kompilacji, ale nie zostanie oszacowane dla preprocesora. Więc jeśli próbujesz zrobić coś takiego jak tworzenie FOR_EACH , to nie zadziała.


Ta odpowiedź jest niedoceniana. To zadziała nawet dla NUMARGS(hello, world = 2, ohmy42, !@#$%^&*()-+=)!!! Każdy łańcuch argumentu nie może mieć innych symboli, jak ','chociażby
pterodragon

Musiint count = NUMARGS( foo(1, 2) );
zostać

Nie będzie to działać zgodnie z oczekiwaniami w przypadku wyrażeń lambd, wywołań funkcji lub czegokolwiek innego, co może zawierać dodatkowe przecinki w parametrach.
Nandee

2

tutaj prosty sposób na policzenie 0 lub więcej argumentów VA_ARGS , mój przykład zakłada maksymalnie 5 zmiennych, ale możesz dodać więcej, jeśli chcesz.

#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
         ...

Niestety podejście działa niepoprawnie, gdy VA_ARGS_NUMjest używane z makrem: jeśli mam #define TEST(tj. Puste TEST) i VA_ARGS_NUM(TEST)nie zwraca 0 (zero), gdy jest używane w #if:(
AntonK

@AntonK czy możesz opublikować dokładnie to, co zrobiłeś?
elhadi dp ıpɐɥ ן ǝ

0

Możesz ciągnąć i liczyć tokeny:

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
Właśnie rzuciłem okiem na oczekującą edycję tej odpowiedzi - wydaje się, że możesz mieć dwa konta. Jeśli trzymasz się jednego, będziesz mógł edytować własne posty bez konieczności ich zatwierdzania.
J Richard Snape

0

Boost Preprocessor faktycznie ma to od Boost 1.49, as BOOST_PP_VARIADIC_SIZE(...). Działa do rozmiaru 64.

Pod maską to w zasadzie to samo, co odpowiedź Kornela Kisielewicza .


@CarloWood Rzeczywiście. Preprocesor tak naprawdę nie ma pojęcia „zero argumentów”. To, co nazywamy „zerowymi argumentami”, to „jeden pusty argument” w preprocesorze. Ale można to naprawić za pomocą C ++ 20 __VA_OPT__lub rozszerzeń kompilatora do ##__VA_ARGS__usunięcia poprzedniego przecinka, np .: godbolt.org/z/X7OvnK
Justin

0

Znalazłem tutaj odpowiedzi wciąż niekompletne.

Najbardziej najbliższa przenośna implementacja, jaką tutaj znalazłem, to: preprocesor C ++ __VA_ARGS__ liczba argumentów

Ale to nie działa z zerowymi argumentami w GCC bez przynajmniej -std=gnu++11 parametru wiersza poleceń.

Postanowiłem więc połączyć to rozwiązanie z tym: 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
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.