C ++ - Präprozessor __VA_ARGS__ Anzahl der Argumente


99

Einfache Frage, auf die ich im Internet keine Antwort finden konnte. Wie kann man in Makros mit variablen Argumenten die Anzahl der Argumente ermitteln? Ich bin mit Boost-Präprozessor einverstanden, wenn er die Lösung hat.

Wenn es einen Unterschied macht, versuche ich, eine variable Anzahl von Makroargumenten zu konvertieren, um die Präprozessorsequenz, -liste oder das Array für die weitere Wiederaufbereitung zu verbessern.


Um ganz klar zu sein: Sie fragen nach variadischen Makros und nicht nach den Makros, die zum Erstellen variadischer C-Funktionen verwendet werden?

2
sind die Argumente vom gleichen Typ? Wenn ja, und wenn der Typ bekannt ist, gibt es eine Standard-C-Lösung über zusammengesetzte Literale. Wenn es unbekannt ist, können Sie es verwenden __typeof__, um es zumindest auf einigen Compilern zum Laufen zu bringen
Christoph

1
Da es sich bei der Diskussion um die Boost-Präprozessorsequenz usw. handelt, muss es sich um C ++ handeln (weshalb ich das Q neu markiert habe - aber den Fragentitel nicht geändert habe) ... Ups; Ich werde das beheben.
Jonathan Leffler

@ JonathanLeffler Richtig, Boost ist eine C ++ - Bibliothek. Boost.Preprocessor kann jedoch mit C. AFAIK verwendet werden. Nichts, was es verwendet, ist C ++ - spezifisch.
Justin

Antworten:


90

Dies ist tatsächlich vom Compiler abhängig und wird von keinem Standard unterstützt.

Hier haben Sie jedoch eine Makroimplementierung , die zählt:

#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

.... aber jetzt ist Standard in C ++ 0x und sollte schon länger her sein, da es eine großartige Möglichkeit bietet, varadische Funktionen vor beschädigten Aufrufen zu schützen (dh Sie können Werte nach den varadischen Elementen übergeben. Dies ist eigentlich eine Möglichkeit von der Zählung zu bekommen, die ich verwendet habe, aber ich denke, sizeof könnte auch funktionieren ..
osirisgothra

Die Antwort verweist auf eine andere Site. Außerdem scheint der Link nicht auf die richtige Antwort zu verweisen. Und selbst wenn ich es geschafft habe, die beabsichtigte Antwort zu finden, scheint sie schlecht zu sein, da sie ein fest codiertes "-1" einbettet, das kompiliert wird. Es gibt bessere Methoden.
Ceztko

2
Vielen Dank! Dies funktionierte in Visual Studio 2013 für mich: #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()gibt 0 nicht zurück. Die GET_ARG_COUNT()& Y_TUPLE_SIZE()-Lösungen funktionieren.
PSkocik

1
"gibt PP_NARG()0 nicht zurück" ... ist nicht unbedingt ein Problem. Man kann sagen , dass PP_NARG() sollte 1 aus dem gleichen Grund zurückkehren PP_NARG(,)sollte 2. Rückkehr Detecting 0 in der Tat in einigen Fällen nützlich sein kann, aber die Lösungen scheinen entweder weniger allgemein zu sein (erfordert , dass erste Token pasteable zu sein, welche möglicherweise oder möglicherweise nicht in Ordnung sein je nachdem, wofür Sie es verwenden) oder implementierungsspezifisch (z. B. wenn Sie den Trick zum Entfernen von Kommas zum Entfernen und Einfügen von Gnu benötigen).
H Walters

100

Normalerweise benutze ich dieses Makro, um eine Reihe von Parametern zu finden:

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

Vollständiges Beispiel:

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

Es ist ein vollständig gültiger C99-Code. Es hat jedoch einen Nachteil: Sie können das Makro nicht SUM()ohne Parameter aufrufen, aber GCC hat eine Lösung dafür - siehe hier .

Im Fall von GCC müssen Sie also Makros wie folgt definieren:

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

und es funktioniert auch mit leerer Parameterliste


4
UM, es funktioniert nicht für das OP, er benötigt die Größe für BOOST_PP, die zur Kompilierungszeit ausgeführt wird.
Kornel Kisielewicz

5
Klug! Funktioniert es auch wann sizeof(int) != sizeof(void *)?
Adam Liss

3
@Kornel Wie jedes Makro wird es zur Kompilierungszeit ausgewertet. Ich habe keine Ahnung von Boost, aber Boost wird sowieso nicht benötigt.
Qrdl

4
@ Adam Weil ich {__VA_ARGS__}zu besetze int[], ist es nur int[], unabhängig vom tatsächlichen Inhalt von__VA_ARGS__
qrdl

3
Elegante Lösung! Funktioniert in VS2017. Das ##wird in VS2017 nicht benötigt, da ein Leerzeichen __VA_ARGS__automatisch alle vorhergehenden Kommas entfernt.
Poby

37

Wenn Sie C ++ 11 verwenden und den Wert als C ++ - Kompilierungszeitkonstante benötigen, ist dies eine sehr elegante Lösung:

#include <tuple>

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

Bitte beachten Sie: Die Zählung erfolgt vollständig zur Kompilierungszeit. Der Wert kann immer dann verwendet werden, wenn eine Ganzzahl zur Kompilierungszeit erforderlich ist, z. B. als Vorlagenparameter für std :: array.


2
Tolle Lösung! Und anders als sizeof((int[]){__VA_ARGS__})/sizeof(int)oben vorgeschlagen, funktioniert es auch dann, wenn nicht alle Argumente verwendet werden können int.
Wim

Einverstanden. Tolle Lösung! ++.
Davernator

Funktioniert nicht mit Vorlagen, dh NUMARGS (Summe <1,2>); siehe godbolt.org/z/_AAxmL
jorgbrown

1
Ich ... denke, das könnte tatsächlich ein Punkt dafür sein, @jorgbrown, zumindest in den meisten Fällen, in denen es auftauchen würde. Da beim Zählen der Compiler anstelle des Präprozessors verwendet wird, wird die Anzahl der vom Compiler gesehenen Argumente angegeben, die wahrscheinlich den Erwartungen der meisten Programmierer entsprechen. Es wird jedoch Probleme verursachen, wenn Sie erwarten, dass die Gier des Präprozessors berücksichtigt wird.
Justin Time - Stellen Sie Monica

Hervorragende Antwort. Sie können es in ein Makro einfügen#define NUM_ARGS(...) std::tuple_size<decltype(std::make_tuple(__VA_ARGS__))>::value
Richard Whitehead

23

Der Einfachheit halber ist hier eine Implementierung, die für 0 bis 70 Argumente funktioniert und in Visual Studio, GCC und Clang funktioniert . Ich glaube, dass es in Visual Studio 2010 und höher funktionieren wird, habe es aber nur in VS2013 getestet.

#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 schlägt die Microsoft-Variante für null Argumente fehl.
Vroomfondel

@Vroomfondel Die Microsoft-Variante funktioniert für Nullargumente. Der allererste static_assert im obigen Beispiel ist ein spezifischer Test für den Fall mit null Argumenten. Ich habe ihn gerade kompiliert und unter Visual Studio 2017 v15.8.9 ausgeführt.
Chris Kline

Interessant - die Verwendung der Microsoft-Variante auf einem Nicht-Microsoft-Compiler funktioniert nicht - wissen Sie, was der M $ -Vorprozessor anders macht, sodass Code umgekehrt funktioniert? Übrigens habe ich C versucht, nicht C ++;
Vroomfondel

Ich glaube, das liegt daran, dass MSVC in Bezug auf "Zero-Length __VA_ARGS__" (das in C ++ technisch gesehen eine (nahezu universelle, de facto Standard) Compiler-Erweiterung bis C ++ 20 ist) etwas besser ist . Die meisten (? Alle) Compilern erlauben Länge Null, aber Drossel auf dem Komma , wenn die Liste ist leer (und Überlastung ##als proto __VA_OPT__, das Komma in diesem Fall zu entfernen); MSVC Version der Erweiterung nicht ersticken gerade nicht auf dem Komma (aber wird ersticken die überlastet ##). Vergleichen Sie MSVC unused, __VA_ARGS__mit Nicht-MSVC 0, ## __VA_ARGS__. beides ist nicht korrekter, das Problem ist, dass sie unterschiedlich sind.
Justin Time - Monica wieder einsetzen

Ich bin mir nicht sicher, ob dies in C, @Vroomfondel, dasselbe ist, da ich mein Lesezeichen durch den neuesten Entwurf verloren habe.
Justin Time - Stellen Sie Monica

10

Es gibt einige C ++ 11-Lösungen, um die Anzahl der Argumente zur Kompilierungszeit zu ermitteln, aber ich bin überrascht zu sehen, dass niemand etwas so Einfaches vorgeschlagen hat wie:

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

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

Dies erfordert auch nicht die Aufnahme des <tuple>Headers.


1
"aber warum nicht einfach eine variable Vorlage und Größe von ... verwenden (wie in meiner eigenen Antwort)" c ++ ist ein Monster geworden. Es hat zu viele Funktionen und viele von ihnen, wie verschiedene Vorlagen, werden selten verwendet. Sie lesen darüber, Sie schreiben einige Beispiele und dann vergessen Sie es. Daher ist es schwierig, zur richtigen Zeit die richtige Idee zu finden. Da Ihre Lösung eine bessere Option zu sein scheint als meine, lasse ich die natürliche Auswahl funktionieren und lösche meine Lösung.
zdf

1
@ZDF verständlich, aber ich benutze ständig verschiedene Vorlagen. Meine Programme sind seit C ++ 11 viel robuster geworden, und dies ist einer der Hauptgründe dafür. Ich denke, Sie müssen Ihre Antwort nicht löschen.
monkey0506

1
Es wird nicht mit etw wie funktionieren VA_COUNT(&,^,%). Wenn Sie über eine Funktion zählen, sehe ich keinen Sinn darin, ein Makro zu erstellen.
Qwertiy

Diese Lösung bleibt eine Frage: Die Parameter von VA_COUNT sind alle Bezeichner, die noch nicht als Variable oder etwas definiert sind, und sie verursachen den Fehler '*** Variable ist nicht definiert'. Gibt es eine Möglichkeit, dies zu beheben?
IPID

7

Dies funktioniert mit 0 Argumenten mit gcc / llvm. [Links sind dumm]

/*
 * 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 scheint den Operator ## zu ignorieren, mit dem das leere Argument verwendet wird. Sie können das wahrscheinlich mit so etwas umgehen

#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

Ich habe dies für Visual Studio 2008 getestet und es hat nicht für 0 Argumente funktioniert. COUNT_ARGS () = 1.
user720594

Die Verbindung scheint unterbrochen zu sein.
Jan Smrčina

fester Link. VS muss wie immer etwas anderes machen :). Ich glaube nicht, dass sie C99 bald vollständig unterstützen werden.
user1187902

2
Ähm, ##__VA_ARGS__das Komma vorher zu essen, wenn __VA_ARGS__es leer ist, ist eine GCC-Erweiterung. Es ist nicht das Standardverhalten.
Fund Monica Klage

6

Mit MSVC-Erweiterung:

#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

Funktioniert für 0 - 32 Argumente. Diese Grenze kann leicht erweitert werden.

BEARBEITEN: Vereinfachte Version (funktioniert in VS2015 14.0.25431.01 Update 3 & gcc 7.4.0) bis zu 100 Argumente zum Kopieren und Einfügen:

#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
Ist es nur ich oder verstößt dies irgendwie gegen die Regeln für Codegeruch?
Osirisgothra

Es funktioniert für mich mit VC ++ bis mindestens VS2012 und GCC und Clang auch in meinen grundlegenden Tests.
ThreeBit

@osirisgothra, genau warum riecht es?
Ceztko

Obwohl dieses Makro eine breite Unterstützung für Compiler bietet, funktioniert es nicht mit Makroargumenten wie einem String Y_TUPLE_SIZE("Hello"), was es ziemlich unmöglich macht. Ich stimme @osirisgothra zu.
Ceztko

1
Dieses Makro kann für Sie arbeiten, weist jedoch schwerwiegende Mängel auf. Ich habe viel recherchiert und sauberere Ansätze gefunden, die in GCC und VS funktionieren. Sie finden sie in meiner Antwort auf eine ähnliche Frage.
Ceztko

3

Ich gehe davon aus, dass jedes Argument für VA_ARGS durch Kommas getrennt wird. Wenn ja, denke ich, sollte dies ein ziemlich sauberer Weg sein, dies zu tun.

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

Arbeitete für mich an Godbolt für Clang 4 und GCC 5.1. Dies wird zur Kompilierungszeit berechnet, jedoch nicht für den Präprozessor ausgewertet. Wenn Sie also versuchen, FOR_EACH zu erstellen , funktioniert dies nicht.


Diese Antwort wird unterschätzt. Es wird sogar funktionieren für NUMARGS(hello, world = 2, ohmy42, !@#$%^&*()-+=)!!! Jeder arg String kann nicht haben einige andere Symbole wie ','wenn
pterodragon

Muss für Parens optimiert werden, da int count = NUMARGS( foo(1, 2) );2 statt 1 produziert werden. Godbolt.org/z/kpBuOm
jorgbrown

Dies funktioniert nicht wie erwartet mit Lambdas, Funktionsaufrufen oder anderen Elementen, die möglicherweise zusätzliche Kommas in den Parametern enthalten.
Nandee

2

Hier ist eine einfache Möglichkeit, 0 oder mehr Argumente von VA_ARGS zu zählen. In meinem Beispiel werden maximal 5 Variablen angenommen, aber Sie können weitere hinzufügen, wenn Sie möchten.

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

Leider funktioniert der Ansatz falsch, wenn VA_ARGS_NUMer mit einem Makro verwendet wird: wenn ich #define TEST(dh leer TEST) habe und VA_ARGS_NUM(TEST)bei Verwendung in #if:(
AntonK

@AntonK Kannst du bitte genau posten, was du getan hast?
elhadi dp ıpɐɥ ן ǝ

0

Sie können Token fädeln und zählen:

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
Ich habe mir gerade die Bearbeitung dieser Antwort angesehen - anscheinend haben Sie zwei Konten. Wenn Sie sich an einen halten, können Sie Ihre eigenen Beiträge bearbeiten, ohne dass eine Genehmigung erforderlich ist.
J Richard Snape

0

Boost Preprocessor hat dies tatsächlich ab Boost 1.49, as BOOST_PP_VARIADIC_SIZE(...). Es funktioniert bis zu Größe 64.

Unter der Haube ist es im Grunde dasselbe wie die Antwort von Kornel Kisielewicz .


@ CarloWood In der Tat. Der Präprozessor hat nicht wirklich das Konzept von "Nullargumenten". Was wir als "Nullargumente" betrachten, ist "ein leeres Argument" im Präprozessor. Aber es kann mit C ++ 20 __VA_OPT__oder den Compiler-Erweiterungen zum ##__VA_ARGS__Entfernen des vorherigen Kommas behoben werden
Justin

0

Ich habe hier noch unvollständige Antworten gefunden.

Die nächste tragbare Implementierung, die ich hier gefunden habe, ist: C ++ - Präprozessor __VA_ARGS__ Anzahl der Argumente

Es funktioniert jedoch nicht mit den Nullargumenten im GCC ohne mindestens -std=gnu++11Befehlszeilenparameter.

Deshalb habe ich beschlossen, diese Lösung mit der folgenden zusammenzuführen: 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
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.