Preprocesador C ++ __VA_ARGS__ número de argumentos


99

Pregunta simple para la que no pude encontrar respuesta en la red. En macros de argumentos variados, ¿cómo encontrar el número de argumentos? Estoy de acuerdo con el preprocesador boost, si tiene la solución.

Si hace una diferencia, estoy tratando de convertir un número variable de argumentos macro para impulsar la secuencia, lista o matriz del preprocesador para su posterior reprocesamiento.


Para ser claros, ¿está preguntando sobre macros variadic y no sobre las macros utilizadas para crear funciones C variadic?

2
¿Son los argumentos del mismo tipo? si es así, y si se conoce el tipo, hay una solución C estándar mediante literales compuestos; si es desconocido, podría usarlo __typeof__para que funcione al menos en algunos compiladores
Christoph

1
Dado que la discusión es sobre la secuencia del preprocesador Boost, etc., tiene que ser C ++ (por eso volví a etiquetar la Q, pero no pude cambiar el título de la pregunta) ... Vaya; Yo arreglaré eso.
Jonathan Leffler

@JonathanLeffler Es cierto, Boost es una biblioteca de C ++. Sin embargo, Boost.Preprocessor se puede usar con C. AFAIK, nada de lo que usa es específico de C ++.
Justin

Respuestas:


90

En realidad, esto depende del compilador y no es compatible con ningún estándar.

Aquí, sin embargo, tiene una implementación de macro que hace el recuento:

#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

.... pero ahora es estándar en C ++ 0x y debería haber sido hace más tiempo porque permite una excelente manera de proteger funciones varadic de llamadas corruptas (es decir, puede pasar valores después de los elementos varadic. Esta es en realidad una forma de obtener el conteo que solía usar, pero supongo que sizeof también podría funcionar ..
osirisgothra

La respuesta enlaza a otro sitio. Además, el enlace no parece apuntar a la respuesta correcta. E incluso si logré encontrar la respuesta deseada, parece una mala, ya que incrusta un "-1" codificado que se compilará. Hay mejores métodos.
ceztko

2
¡Gracias! esto funcionó en Visual Studio 2013 para mí: #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()no devuelve 0. Las soluciones GET_ARG_COUNT()& Y_TUPLE_SIZE()funcionan.
PSkocik

1
" PP_NARG()no devuelve 0" ... no es necesariamente un problema. Se puede decir que PP_NARG() debería devolver 1 por la misma razón PP_NARG(,)debería devolver 2. Detectar 0 puede ser útil en algunos casos, pero las soluciones parecen ser menos generales (requiriendo que el primer token sea pegable; lo cual puede o no estar bien dependiendo de para qué lo esté usando), o una implementación específica (como requerir el truco de gnu para quitar y pegar comas).
H Walters

100

Normalmente uso esta macro para encontrar varios parámetros:

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

Ejemplo completo:

#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 un código C99 completamente válido. Sin embargo, tiene un inconveniente: no puede invocar la macro SUM()sin parámetros, pero GCC tiene una solución, consulte aquí .

Entonces, en el caso de GCC, debe definir macros como este:

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

y funcionará incluso con la lista de parámetros vacía


4
UM, no funcionará para el OP, necesita el tamaño de BOOST_PP que se ejecuta en tiempo de compilación.
Kornel Kisielewicz

5
¡Inteligente! ¿También funciona cuando sizeof(int) != sizeof(void *)?
Adam Liss

3
@Kornel Como cualquier macro, se evalúa en tiempo de compilación. No tengo ni idea de Boost, pero de todos modos no es necesario.
qrdl

4
@Adam Porque lanzo {__VA_ARGS__}a int[], es justo int[], independientemente del contenido real de__VA_ARGS__
qrdl

3
¡Solución elegante! Funciona en VS2017. No ##es necesario en VS2017 ya que un vacío __VA_ARGS__eliminará automáticamente cualquier coma anterior.
poby

37

Si está utilizando C ++ 11 y necesita el valor como una constante de tiempo de compilación de C ++, una solución muy elegante es esta:

#include <tuple>

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

Tenga en cuenta: el recuento ocurre completamente en tiempo de compilación, y el valor se puede usar siempre que se requiera un entero en tiempo de compilación, por ejemplo, como un parámetro de plantilla para std :: array.


2
¡Gran solución! Y a diferencia de lo sizeof((int[]){__VA_ARGS__})/sizeof(int)sugerido anteriormente, funciona incluso cuando no se pueden lanzar todos los argumentos int.
Wim

Convenido. ¡Gran solución! ++.
davernator

No funciona con plantillas, es decir, NUMARGS (suma <1,2>); ver godbolt.org/z/_AAxmL
jorgbrown

1
Yo ... creo que en realidad podría ser un punto a favor, @jorgbrown, al menos en la mayoría de los casos en los que surgió. Dado que se basa en el compilador en lugar del preprocesador para realizar el recuento, proporciona la cantidad de argumentos tal como los ve el compilador, que probablemente coincidirá con lo que esperan la mayoría de los programadores. Se va a causar problemas si se espera que entre en la avidez preprocesador en cuenta, sin embargo.
Justin Time - Reincorpora a Monica

Excelente respuesta. Puedes ponerlo en una macro#define NUM_ARGS(...) std::tuple_size<decltype(std::make_tuple(__VA_ARGS__))>::value
Richard Whitehead

23

Para mayor comodidad, aquí hay una implementación que funciona para 0 a 70 argumentos y funciona en Visual Studio, GCC y Clang . Creo que funcionará en Visual Studio 2010 y versiones posteriores, pero solo lo he probado en 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");

En mi humilde opinión, la variante de Microsoft falla por cero argumentos.
Vroomfondel

@Vroomfondel, la variante de Microsoft funciona para cero argumentos. El primer static_assert en el ejemplo anterior es una prueba específica para el caso de argumento cero, y simplemente lo compilé y ejecuté en Visual Studio 2017 v15.8.9.
Chris Kline

Interesante: usar la variante de Microsoft en un compilador que no es de Microsoft no funciona, ¿sabe qué hace el preprocesador M $ de manera diferente que hace que el código funcione de manera opuesta? Por cierto, probé C, no C ++;
Vroomfondel

Creo que eso se debe a que MSVC es un poco más agradable con respecto a la "longitud cero __VA_ARGS__" (que en C ++, es técnicamente una extensión del compilador (casi universal, estándar de facto ) hasta C ++ 20). La mayoría (¿todos?) Los compiladores permiten la longitud cero, pero se ahogan con la coma final si la lista está vacía (y se sobrecargan ##como un proto- __VA_OPT__, para eliminar la coma en este caso); La versión de MSVC de la extensión simplemente no se ahoga con la coma (pero se ahoga con la sobrecargada ##). Compare MSVC unused, __VA_ARGS__con no MSVC 0, ## __VA_ARGS__; ninguno es más correcto, el problema es que son diferentes.
Justin Time - Reincorpora a Monica

Sin embargo, no estoy seguro de si esto es lo mismo en C, @Vroomfondel, ya que perdí mi marcador en el borrador más reciente.
Justin Time - Reincorpora a Monica

11

Hay algunas soluciones de C ++ 11 para encontrar la cantidad de argumentos en tiempo de compilación, pero me sorprende ver que nadie ha sugerido algo tan simple como:

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

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

Esto tampoco requiere la inclusión del <tuple>encabezado.


1
"pero ¿por qué no usar una plantilla variable y un tamaño de ... en su lugar (como en mi propia respuesta)" c ++ se ha convertido en un monstruo. Tiene demasiadas funciones y muchas de ellas, como las plantillas variadas, rara vez se utilizan. Lees sobre ello, escribes algunos ejemplos y luego lo olvidas. Por lo tanto, es difícil tener la idea adecuada en el momento adecuado. Dado que su solución parece ser una mejor opción que la mía, dejaré que la selección natural funcione y eliminaré mi solución.
zdf

1
@ZDF es comprensible, pero uso plantillas variadas constantemente. Mis programas se han vuelto mucho más robustos desde C ++ 11, y esta es una de las principales razones. Sin embargo, creo que no es necesario eliminar tu respuesta.
monkey0506

1
No funcionará con algo parecido VA_COUNT(&,^,%). Además, si está contando a través de una función, no veo ningún sentido en hacer una macro.
Qwertiy

Esta solución sigue siendo una pregunta: los parámetros de VA_COUNT son todos identificadores que aún no se han definido como una variable o algo así, y causa el error '*** la variable no está definida'. ¿Hay alguna forma de arreglar esto?
ipid

7

esto funciona con 0 argumentos con gcc / llvm. [los enlaces son tontos]

/*
 * 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 parece estar ignorando el operador ## utilizado para consumir el argumento vacío. Probablemente puedas evitar eso con algo como

#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

Probé esto para Visual Studio 2008 y no funcionó para 0 argumentos COUNT_ARGS () = 1.
user720594

El vínculo parece roto.
Jan Smrčina

enlace fijo. VS debe estar haciendo algo diferente como de costumbre :). No creo que vayan a admitir C99 por completo en el corto plazo.
user1187902

2
Er, ##__VA_ARGS__comerse la coma antes de si __VA_ARGS__está vacío es una extensión GCC. No es el comportamiento estándar.
Financia la demanda de Monica

6

Con extensión 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

Funciona para 0 - 32 argumentos. Este límite se puede ampliar fácilmente.

EDITAR: Versión simplificada (funciona en VS2015 14.0.25431.01 Actualización 3 y gcc 7.4.0) hasta 100 argumentos para copiar y pegar:

#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
¿Soy solo yo o esto rompe un poco las reglas de olor del código ...?
osirisgothra

Me funciona con VC ++ hasta al menos VS2012, y GCC y clang también en mis pruebas básicas.
ThreeBit

@osirisgothra, ¿exactamente por qué huele?
ceztko

Si bien esta macro tiene un amplio soporte de compiladores, no funciona con argumentos de macro como una cadena Y_TUPLE_SIZE("Hello"), por lo que es bastante inviable. Estoy de acuerdo con @osirisgothra.
ceztko

1
Esta macro puede funcionar para usted pero tiene serios defectos. Investigué mucho y encontré enfoques más limpios que funcionan en GCC y VS. Puede encontrarlos en mi respuesta a una pregunta similar.
ceztko

3

Supongo que cada argumento de VA_ARGS estará separado por comas. Si es así, creo que esto debería funcionar como una forma bastante limpia de hacer esto.

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

Funcionó para mí en godbolt para clang 4 y GCC 5.1. Esto se computará en tiempo de compilación, pero no se evaluará para el preprocesador. Entonces, si está tratando de hacer algo como hacer un FOR_EACH , esto no funcionará.


Esta respuesta está subestimada. Funcionará incluso para NUMARGS(hello, world = 2, ohmy42, !@#$%^&*()-+=)!!! Sin ','embargo
pterodragon

Debe modificarse para parens, porque int count = NUMARGS( foo(1, 2) );produce 2 en lugar de 1. godbolt.org/z/kpBuOm
jorgbrown

Esto no funcionará como se esperaba con lambdas, llamadas a funciones o cualquier otra cosa que pueda contener comas adicionales en los parámetros.
Nandee

2

aquí una forma simple de contar 0 o más argumentos de VA_ARGS , mi ejemplo asume un máximo de 5 variables, pero puede agregar más si lo desea.

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

Desafortunadamente, el enfoque funciona incorrectamente cuando VA_ARGS_NUMse usa con macro: si tengo #define TEST(es decir, vacío TEST) y VA_ARGS_NUM(TEST)no devuelve 0 (cero) cuando se usa en #if:(
AntonK

@AntonK, ¿puedes publicar exactamente lo que has hecho, por favor?
elhadi dp ıpɐɥ ן ǝ

0

Puede encadenar y contar tokens:

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
Acabo de echar un vistazo a la edición pendiente de esta respuesta: parece que tiene dos cuentas. Si se apega a uno, podrá editar sus propias publicaciones sin necesidad de aprobación.
J Richard Snape

0

Boost Preprocessor en realidad tiene esto a partir de Boost 1.49, como BOOST_PP_VARIADIC_SIZE(...) . Funciona hasta el tamaño 64.

Debajo del capó, es básicamente lo mismo que la respuesta de Kornel Kisielewicz .


@CarloWood De hecho. El preprocesador no tiene realmente el concepto de "cero argumentos". Lo que consideramos "cero argumentos" es "un argumento vacío" en el preprocesador. Pero se puede arreglar usando C ++ 20 __VA_OPT__o las extensiones del compilador para ##__VA_ARGS__eliminar la coma anterior, por ejemplo: godbolt.org/z/X7OvnK
Justin

0

He encontrado respuestas aquí que todavía están incompletas.

La implementación portátil más cercana que he encontrado desde aquí es: preprocesador C ++ __VA_ARGS__ número de argumentos

Pero no funciona con los argumentos cero en el GCC sin al menos el -std=gnu++11parámetro de línea de comando.

Así que decidí fusionar esta solución con eso: 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
Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.