C++ preprocessor __VA_ARGS__ number of arguments
简单的问题,我无法在网上找到答案。 在可变参数宏中,如何查找参数的数量? 如果它有解决方案,我可以使用boost预处理器。
如果它有所不同,我试图转换可变数量的宏参数来增强预处理器序列,列表或数组以进行进一步的重新处理。
我通常使用这个宏来查找一些参数:
1 | #define NUMARGS(...) (sizeof((int[]){__VA_ARGS__})/sizeof(int)) |
完整示例:
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 | #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 ", total); return; } |
它是完全有效的C99代码。但它有一个缺点 - 你不能在没有params的情况下调用宏
所以在GCC的情况下,你需要定义这样的宏:
1 2 | #define NUMARGS(...) (sizeof((int[]){0, ##__VA_ARGS__})/sizeof(int)-1) #define SUM(...) sum(NUMARGS(__VA_ARGS__), ##__VA_ARGS__) |
它甚至可以用于空参数列表
这实际上是依赖于编译器的,并且不受任何标准的支持。
但是,你有一个宏计算实现计数:
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 | #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 |
如果您使用的是C ++ 11,并且您需要将该值作为C ++编译时常量,那么这是一个非常优雅的解决方案:
1 2 3 4 5 6 | #include <tuple> #define MACRO(...) \ std::cout <<"num args:" \ << std::tuple_size<decltype(std::make_tuple(__VA_ARGS__))>::value \ << std::endl; |
请注意:计数完全在编译时发生,并且只要需要编译时整数,就可以使用该值,例如作为std :: array的模板参数。
为方便起见,这里有一个适用于0到70个参数的实现,适用于Visual Studio,GCC和Clang。我相信它将在Visual Studio 2010及更高版本中运行,但仅在VS2013中进行了测试。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 | #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"); |
有一些C ++ 11解决方案可以在编译时查找参数的数量,但我很惊讶地看到没有人提出任何如此简单的建议:
1 2 3 4 5 6 7 | #define VA_COUNT(...) detail::va_count(__VA_ARGS__) namespace detail { template<typename ...Args> constexpr std::size_t va_count(Args&&...) { return sizeof...(Args); } } |
这也不需要包含
这与gcc / llvm的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 | /* * 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似乎忽略了用于使用空参数的##运算符。你可以用类似的东西解决这个问题
1 2 3 4 5 6 7 8 9 10 | #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 |
使用msvc扩展名:
1 2 3 4 5 6 | #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个参数。这个限制可以很容易地扩展。
我假设VA_ARGS的每个参数都以逗号分隔。如果是这样,我认为这应该是一个非常干净的方式来做到这一点。
1 2 3 4 5 6 7 8 9 10 11 12 | #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; } |
为godbolt为clang 4和GCC 5.1工作。这将在编译时计算,但不会评估预处理器。因此,如果您尝试执行类似FOR_EACH的操作,那么这将无效。
这里有一个简单的方法来计算VA_ARGS的0个或更多个参数,我的例子假定最多有5个变量,但是你可以根据需要添加更多变量。
1 2 3 4 5 6 7 8 | #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 ... |
Boost预处理器实际上具有Boost 1.49,为
在引擎盖下,它与Kornel Kisielewicz的回答基本相同。
你可以使用stringfy和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 | 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 ",count); \ } |