C++ 预处理器 __VA_ARGS__ 参数数量

Posted

技术标签:

【中文标题】C++ 预处理器 __VA_ARGS__ 参数数量【英文标题】:C++ preprocessor __VA_ARGS__ number of arguments 【发布时间】:2011-01-08 14:55:37 【问题描述】:

我在网上找不到答案的简单问题。在可变参数宏中,如何找到参数的数量?如果有解决方案,我可以使用 boost 预处理器。

如果有影响,我会尝试将可变数量的宏参数转换为提升预处理器序列、列表或数组,以便进一步重新处理。

【问题讨论】:

澄清一下 - 您问的是可变参数宏,而不是用于创建可变 C 函数的宏? 参数是否相同?如果是这样,并且如果类型已知,则通过复合文字有一个标准的 C 解决方案;如果未知,您可以使用__typeof__ 使其至少在某些编译器上工作 由于讨论是关于 Boost 预处理器序列等,它必须是 C++(这就是我重新标记 Q 的原因 - 但未能更改问题标题)...哎呀;我会解决的。 @JonathanLeffler 没错,Boost 是一个 C++ 库。但是,Boost.Preprocessor 可以与 C 一起使用。AFAIK,它使用的不是 C++ 特定的。 相关:***.com/questions/11761703/… 【参考方案1】:

我通常使用这个宏来查找多个参数:

#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__)

即使参数列表为空,它也能正常工作

【讨论】:

嗯,它对 OP 不起作用,他需要在编译时运行的 BOOST_PP 的大小。 聪明! sizeof(int) != sizeof(void *) 时也有效吗? @Kornel 与任何宏一样,它在编译时进行评估。我不知道 Boost,但无论如何都不需要 Boost。 @Adam 因为我将__VA_ARGS__ 转换为int[],所以只是int[],不管__VA_ARGS__ 的实际内容如何 优雅的解决方案!在VS2017中工作。 VS2017 中不需要 ##,因为空的 __VA_ARGS__ 会自动删除任何前面的逗号。【参考方案2】:

这实际上取决于编译器,不受任何标准支持。

但是你有一个 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

【讨论】:

....但现在在 C++0x 中是标准的,应该在很久以前,因为它提供了一种很好的方法来保护 varad 函数免受损坏的调用(即,您可以在可变项。这实际上是一种获取我以前使用的计数的方法,但我想 sizeof 也可以工作.. 谢谢!这对我来说在 Visual Studio 2013 中有效:#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))``` PP_NARG() 无法返回 0。GET_ARG_COUNT()Y_TUPLE_SIZE() 解决方案有效。 "PP_NARG() 无法返回 0" ...不一定是问题。可以说PP_NARG() 应该 返回 1,原因与 PP_NARG(,) 应该返回 2 的原因相同。检测 0 在某些情况下确实很方便,但解决方案似乎要么不太通用(要求第一个令牌是可粘贴的;这 可能可能不 可以,具体取决于您使用它的目的),或者特定于实现(例如因为需要 gnu 的逗号删除粘贴技巧)。 重复/相关答案:1)***.com/questions/11761703/… 和 2)(这个可以帮助我更轻松地了解最初发生的事情,因为它是一个较短的宏):***.com/questions/11761703/…【参考方案3】:

如果您使用的是 C++11,并且需要将该值作为 C++ 编译时常量,那么一个非常优雅的解决方案是:

#include <tuple>

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

请注意:计数完全在编译时发生,并且该值可以在需要编译时整数时使用,例如作为 std::array 的模板参数。

【讨论】:

很好的解决方案!与上面建议的 sizeof((int[])__VA_ARGS__)/sizeof(int) 不同,即使参数不能全部转换为 int,它也可以工作。 同意。很好的解决方案! ++. 不适用于模板,即 NUMARGS( sum );见godbolt.org/z/_AAxmL 我...认为 可能 实际上是支持它的一个观点,@j​​orgbrown,至少在大多数情况下它会出现。由于它依赖于编译器而不是预处理器来进行计数,因此它给出了编译器看到的参数数量,这可能与大多数程序员的期望相匹配。但是,如果您希望它考虑到预处理器的贪婪,它造成麻烦。 绝妙的答案。您可以将其放入宏#define NUM_ARGS(...) std::tuple_size&lt;decltype(std::make_tuple(__VA_ARGS__))&gt;::value【参考方案4】:

为方便起见,这里有一个适用于 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");

【讨论】:

恕我直言,Microsoft 变体因零参数而失败。 @Vroomfondel Microsoft 变体确实适用于零参数。上面示例中的第一个 static_assert 是针对零参数情况的特定测试,我刚刚在 Visual Studio 2017 v15.8.9 上编译并运行它。 有趣 - 在非 Microsoft 编译器上使用 Microsoft 变体不起作用 - 你知道 M$ 预处理器的不同之处在于使代码以相反的方式工作吗?顺便说一句,我尝试过 C,而不是 C++; 我相信这是因为 MSVC 对“零长度 __VA_ARGS__”(在 C++ 中,在技术上是一个(近乎普遍的,事实上的标准)编译器扩展直到 C++20)。大多数(全部?)编译器允许零长度,但如果列表 is 为空(并将## 重载为原型__VA_OPT__,以在这种情况下删除逗号,则会阻塞尾随逗号); MSVC 的扩展版本不会因逗号而窒息(但因重载的## 而窒息)。将 MSVC unused, __VA_ARGS__ 与非 MSVC 0, ## __VA_ARGS__ 进行比较;两者都不是更正确的,问题是它们是不同的。 不幸的是 gcc 不接受没有扩展名的 0 个参数:至少必须设置 -std=gnu++11 选项。【参考方案5】:

有一些 C++11 解决方案可以在编译时查找参数的数量,但令我惊讶的是,没有人提出任何如此简单的建议:

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

namespace detail

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

这也不需要包含 &lt;tuple&gt; 标头。

【讨论】:

“但为什么不直接使用可变参数模板和 sizeof... 代替(如我自己的回答)” c++ 已成为怪物。它有太多的功能,其中许多功能,如可变参数模板,很少使用。你读过它,你写了一些例子,然后你就忘记了。因此,很难在正确的时间提出正确的想法。由于您的解决方案似乎比我的解决方案更好,因此我将让自然选择起作用并删除我的解决方案。 @ZDF 可以理解,但我碰巧经常使用可变参数模板。自 C++11 以来,我的程序变得更加健壮,这是主要原因之一。我认为无需删除您的答案。 它不适用于像VA_COUNT(&amp;,^,%) 这样的东西。另外,如果您通过函数进行计数,我看不出制作宏有任何意义。 这个解决方案还有一个问题:VA_COUNT的参数都是尚未定义为变量或其他东西的标识符,它会导致错误'***变量未定义'。有没有办法解决这个问题?【参考方案6】:

这适用于 gcc/llvm 的 0 个参数。 [链接是愚蠢的]

/*
 * 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

【讨论】:

我在 Visual Studio 2008 上对此进行了测试,但它不适用于 0 个参数 COUNT_ARGS() = 1。 链接好像坏了。 固定链接。 VS 必须像往常一样做一些不同的事情:)。我认为他们不会很快完全支持 C99。 呃,如果__VA_ARGS__ 为空,##__VA_ARGS__ 在前面吃逗号是 GCC 扩展。这不是标准行为。 如果你使用-std=c99,它将不适用于GCC。【参考方案7】:

使用 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 个参数。此限制可以轻松扩展。

编辑: 简化版(适用于 VS2015 14.0.25431.01 更新 3 和 gcc 7.4.0)多达 100 个要复制和粘贴的参数:

#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

【讨论】:

是我自己还是这有点违反代码气味规则..? 在我的基本测试中,它适用于至少 VS2012 的 VC++,以及 GCC 和 clang。 @osirisgothra,到底为什么会这么臭? 虽然此宏具有广泛的编译器支持,但它不适用于诸如Y_TUPLE_SIZE("Hello") 之类的字符串之类的宏参数,因此非常不可行。我同意@osirisgothra。 这个宏可以为你工作,但有严重的缺陷。我做了很多研究,发现了在 GCC 和 VS 中工作的更简洁的方法。您可以在我的answer 中找到类似的问题。【参考方案8】:

我假设__VA_ARGS__ 的每个参数都将用逗号分隔。如果是这样,我认为这应该是一种非常干净的方法。

#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 之类的操作,那么这是行不通的。

【讨论】:

这个答案被低估了。它甚至适用于NUMARGS(hello, world = 2, ohmy42, !@#$%^&amp;*()-+=) !!!每个 arg 字符串都不能有其他符号,例如 ',' 需要调整括号,因为int count = NUMARGS( foo(1, 2) ); 产生 2 而不是 1。godbolt.org/z/kpBuOm 这对于 lambdas、函数调用或任何其他可能在参数中包含额外逗号的内容将无法正常工作。【参考方案9】:

这里有一个简单的方法来计算 VA_ARGS 的 0 个或多个参数,我的示例假设最多 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
         ...

【讨论】:

不幸的是,当VA_ARGS_NUM 与宏一起使用时,该方法无法正常工作:如果我有#define TEST(即空TEST)并且VA_ARGS_NUM(TEST) 在使用时不会返回0(零) #if :( @AntonK 你能发布一下你所做的事情吗?【参考方案10】:

我发现这里的答案仍然不完整。

我从这里找到的最接近的可移植实现是: C++ preprocessor __VA_ARGS__ number of arguments

但如果没有至少-std=gnu++11 命令行参数,它不适用于 GCC 中的零参数。

所以我决定将此解决方案与该解决方案合并: 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

【讨论】:

感谢您指出 gnu 标准。它解决了我的问题:D,不像其他答案......【参考方案11】:

您可以对令牌进行字符串化和计数:

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); \

【讨论】:

刚刚查看了此答案的待处理编辑 - 看来您可能有两个帐户。如果您坚持一个,您将能够编辑自己的帖子而无需获得批准。【参考方案12】:

Boost 预处理器实际上在 Boost 1.49 中具有此功能,如 BOOST_PP_VARIADIC_SIZE(...)。它适用于 64 号。

在底层,它与Kornel Kisielewicz's answer基本相同。

【讨论】:

@CarloWood 确实如此。预处理器实际上并没有“零参数”的概念。我们认为的“零参数”是预处理器中的“一个空参数”。但它可以使用 C++20 的 __VA_OPT__##__VA_ARGS__ 的编译器扩展删除前面的逗号来修复,例如:godbolt.org/z/X7OvnK

以上是关于C++ 预处理器 __VA_ARGS__ 参数数量的主要内容,如果未能解决你的问题,请参考以下文章

宏定义中的#,##,...,do{}while,__VA_ARGS__

预定义宏__func___Pragma变长参数宏定义以及__VA_ARGS__

__VA_ARGS__和va_start作用, 关于log

C - 预处理命令

__VA_ARGS__可变参数宏

C语言 函数不定长参数 ##__VA_ARGS__经典案例