Préprocesseur C ++ __VA_ARGS__ nombre d'arguments

99

Question simple à laquelle je n'ai pas trouvé de réponse sur le net. Dans les macros d'arguments variadiques, comment trouver le nombre d'arguments? Je suis d'accord avec le préprocesseur boost, s'il a la solution.

Si cela fait une différence, j'essaie de convertir un nombre variable d'arguments de macro pour stimuler la séquence, la liste ou le tableau du préprocesseur pour un retraitement ultérieur.

Anycorn
la source
Juste pour être clair - vous posez des questions sur les macros variadiques, et non sur les macros utilisées pour créer des fonctions variadiques C?
2
les arguments sont-ils du même type? si c'est le cas, et si le type est connu, il existe une solution C standard via des littéraux composés; s'il est inconnu, vous pouvez l'utiliser __typeof__pour le faire fonctionner au moins sur certains compilateurs
Christoph
1
Puisque la discussion porte sur la séquence du préprocesseur Boost, etc., elle doit être en C ++ (c'est pourquoi j'ai redéfini le Q - mais je n'ai pas réussi à changer le titre de la question) ... Oups; Je vais réparer ça.
Jonathan Leffler
@JonathanLeffler True, Boost est une bibliothèque C ++. Cependant, Boost.Preprocessor peut être utilisé avec C. AFAIK, rien de ce qu'il utilise n'est spécifique à C ++.
Justin
En relation: stackoverflow.com/questions/11761703/…
Gabriel Staples

Réponses:

90

Cela dépend en fait du compilateur et n'est pris en charge par aucun standard.

Ici, cependant, vous avez une implémentation de macro qui fait le décompte:

#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
Kornel Kisielewicz
la source
.... mais maintenant est standard dans C ++ 0x et aurait dû être il y a plus longtemps car il permet un excellent moyen de protéger les fonctions varadic des appels corrompus (c'est-à-dire que vous pouvez passer des valeurs après les éléments varadic. C'est en fait un moyen d'obtenir le décompte que j'avais l'habitude d'utiliser, mais je suppose que sizeof pourrait fonctionner aussi ..
osirisgothra
La réponse renvoie à un autre site. De plus, le lien ne semble pas pointer vers la bonne réponse. Et même si j'ai réussi à trouver la réponse voulue, cela me semble médiocre car il intègre un "-1" codé en dur qui sera compilé. Il existe de meilleures méthodes.
ceztko
2
Merci! cela a fonctionné dans Visual Studio 2013 pour moi: #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()ne parvient pas à renvoyer 0. Les solutions GET_ARG_COUNT()& Y_TUPLE_SIZE()fonctionnent.
PSkocik
1
" PP_NARG()ne retourne pas 0" ... n'est pas nécessairement un problème. On peut dire que PP_NARG() devrait renvoyer 1 pour la même raison PP_NARG(,)devrait retourner 2. La détection de 0 peut en effet être pratique dans certains cas, mais les solutions semblent soit être moins générales (exigeant que ce premier jeton soit collable; ce qui peut ou non être correct selon l'utilisation que vous en faites), ou spécifique à l'implémentation (comme exiger l'astuce de suppression de virgule-coller de gnu).
H Walters
100

J'utilise généralement cette macro pour trouver un certain nombre de paramètres:

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

Exemple complet:

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

C'est un code C99 entièrement valide. Il a un inconvénient, cependant - vous ne pouvez pas invoquer la macro SUM()sans paramètres, mais GCC a une solution - voir ici .

Donc, dans le cas de GCC, vous devez définir des macros comme ceci:

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

et cela fonctionnera même avec une liste de paramètres vide

qrdl
la source
4
UM, cela ne fonctionnera pas pour l'OP, il a besoin de la taille de BOOST_PP qui s'exécute au moment de la compilation.
Kornel Kisielewicz
5
Intelligent! Est-ce que ça marche aussi quand sizeof(int) != sizeof(void *)?
Adam Liss
3
@Kornel Comme toute macro, elle est évaluée à la compilation. Je n'ai aucune idée de Boost, mais de toute façon Boost n'est pas nécessaire.
qrdl
4
@Adam Parce que j'ai casté {__VA_ARGS__}sur int[], c'est juste int[], quel que soit le contenu réel de__VA_ARGS__
qrdl
3
Solution élégante! Fonctionne dans VS2017. Le ##n'est pas nécessaire dans VS2017 car un vide __VA_ARGS__supprimera automatiquement toute virgule précédente.
poby
37

Si vous utilisez C ++ 11 et que vous avez besoin de la valeur en tant que constante de compilation C ++, une solution très élégante est la suivante:

#include <tuple>

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

Remarque: le comptage se produit entièrement au moment de la compilation, et la valeur peut être utilisée chaque fois qu'un entier au moment de la compilation est requis, par exemple comme paramètre de modèle pour std :: array.

Maciek Gajewski
la source
2
Excellente solution! Et contrairement à ce qui est sizeof((int[]){__VA_ARGS__})/sizeof(int)suggéré ci-dessus, cela fonctionne même lorsque les arguments ne peuvent pas tous être exprimés int.
Wim
D'accord. Excellente solution! ++.
davernator
Ne fonctionne pas avec les modèles, c'est-à-dire NUMARGS (somme <1,2>); voir godbolt.org/z/_AAxmL
jorgbrown
1
Je ... pense que cela pourrait en fait être un point en faveur, @jorgbrown, du moins dans la plupart des cas où cela se présenterait. Puisqu'il s'appuie sur le compilateur au lieu du préprocesseur pour faire le comptage, il donne le nombre d'arguments vu par le compilateur, qui correspondra probablement à ce que la plupart des programmeurs attendent. Il va causer des ennuis si vous vous attendez à prendre en compte préprocesseur greediness, cependant.
Justin Time - Réintègre Monica
Superbe réponse. Vous pouvez le mettre dans une macro#define NUM_ARGS(...) std::tuple_size<decltype(std::make_tuple(__VA_ARGS__))>::value
Richard Whitehead
23

Pour plus de commodité, voici une implémentation qui fonctionne pour 0 à 70 arguments et fonctionne dans Visual Studio, GCC et Clang . Je pense que cela fonctionnera dans Visual Studio 2010 et versions ultérieures, mais je ne l'ai testé que dans 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");
Chris Kline
la source
IMHO la variante Microsoft échoue pour zéro argument.
Vroomfondel
@Vroomfondel la variante Microsoft fonctionne pour zéro argument. Le tout premier static_assert dans l'exemple ci-dessus est un test spécifique pour le cas zéro argument, et je viens de le compiler et de l'exécuter sur Visual Studio 2017 v15.8.9.
Chris Kline
Intéressant - l'utilisation de la variante Microsoft sur un compilateur non-Microsoft ne fonctionne pas - savez-vous ce que le préprocesseur M $ fait différemment pour que le code fonctionne dans le sens inverse? BTW j'ai essayé C, pas C ++;
Vroomfondel
Je crois que c'est parce que MSVC est un peu plus gentil avec la "longueur nulle __VA_ARGS__" (qui en C ++, est techniquement une extension de compilateur (presque universelle, de facto standard) jusqu'à C ++ 20). La plupart (tous?) Des compilateurs autorisent une longueur nulle, mais s'étouffent avec la virgule de fin si la liste est vide (et surcharge en ##tant que proto- __VA_OPT__, pour supprimer la virgule dans ce cas); La version de MSVC de l'extension ne fonctionne tout simplement pas étouffer la virgule (mais va étouffer la surcharge ##). Comparez MSVC unused, __VA_ARGS__à non MSVC 0, ## __VA_ARGS__; ni l'un ni l'autre n'est plus correct, le problème est qu'ils sont différents.
Justin Time - Réintègre Monica
Je ne sais pas si c'est la même chose en C, cependant, @Vroomfondel, car j'ai perdu mon signet dans le brouillon le plus récent.
Justin Time - Réintègre Monica
11

Il existe des solutions C ++ 11 pour trouver le nombre d'arguments au moment de la compilation, mais je suis surpris de voir que personne n'a suggéré quelque chose d'aussi simple que:

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

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

Cela ne nécessite pas non plus l'inclusion de l'en- <tuple>tête.

singe0506
la source
1
"mais pourquoi ne pas simplement utiliser un template variadic et sizeof ... à la place (comme dans ma propre réponse)" c ++ est devenu un monstre. Il a trop de fonctionnalités et beaucoup d'entre elles, comme les modèles variadiques, sont rarement utilisées. Vous lisez à ce sujet, vous écrivez des exemples et ensuite vous l'oubliez. Par conséquent, il est difficile de trouver la bonne idée au bon moment. Puisque votre solution semble être une meilleure option que la mienne, je laisserai la sélection naturelle fonctionner et je supprimerai ma solution.
zdf
1
@ZDF compréhensible, mais j'utilise constamment des modèles variadiques. Mes programmes sont devenus beaucoup plus robustes depuis C ++ 11, et c'est l'une des principales raisons pour lesquelles. Inutile de supprimer votre réponse, je pense.
monkey0506
1
Cela ne fonctionnera pas avec smth like VA_COUNT(&,^,%). De plus, si vous comptez via une fonction, je ne vois aucun sens à créer une macro.
Qwertiy
Cette solution reste une question: les paramètres de VA_COUNT sont tous des identifiants qui ne sont pas encore définis comme une variable ou quelque chose, et cela provoque l'erreur '*** variable n'est pas définie'. Est-ce qu'il y a un moyen de réparer ceci?
ipid
7

cela fonctionne avec 0 argument avec gcc / llvm. [les liens sont stupides]

/*
 * 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 semble ignorer l'opérateur ## utilisé pour consommer l'argument vide. Vous pouvez probablement contourner cela avec quelque chose comme

#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
user1187902
la source
J'ai testé cela pour Visual Studio 2008 et cela n'a pas fonctionné pour 0 argument COUNT_ARGS () = 1.
user720594
Le lien semble rompu.
Jan Smrčina
lien fixe. VS doit faire quelque chose de différent comme d'habitude :). Je ne pense pas qu'ils soutiendront pleinement C99 de si tôt.
user1187902
2
Euh, ##__VA_ARGS__manger la virgule avant si __VA_ARGS__est vide est une extension GCC. Ce n'est pas le comportement standard.
Fund Monica's Lawsuit le
6

Avec l'extension 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

Fonctionne pour 0 à 32 arguments. Cette limite peut être facilement étendue.

EDIT: Version simplifiée (fonctionne dans VS2015 14.0.25431.01 Update 3 & gcc 7.4.0) jusqu'à 100 arguments à copier et coller:

#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
user720594
la source
4
est-ce juste moi ou est-ce que cela enfreint les règles d'odeur du code ..
osirisgothra
Cela fonctionne pour moi avec VC ++ jusqu'à au moins VS2012, et GCC et clang aussi dans mes tests de base.
ThreeBit
@osirisgothra, pourquoi ça sent exactement?
ceztko
Bien que cette macro ait une large prise en charge des compilateurs, elle ne fonctionne pas avec des arguments de macro comme une chaîne Y_TUPLE_SIZE("Hello"), ce qui la rend tout à fait irréalisable. Je suis d'accord avec @osirisgothra.
ceztko
1
Cette macro peut fonctionner pour vous mais présente de sérieux défauts. J'ai fait beaucoup de recherches et trouvé des approches plus propres qui fonctionnent dans GCC et VS. Vous pouvez les trouver dans ma réponse à une question similaire.
ceztko
3

Je suppose que chaque argument de VA_ARGS sera séparé par des virgules. Si c'est le cas, je pense que cela devrait fonctionner comme un moyen assez propre de le faire.

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

A travaillé pour moi sur godbolt pour clang 4 et GCC 5.1. Cela calculera au moment de la compilation, mais n'évaluera pas pour le préprocesseur. Donc, si vous essayez de faire quelque chose comme créer un FOR_EACH , cela ne fonctionnera pas.

matanmarkind
la source
Cette réponse est sous-estimée. Cela fonctionnera même pour NUMARGS(hello, world = 2, ohmy42, !@#$%^&*()-+=)!!! Chaque chaîne d'argument ne peut pas avoir d'autres symboles comme ','si
ptérodragon
Doit être modifié pour les parens, car int count = NUMARGS( foo(1, 2) );produit 2 au lieu de 1. godbolt.org/z/kpBuOm
jorgbrown
Cela ne fonctionnera pas comme prévu avec les lambdas, les appels de fonction ou tout autre élément pouvant contenir des virgules supplémentaires dans les paramètres.
Nandee
2

ici un moyen simple de compter 0 ou plus d'arguments de VA_ARGS , mon exemple suppose un maximum de 5 variables, mais vous pouvez en ajouter plus si vous le souhaitez.

#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
         ...
elhadi dp ıpɐɥ ן ǝ
la source
Malheureusement, l'approche ne fonctionne pas correctement lorsqu'elle VA_ARGS_NUMest utilisée avec la macro: si j'ai #define TEST(c'est-à-dire vide TEST) et VA_ARGS_NUM(TEST)ne renvoie pas 0 (zéro) lorsqu'elle est utilisée dans #if:(
AntonK
@AntonK pouvez-vous poster ce que vous avez fait exactement s'il vous plaît?
elhadi dp ıpɐɥ ן ǝ
0

Vous pouvez enchaîner et compter les jetons:

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); \
}
Carlos Leite
la source
2
Je viens de jeter un œil à la modification en attente sur cette réponse - il semble que vous ayez peut-être deux comptes. Si vous vous en tenez à un, vous pourrez modifier vos propres messages sans qu'il soit soumis à l'approbation.
J Richard Snape
0

Boost Preprocessor a en fait ceci à partir de Boost 1.49, car BOOST_PP_VARIADIC_SIZE(...) . Cela fonctionne jusqu'à la taille 64.

Sous le capot, c'est fondamentalement la même chose que la réponse de Kornel Kisielewicz .

Justin
la source
@CarloWood En effet. Le préprocesseur n'a pas vraiment le concept de "zéro argument". Ce que nous considérons comme "zéro argument" est "un argument vide" dans le préprocesseur. Mais il est réparable en utilisant C ++ 20 __VA_OPT__ou les extensions du compilateur pour ##__VA_ARGS__supprimer la virgule précédente, par exemple: godbolt.org/z/X7OvnK
Justin
0

J'ai trouvé ici que les réponses sont encore incomplètes.

L'implémentation portable la plus proche que j'ai trouvée à partir d'ici est: préprocesseur C ++ __VA_ARGS__ nombre d'arguments

Mais cela ne fonctionne pas avec les arguments zéro dans le GCC sans au moins -std=gnu++11 paramètre de ligne de commande.

J'ai donc décidé de fusionner cette solution avec celle-ci: 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
Andry
la source