标准替代GCC的## __ VA_ARGS__技巧?

在C99中,可变macros的空参数有一个众所周知的 问题 。

例:

#define FOO(...) printf(__VA_ARGS__) #define BAR(fmt, ...) printf(fmt, __VA_ARGS__) FOO("this works fine"); BAR("this breaks!"); 

上面的BAR()的使用根据C99标准确实是不正确的,因为它会扩展到:

 printf("this breaks!",); 

注意尾随的逗号 – 不可行。

一些编译器(例如:Visual Studio 2010)将安静地摆脱尾随的逗号。 其他编译器(例如:GCC)支持在__VA_ARGS__放置## ,如下所示:

 #define BAR(fmt, ...) printf(fmt, ##__VA_ARGS__) 

但是有没有符合标准的方法来获得这种行为? 也许使用多个macros?

现在, ##版本似乎得到了很好的支持(至less在我的平台上),但是我更愿意使用符合标准的解决scheme。

先发制人:我知道我只能写一个小function。 我正在尝试使用macros来做到这一点。

编辑 :这是一个例子(虽然简单)为什么我想要使用BAR():

 #define BAR(fmt, ...) printf(fmt "\n", ##__VA_ARGS__) BAR("here is a log message"); BAR("here is a log message with a param: %d", 42); 

这会自动为我的BAR()日志语句添加一个换行符,假设fmt总是一个双引号的Cstring。 它不会将换行符作为单独的printf()进行打印,如果日志logging是行caching的并且是asynchronous来自多个源的,则这是有利的。

如Richard Hansen对这个问题的回答所描述的,如果你愿意接受一些你可以传递给可变参数macros的参数的硬编码上限,那么可以避免使用GCC的扩展名,##__VA_ARGS__ 。 如果你不想有任何这样的限制,但是据我所知,仅仅使用C99指定的预处理器特性是不可能的。 你必须使用一些扩展的语言。 铿锵和icc采用了这个GCC扩展,但MSVC没有。

早在2001年,我就在__VA_ARGS__ 文档中写下了GCC扩展标准化(以及相关的扩展名,可以使用不同于__VA_ARGS__的名称作为其他参数),但没有收到委员会的任何回复。 我甚至不知道有没有人读过它。 在2016年, N2023再次提出了这个build议,我鼓励任何知道这个build议将如何让我们知道的人。

有一个参数计数技巧,你可以使用。

下面是在jwd的问题中实现第二个BAR()示例的一种符合标准的方法:

 #include <stdio.h> #define BAR(...) printf(FIRST(__VA_ARGS__) "\n" REST(__VA_ARGS__)) /* expands to the first argument */ #define FIRST(...) FIRST_HELPER(__VA_ARGS__, throwaway) #define FIRST_HELPER(first, ...) first /* * if there's only one argument, expands to nothing. if there is more * than one argument, expands to a comma followed by everything but * the first argument. only supports up to 9 arguments but can be * trivially expanded. */ #define REST(...) REST_HELPER(NUM(__VA_ARGS__), __VA_ARGS__) #define REST_HELPER(qty, ...) REST_HELPER2(qty, __VA_ARGS__) #define REST_HELPER2(qty, ...) REST_HELPER_##qty(__VA_ARGS__) #define REST_HELPER_ONE(first) #define REST_HELPER_TWOORMORE(first, ...) , __VA_ARGS__ #define NUM(...) \ SELECT_10TH(__VA_ARGS__, TWOORMORE, TWOORMORE, TWOORMORE, TWOORMORE,\ TWOORMORE, TWOORMORE, TWOORMORE, TWOORMORE, ONE, throwaway) #define SELECT_10TH(a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, ...) a10 int main(int argc, char *argv[]) { BAR("first test"); BAR("second test: %s", "a string"); return 0; } 

这同样的技巧是用来:

  • 统计参数的数量
  • 根据参数数量的不同展开
  • 附加到__VA_ARGS__

说明

策略是将__VA_ARGS__分隔成第一个参数和其余部分(如果有的话)。 这使得可以在第一个参数之后但在第二个之前(如果存在)插入东西。

FIRST()

这个macros只是扩展到第一个参数,其余的都是抛弃。

实施很简单。 throwaway参数确保FIRST_HELPER()获取两个参数,这是必需的,因为...需要至less一个参数。 有一个论点,它扩展如下:

  1. FIRST(firstarg)
  2. FIRST_HELPER(firstarg, throwaway)
  3. firstarg

有两个或更多,它扩展如下:

  1. FIRST(firstarg, secondarg, thirdarg)
  2. FIRST_HELPER(firstarg, secondarg, thirdarg, throwaway)
  3. firstarg

REST()

这个macros扩展到除了第一个参数(包括第一个参数之后的逗号,如果有多个参数)之外的所有东西。

这个macros的实现要复杂得多。 一般策略是计算参数个数(一个或多个参数),然后展开为REST_HELPER_ONE() (如果只给出一个参数)或REST_HELPER_TWOORMORE() (如果给出两个或多个参数)。 REST_HELPER_ONE()简单地扩展为空 – 第一个之后没有参数,所以剩下的参数是空集。 REST_HELPER_TWOORMORE()也很简单 – 它扩展为一个逗号,除了第一个参数外,其余都是逗号。

参数使用NUM()macros进行计数。 如果只给出一个参数,则该macros扩展为ONE如果给出两个和九个参数之间的TWOORMORE ,并且如果给出10个或更多个参数(因为它扩展到第十个参数)则中断。

NUM()macros使用SELECT_10TH()macros来确定参数的数量。 顾名思义, SELECT_10TH()简单地扩展到第10个参数。 由于省略号, SELECT_10TH()需要传递至less11个参数(标准说至less有一个省略号参数)。 这就是为什么NUM()作为最后一个parameter passing一次(没有它,传递一个参数到NUM()将导致只有10个parameter passing给SELECT_10TH() ,这将违反标准)。

REST_HELPER_ONE()REST_HELPER_TWOORMORE()是通过连接REST_HELPER_REST_HELPER_TWOORMORE()中的NUM(__VA_ARGS__)的扩展来完成的。 请注意, REST_HELPER()的目的是确保NUM(__VA_ARGS__)在与REST_HELPER_连接之前完全展开。

一个论点的扩展如下:

  1. REST(firstarg)
  2. REST_HELPER(NUM(firstarg), firstarg)
  3. REST_HELPER2(SELECT_10TH(firstarg, TWOORMORE, TWOORMORE, TWOORMORE, TWOORMORE, TWOORMORE, TWOORMORE, TWOORMORE, TWOORMORE, ONE, throwaway), firstarg)
  4. REST_HELPER2(ONE, firstarg)
  5. REST_HELPER_ONE(firstarg)
  6. (空)

有两个或更多参数的扩展如下:

  1. REST(firstarg, secondarg, thirdarg)
  2. REST_HELPER(NUM(firstarg, secondarg, thirdarg), firstarg, secondarg, thirdarg)
  3. REST_HELPER2(SELECT_10TH(firstarg, secondarg, thirdarg, TWOORMORE, TWOORMORE, TWOORMORE, TWOORMORE, TWOORMORE, TWOORMORE, TWOORMORE, TWOORMORE, ONE, throwaway), firstarg, secondarg, thirdarg)
  4. REST_HELPER2(TWOORMORE, firstarg, secondarg, thirdarg)
  5. REST_HELPER_TWOORMORE(firstarg, secondarg, thirdarg)
  6. , secondarg, thirdarg

不是一个通用的解决scheme,但在printf的情况下,你可以添加一个换行符,如:

 #define BAR_HELPER(fmt, ...) printf(fmt "\n%s", __VA_ARGS__) #define BAR(...) BAR_HELPER(__VA_ARGS__, "") 

我相信它会忽略任何格式string中未引用的额外参数。 所以你甚至可以逃避:

 #define BAR_HELPER(fmt, ...) printf(fmt "\n", __VA_ARGS__) #define BAR(...) BAR_HELPER(__VA_ARGS__, 0) 

我不能相信C99被批准没有一个标准的方式来做到这一点。 AFAICT也存在C ++ 11中的问题。

有一种方法可以使用Boost.Preprocessor来处理这个特定的情况。 您可以使用BOOST_PP_VARIADIC_SIZE来检查参数列表的大小,然后有条件地扩展到另一个macros。 这样做的一个缺点是它不能区分0和1之间的争论,一旦你考虑以下事项,原因就会变得清晰:

 BOOST_PP_VARIADIC_SIZE() // expands to 1 BOOST_PP_VARIADIC_SIZE(,) // expands to 2 BOOST_PP_VARIADIC_SIZE(,,) // expands to 3 BOOST_PP_VARIADIC_SIZE(a) // expands to 1 BOOST_PP_VARIADIC_SIZE(a,) // expands to 2 BOOST_PP_VARIADIC_SIZE(,b) // expands to 2 BOOST_PP_VARIADIC_SIZE(a,b) // expands to 2 BOOST_PP_VARIADIC_SIZE(a, ,c) // expands to 3 

空的macros参数列表实际上由一个恰好为空的参数组成。

在这种情况下,我们很幸运,因为你所期望的macros至less有一个参数,我们可以把它作为两个“过载”macros来实现:

 #define BAR_0(fmt) printf(fmt "\n") #define BAR_1(fmt, ...) printf(fmt "\n", __VA_ARGS__) 

然后是另一个macros在它们之间切换,比如:

 #define BAR(...) \ BOOST_PP_CAT(BAR_, BOOST_PP_GREATER( BOOST_PP_VARIADIC_SIZE(__VA_ARGS__), 1))(__VA_ARGS__) \ /**/ 

要么

 #define BAR(...) BOOST_PP_IIF( \ BOOST_PP_GREATER(BOOST_PP_VARIADIC_SIZE(__VA_ARGS__), 1), \ BAR_1, BAR_0)(__VA_ARGS__) \ /**/ 

无论你发现更可读(我更喜欢第一,因为它给你一个通用的forms来重载macros参数的数量)。

也可以通过访问和variablesvariables参数列表来完成这个工作,但是它的可读性较差,而且这个问题非常具体:

 #define BAR(...) printf( \ BOOST_PP_VARIADIC_ELEM(0, __VA_ARGS__) "\n" \ BOOST_PP_COMMA_IF( \ BOOST_PP_GREATER(BOOST_PP_VARIADIC_SIZE(__VA_ARGS__), 1)) \ BOOST_PP_ARRAY_ENUM(BOOST_PP_ARRAY_POP_FRONT( \ BOOST_PP_VARIADIC_TO_ARRAY(__VA_ARGS__)))) \ /**/ 

另外,为什么没有BOOST_PP_ARRAY_ENUM_TRAILING? 这将使这个解决scheme更不可怕。

编辑:好的,这里是一个BOOST_PP_ARRAY_ENUM_TRAILING,和一个使用它的版本(这是我现在最喜欢的解决scheme):

 #define BOOST_PP_ARRAY_ENUM_TRAILING(array) \ BOOST_PP_COMMA_IF(BOOST_PP_ARRAY_SIZE(array)) BOOST_PP_ARRAY_ENUM(array) \ /**/ #define BAR(...) printf( \ BOOST_PP_VARIADIC_ELEM(0, __VA_ARGS__) "\n" \ BOOST_PP_ARRAY_ENUM_TRAILING(BOOST_PP_ARRAY_POP_FRONT( \ BOOST_PP_VARIADIC_TO_ARRAY(__VA_ARGS__)))) \ /**/ 

最近我碰到类似的问题,我相信有一个解决scheme。

关键的想法是,有一种方法可以编写一个macrosNUM_ARGS来计算可变macros的参数个数。 您可以使用NUM_ARGS的变体来构buildNUM_ARGS_CEILING2 ,它可以告诉您可变macros是否被赋予1个参数或2个或更多个参数。 然后你可以编写你的Barmacros,以便它使用NUM_ARGS_CEILING2CONCAT将它的参数发送给两个助手macros之一:一个需要一个参数,另一个需要大于1的可变参数。

下面是一个例子,我用这个技巧来写macrosUNIMPLEMENTED ,它和BAR非常相似:

步骤1:

 /** * A variadic macro which counts the number of arguments which it is * passed. Or, more precisely, it counts the number of commas which it is * passed, plus one. * * Danger: It can't count higher than 20. If it's given 0 arguments, then it * will evaluate to 1, rather than to 0. */ #define NUM_ARGS(...) \ NUM_ARGS_COUNTER(__VA_ARGS__, 20, 19, 18, 17, 16, 15, 14, 13, \ 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1) #define NUM_ARGS_COUNTER(a1, a2, a3, a4, a5, a6, a7, \ a8, a9, a10, a11, a12, a13, \ a14, a15, a16, a17, a18, a19, a20, \ N, ...) \ N 

步骤1.5:

 /* * A variant of NUM_ARGS that evaluates to 1 if given 1 or 0 args, or * evaluates to 2 if given more than 1 arg. Behavior is nasty and undefined if * it's given more than 20 args. */ #define NUM_ARGS_CEIL2(...) \ NUM_ARGS_COUNTER(__VA_ARGS__, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, \ 2, 2, 2, 2, 2, 2, 2, 1) 

第2步:

 #define _UNIMPLEMENTED1(msg) \ log("My creator has forsaken me. %s:%s:%d." msg, __FILE__, \ __func__, __LINE__) #define _UNIMPLEMENTED2(msg, ...) \ log("My creator has forsaken me. %s:%s:%d." msg, __FILE__, \ __func__, __LINE__, __VA_ARGS__) 

第3步:

 #define UNIMPLEMENTED(...) \ CONCAT(_UNIMPLEMENTED, NUM_ARGS_CEIL2(__VA_ARGS__))(__VA_ARGS__) 

CONCAT以通常的方式实施。 作为一个简短的提示,如果上面的内容看起来很混乱:CONCAT的目标就是扩展到另一个macros“呼叫”。

请注意,NUM_ARGS本身不被使用。 我只是把它包括在这里来说明这里的基本技巧。 请参阅Jens Gustedt的P99博客 ,对其进行很好的处理。

两个注释:

  • NUM_ARGS在处理的参数数量上受到限制。 矿井只能处理20个,尽pipe数量完全是任意的。

  • 如图所示,NUM_ARGS有一个缺陷,即在给出0个参数时返回1。 其要点在于,NUM_ARGS在技术上计数[逗号+ 1],而不是参数。 在这个特殊情况下,它实际上对我们有利。 _UNIMPLEMENTED1将处理一个空的标记就好了,它使我们不必编写_UNIMPLEMENTED0。 Gustedt也有一个解决方法,虽然我没有使用它,我不知道这是否会为我们在这里做的工作。

标准的解决scheme是使用FOO而不是BAR 。 有一些很奇怪的情况,它可能对你没有帮助(虽然我敢打赌,有人可以用巧妙的方式来拆卸和重新组装__VA_ARGS__ ,但是一般情况下使用FOO “通常“只是工作。