2010-01-23 4 views
67

Простой вопрос, для которого я не смог найти ответ в сети. В переменном аргументе макросы, как найти количество аргументов? Я в порядке с препроцессором boost, если у него есть решение.Препроцессор C++ __VA_ARGS__ количество аргументов

Если это имеет значение, я пытаюсь преобразовать переменное количество аргументов макроса, чтобы повысить последовательность, список или массив препроцессора для дальнейшей переработки.

+0

Для того чтобы быть ясным - вы спрашиваете о переменных макросах, а не о макросах, используемых для создания вариативных функций C? –

+1

являются аргументами того же типа? если это так, и если тип известен, то существует стандартный раствор С через составные литералы; если это неизвестно, вы можете использовать '__typeof__', чтобы заставить его работать хотя бы на некоторых компиляторах. – Christoph

+1

Поскольку обсуждение посвящено последовательности препроцессора Boost и т. д., это должно быть C++ (поэтому я перезапустил Q - но не смог изменить название вопроса) ... К сожалению; Я исправлю это. –

ответ

54

Это на самом деле зависит от компилятора и не поддерживается никаким стандартом.

Однако здесь у вас есть macro implementation, что делает подсчет:

#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 
+0

Ссылка исчезла! –

+0

.... но теперь он стандартный в C++ 0x и должен был быть более длинным, потому что он позволяет отличным способом защищать функции varadic от поврежденных вызовов (т. Е. Вы можете передавать значения после элементов varadic. способ получить счетчик, который я использовал, но я думаю, что sizeof может работать тоже. – osirisgothra

+0

Ответ ссылки на другой сайт. Также ссылка не указывает на правильный ответ. И даже если мне удалось найти Ответ на него кажется плохим, поскольку он встраивает жесткокодированный «-1», который будет скомпилирован. Есть более эффективные методы. – ceztko

74

Я обычно использую этот макрос, чтобы найти ряд Params:

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

Полный пример:

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

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

void sum(int numargs, ...); 

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

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

    return 1; 
} 

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

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

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

    return; 
} 

Это вполне допустимый код C99. Однако у него есть один недостаток - вы не можете вызывать макрос SUM() без параметров, но у GCC есть решение - см. here.

Так что в случае GCC вам нужно определить макросы, как это:

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

и он будет работать даже с пустым списком параметров

+1

UM, он не будет работать для OP, ему нужен размер для BOOST_PP, который запускается во время компиляции. –

+4

Умный! Это также работает, когда 'sizeof (int)! = Sizeof (void *)'? –

+1

@Kornel Как и любой макрос, он оценивается во время компиляции. Я понятия не имею о Boost, но в любом случае Boost не нужен. – qrdl

6

С MSVC расширения:

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

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

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

Работы для 0 - 32 аргумента. Этот предел может быть легко расширен.

+4

это только я или этот вид нарушает правила запаха кода ..? – osirisgothra

+0

Он работает для меня с VC++ до, по крайней мере, VS2012, и GCC и clang также в моем базовом тестировании. – ThreeBit

+0

@osirisgothra, именно поэтому он пахнет? – ceztko

3

Это работает с 0 аргументами с gcc/llvm. [ссылки тупые]

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

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

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

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

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

Я тестировал это для Visual Studio 2008 и не работал для 0 аргументов COUNT_ARGS() = 1. – user720594

+0

Ссылка кажется сломанной. –

+0

фиксированная связь. VS должен делать что-то другое, как обычно :). Я не думаю, что они будут поддерживать C99 полностью в ближайшее время. – user1187902

0

Вы можете stringfy и сосчитать жетоны:

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

Только что посмотрел на редактирование, ожидающее ответа на этот вопрос - похоже, у вас могут быть две учетные записи. Если вы придерживаетесь одного, вы сможете редактировать свои собственные сообщения без его одобрения. –

18

Если вы используете C++ 11, и вам нужно значение, как C++ во время компиляции константа, очень элегантное решение заключается в следующем:

#include <tuple> 

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

Пожалуйста, обратите внимание: отсчет происходит полностью во время компиляции, а значение может использоваться всякий раз, когда требуется время компиляции целым числом, например, в качестве шаблона ра rameter to std :: array.

+1

Отличное решение! И в отличие от 'sizeof ((int []) {__ VA_ARGS __})/sizeof (int)', предложенного выше, он работает, даже если аргументы не могут быть переданы в 'int'. – Wim

3

См. Ниже решение для компиляции C++ 11. Чтобы получить аргументы, нажмите this.

#include <iostream> 

template < unsigned N > constexpr 
unsigned countarg(const char(&s)[N], unsigned i = 0, unsigned c = 0) 
{ 
    return 
    s[i] == '\0' 
    ? i == 0 
    ? 0 
    : c + 1 
    : s[i] == ',' 
    ? countarg(s, i + 1, c + 1) 
    : countarg(s, i + 1, c); 
} 

constexpr 
unsigned countarg() 
{ 
    return 0; 
} 

#define ARGC(...) countarg(#__VA_ARGS__) 


int main() 
{ 
    std::cout 
    << ARGC() << std::endl 
    << ARGC(1) << std::endl 
    << ARGC(one, two) << std::endl 
    << ARGC("abc", 123, XYZ) << std::endl 
    << ARGC(unknown = 0, red = 1, green = 2, blue = 4) << std::endl 
    << ARGC("1", "2", "3", "4", "5") << std::endl 
    << "Wrong (comma must be escaped):" << ARGC("This is a comma: ,") << std::endl 
    << "Fine: " << ARGC("This is a comma: \x2c"); 


    return 0; 
} 

Demo

+1

Мне нравится идея, однако у этого кода есть проблема - ARGC (",,,") вернет 3 (#__VA_ARGS__ преобразует пакет аргументов в строку, разделенную на кому, если аргументы являются строковыми литералами, они просто будут объединены). Вы знаете, как это исправить? –

+0

@PavelDavydov См. Обновленный ответ. – ZDF

2

здесь, простой способ подсчета 0 или более аргументов va_args, мой Exemple предполагает максимум 5 переменных, но вы можете добавить больше, если вы хотите.

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


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

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

#ifdef _MSC_VER // Microsoft compilers 

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

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

#else // Non-Microsoft compilers 

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

#endif 

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