标准替代GCC的##__VA_ARGS__技巧?

时间:2022-11-25 10:06:31

There is a well-known problem with empty args for variadic macros in C99.

C99中有一个众所周知的问题,即对于可变的宏来说,空的args存在问题。

example:

例子:

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

FOO("this works fine");
BAR("this breaks!");

The use of BAR() above is indeed incorrect according to the C99 standard, since it will expand to:

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

printf("this breaks!",);

Note the trailing comma - not workable.

注意后面的逗号——不可用。

Some compilers (eg: Visual Studio 2010) will quietly get rid of that trailing comma for you. Other compilers (eg: GCC) support putting ## in front of __VA_ARGS__, like so:

一些编译器(例如:Visual Studio 2010)会悄悄地为您删除后面的逗号。其他编译器(如:GCC)支持在__VA_ARGS__前面添加##,比如:

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

But is there a standards-compliant way to get this behavior? Perhaps using multiple macros?

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

Right now, the ## version seems fairly well-supported (at least on my platforms), but I'd really rather use a standards-compliant solution.

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

Pre-emptive: I know I could just write a small function. I'm trying to do this using macros.

先发制人:我知道我可以写一个小函数。我试着用宏来做这个。

Edit: Here is an example (though simple) of why I would want to use BAR():

编辑:这里有一个例子(尽管很简单),为什么我想使用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);

This automatically adds a newline to my BAR() logging statements, assuming fmt is always a double-quoted C-string. It does NOT print the newline as a separate printf(), which is advantageous if the logging is line-buffered and coming from multiple sources asynchronously.

假设fmt始终是一个双引号的C-string,那么这将自动向BAR()日志语句添加一个换行符。它不以单独的printf()形式打印换行符,如果日志记录是行缓冲的,并且异步地来自多个源,这是有利的。

7 个解决方案

#1


49  

It is possible to avoid the use of GCC's ,##__VA_ARGS__ extension if you are willing to accept some hardcoded upper limit on the number of arguments you can pass to your variadic macro, as described in Richard Hansen's answer to this question. If you do not want to have any such limit, however, to the best of my knowledge it is not possible using only C99-specified preprocessor features; you must use some extension to the language. clang and icc have adopted this GCC extension, but MSVC has not.

如果您愿意接受可以传递给变量宏的参数数量的硬编码上限,那么可以避免使用GCC的#__VA_ARGS__扩展,正如Richard Hansen在回答这个问题时所描述的那样。但是,如果您不希望有任何这样的限制,就我所知,仅使用c99指定的预处理器特性是不可能的;你必须对语言做一些扩展。clang和icc已经采用了这个GCC扩展,但是MSVC没有。

Back in 2001 I wrote up the GCC extension for standardization (and the related extension that lets you use a name other than __VA_ARGS__ for the rest-parameter) in document N976, but that received no response whatsoever from the committee; I don't even know if anyone read it. In 2016 it was proposed again in N2023, and I encourage anyone who knows how that proposal is going to let us know in the comments.

早在2001年,我就在N976号文件中编写了GCC扩展以实现标准化(以及相关的扩展,允许您使用除__VA_ARGS__之外的名称作为rest-参数),但是没有得到委员会的任何响应;我甚至不知道有没有人读过。2016年,它在N2023年再次被提出,我鼓励任何知道这个建议将如何在评论中告诉我们的人。

#2


98  

There is an argument counting trick that you can use.

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

Here is one standard-compliant way to implement the second BAR() example in jwd's question:

下面是实现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;
}

This same trick is used to:

同样的技巧也适用于:

Explanation

The strategy is to separate __VA_ARGS__ into the first argument and the rest (if any). This makes it possible to insert stuff after the first argument but before the second (if present).

策略是将__VA_ARGS__分成第一个参数和其他参数(如果有的话)。这使得在第一个参数之后插入内容成为可能,但是在第二个参数之前插入内容(如果存在)。

FIRST()

This macro simply expands to the first argument, discarding the rest.

这个宏简单地扩展到第一个参数,去掉其余的。

The implementation is straightforward. The throwaway argument ensures that FIRST_HELPER() gets two arguments, which is required because the ... needs at least one. With one argument, it expands as follows:

实现比较简单。抛出的参数确保FIRST_HELPER()得到两个参数,这是必需的,因为…需要至少一个。有一个论点,它的扩展如下:

  1. FIRST(firstarg)
  2. 第一个(firstarg)
  3. FIRST_HELPER(firstarg, throwaway)
  4. FIRST_HELPER(firstarg一次性)
  5. firstarg
  6. firstarg

With two or more, it expands as follows:

有两个或两个以上,它的扩展如下:

  1. FIRST(firstarg, secondarg, thirdarg)
  2. 第一个(firstarg secondarg thirdarg)
  3. FIRST_HELPER(firstarg, secondarg, thirdarg, throwaway)
  4. FIRST_HELPER(firstarg secondarg thirdarg,脱口而出的)
  5. firstarg
  6. firstarg

REST()

This macro expands to everything but the first argument (including the comma after the first argument, if there is more than one argument).

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

The implementation of this macro is far more complicated. The general strategy is to count the number of arguments (one or more than one) and then expand to either REST_HELPER_ONE() (if only one argument given) or REST_HELPER_TWOORMORE() (if two or more arguments given). REST_HELPER_ONE() simply expands to nothing -- there are no arguments after the first, so the remaining arguments is the empty set. REST_HELPER_TWOORMORE() is also straightforward -- it expands to a comma followed by everything except the first argument.

这个宏的实现要复杂得多。一般的策略是计算参数的数量(一个或多个),然后扩展到REST_HELPER_ONE()(如果只给出一个参数)或REST_HELPER_TWOORMORE()(如果给出两个或多个参数)。REST_HELPER_ONE()只是扩展到什么都没有——在第一个参数之后没有参数,所以剩下的参数是空集。REST_HELPER_TWOORMORE()也很简单——它扩展到一个逗号,后面除了第一个参数以外的所有内容。

The arguments are counted using the NUM() macro. This macro expands to ONE if only one argument is given, TWOORMORE if between two and nine arguments are given, and breaks if 10 or more arguments are given (because it expands to the 10th argument).

使用NUM()宏计算参数。如果只给出一个参数,这个宏就会扩展到一个,如果给出两个或九个参数,就会给出两个或两个以上的参数,如果给出了10个或更多的参数(因为它扩展到第10个参数),那么就会中断。

The NUM() macro uses the SELECT_10TH() macro to determine the number of arguments. As its name implies, SELECT_10TH() simply expands to its 10th argument. Because of the ellipsis, SELECT_10TH() needs to be passed at least 11 arguments (the standard says that there must be at least one argument for the ellipsis). This is why NUM() passes throwaway as the last argument (without it, passing one argument to NUM() would result in only 10 arguments being passed to SELECT_10TH(), which would violate the standard).

NUM()宏使用select_10()宏来确定参数的数量。顾名思义,select_10()只是扩展到它的第10个参数。由于省略号的原因,select_10()至少需要传递11个参数(标准要求省略号必须至少有一个参数)。这就是NUM()作为最后一个参数传递的原因(没有它,将一个参数传递给NUM()只会导致将10个参数传递给select_10(),这违反了标准)。

Selection of either REST_HELPER_ONE() or REST_HELPER_TWOORMORE() is done by concatenating REST_HELPER_ with the expansion of NUM(__VA_ARGS__) in REST_HELPER2(). Note that the purpose of REST_HELPER() is to ensure that NUM(__VA_ARGS__) is fully expanded before being concatenated with REST_HELPER_.

REST_HELPER_ONE()或REST_HELPER_TWOORMORE()的选择是通过在REST_HELPER2()中扩展NUM(__VA_ARGS__)连接REST_HELPER_ helper_。注意,REST_HELPER()的目的是确保NUM(__VA_ARGS__)在与REST_HELPER_连接之前得到了充分的扩展。

Expansion with one argument goes as follows:

一个论点的展开如下:

  1. REST(firstarg)
  2. 其他(firstarg)
  3. REST_HELPER(NUM(firstarg), firstarg)
  4. REST_HELPER(NUM(firstarg)、firstarg)
  5. REST_HELPER2(SELECT_10TH(firstarg, TWOORMORE, TWOORMORE, TWOORMORE, TWOORMORE, TWOORMORE, TWOORMORE, TWOORMORE, TWOORMORE, ONE, throwaway), firstarg)
  6. REST_HELPER2(select_10 (firstarg, TWOORMORE, TWOORMORE, TWOORMORE, TWOORMORE, TWOORMORE, TWOORMORE, TWOORMORE, TWOORMORE, TWOORMORE, TWOORMORE, ONE, throwaway), firstarg)
  7. REST_HELPER2(ONE, firstarg)
  8. firstarg REST_HELPER2(一)
  9. REST_HELPER_ONE(firstarg)
  10. REST_HELPER_ONE(firstarg)
  11. (empty)
  12. (空的)

Expansion with two or more arguments goes as follows:

用两个或多个参数展开如下:

  1. REST(firstarg, secondarg, thirdarg)
  2. 其他(firstarg secondarg thirdarg)
  3. REST_HELPER(NUM(firstarg, secondarg, thirdarg), firstarg, secondarg, thirdarg)
  4. REST_HELPER(NUM(firstarg, secondarg, thirdarg), firstarg, secondarg, thirdarg)
  5. REST_HELPER2(SELECT_10TH(firstarg, secondarg, thirdarg, TWOORMORE, TWOORMORE, TWOORMORE, TWOORMORE, TWOORMORE, TWOORMORE, TWOORMORE, TWOORMORE, ONE, throwaway), firstarg, secondarg, thirdarg)
  6. REST_HELPER2(select_10 (firstarg, secondarg, thirdarg, TWOORMORE, TWOORMORE, TWOORMORE, TWOORMORE, TWOORMORE, TWOORMORE, TWOORMORE, TWOORMORE, TWOORMORE, TWOORMORE, ONE, throwaway), firstarg, second darg, thirdarg, thirdarg, thirdarg)
  7. REST_HELPER2(TWOORMORE, firstarg, secondarg, thirdarg)
  8. REST_HELPER2(TWOORMORE firstarg、secondarg thirdarg)
  9. REST_HELPER_TWOORMORE(firstarg, secondarg, thirdarg)
  10. REST_HELPER_TWOORMORE(firstarg secondarg thirdarg)
  11. , secondarg, thirdarg
  12. 、secondarg thirdarg

#3


15  

Not a general solution, but in the case of printf you could append a newline like:

不是一般的解决方案,但如果是printf,可以添加如下的换行:

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

I believe it ignores any extra args that aren't referenced in the format string. So you could probably even get away with:

我认为它忽略了格式字符串中没有引用的任何额外的args。所以你甚至可以这样做:

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

I can't believe C99 was approved without a standard way to do this. AFAICT the problem exists in C++11 too.

如果没有一个标准的方法,我无法相信C99被批准了。这个问题也存在于c++ 11中。

#4


9  

There is a way to handle this specific case using something like Boost.Preprocessor. You can use BOOST_PP_VARIADIC_SIZE to check the size of the argument list, and then conditionaly expand to another macro. The one shortcoming of this is that it can't distinguish between 0 and 1 argument, and the reason for this becomes clear once you consider the following:

有一种方法可以使用boot . preprocessor这样的东西来处理这种特殊情况。您可以使用boost_pp_variradic_size来检查参数列表的大小,然后将其扩展到另一个宏。它的一个缺点是它不能区分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

The empty macro argument list actually consists of one argument that happens to be empty.

空宏参数列表实际上由一个碰巧为空的参数组成。

In this case, we are lucky since your desired macro always has at least 1 argument, we can implement it as two "overload" macros:

在这种情况下,我们很幸运,因为所需的宏总是至少有一个参数,所以我们可以将其实现为两个“过载”宏:

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

And then another macro to switch between them, such as:

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

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

or

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

Whichever you find more readable (I prefer the first as it gives you a general form for overloading macros on the number of arguments).

无论您发现哪个更容易读(我更喜欢第一个,因为它给了您一个一般的格式,用于重载参数数量上的宏)。

It is also possible to do this with a single macro by accessing and mutating the variable arguments list, but it is way less readable, and is very specific to this problem:

通过访问和修改变量参数列表,也可以使用单个宏来实现这一点,但它的可读性较低,而且针对这个问题非常具体:

#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__)))) \
    /**/

Also, why is there no BOOST_PP_ARRAY_ENUM_TRAILING? It would make this solution much less horrible.

另外,为什么没有boost_pp_array_enum_tail ?它会让这个解变得不那么可怕。

Edit: Alright, here is a BOOST_PP_ARRAY_ENUM_TRAILING, and a version that uses it (this is now my favourite solution):

编辑:好的,这里有一个boost_pp_array_enum_tail,以及一个使用它的版本(这是我现在最喜欢的解决方案):

#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__)))) \
    /**/

#5


3  

I ran into a similar problem recently, and I do believe there's a solution.

我最近遇到了一个类似的问题,我相信有解决办法。

The key idea is that there's a way to write a macro NUM_ARGS to count the number of arguments which a variadic macro is given. You can use a variation of NUM_ARGS to build NUM_ARGS_CEILING2, which can tell you whether a variadic macro is given 1 argument or 2-or-more arguments. Then you can write your Bar macro so that it uses NUM_ARGS_CEILING2 and CONCAT to send its arguments to one of two helper macros: one which expects exactly 1 argument, and another which expects a variable number of arguments greater than 1.

关键思想是,有一种方法可以编写宏NUM_ARGS来计算变量宏给出的参数数量。您可以使用NUM_ARGS的变体来构建num_args_ing2,它可以告诉您一个变量宏是给定一个参数还是两个或更多的参数。然后,您可以编写Bar宏,以便它使用num_args_ing2和CONCAT将其参数发送给两个辅助宏中的一个:一个只需要一个参数,另一个参数的可变数量大于1。

Here's an example where I use this trick to write the macro UNIMPLEMENTED, which is very similar to BAR:

这里有一个例子,我使用这个技巧来编写未实现的宏,它与BAR非常相似:

STEP 1:

步骤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

STEP 1.5:

步骤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)

Step 2:

步骤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__)

STEP 3:

步骤3:

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

Where CONCAT is implemented in the usual way. As a quick hint, if the above seems confusing: the goal of CONCAT there is to expand to another macro "call".

CONCAT以通常的方式实现。作为一个简短的提示,如果上面的内容看起来令人困惑:CONCAT的目标是扩展到另一个宏“call”。

Note that NUM_ARGS itself isn't used. I just included it to illustrate the basic trick here. See Jens Gustedt's P99 blog for a nice treatment of it.

注意,NUM_ARGS本身没有使用。我只是用它来说明这里的基本技巧。请参阅Jens Gustedt的P99博客,以获得更好的处理。

Two notes:

两个笔记:

  • NUM_ARGS is limited in the number of arguments that it handles. Mine can only handle up to 20, although the number is totally arbitrary.

    NUM_ARGS处理的参数数量有限。我的只能处理20个,尽管这个数字是完全任意的。

  • NUM_ARGS, as shown, has a pitfall in that it returns 1 when given 0 arguments. The gist of it is that NUM_ARGS is technically counting [commas + 1], and not args. In this particular case, it actually works to our advantage. _UNIMPLEMENTED1 will handle an empty token just fine and it saves us from having to write _UNIMPLEMENTED0. Gustedt has a workaround for that as well, although I haven't used it and I'm not sure if it would work for what we're doing here.

    如图所示,NUM_ARGS在给定0参数时返回1。它的要点是,NUM_ARGS在技术上是计数[commas + 1],而不是args。在这种情况下,它实际上对我们有利。_UNIMPLEMENTED1可以很好地处理一个空的令牌,这样我们就不必写_UNIMPLEMENTED0。Gustedt也有一个变通方法,尽管我还没有使用它,我也不确定它是否适用于我们在这里做的事情。

#6


0  

This is the simplified version that I use. It is based upon the great techniques of the other answers here, so many props to them:

这是我使用的简化版本。它是建立在其他答案的伟大技术之上的,有那么多的支撑:

#define _SELECT(PREFIX,_5,_4,_3,_2,_1,SUFFIX,...) PREFIX ## _ ## SUFFIX

#define _BAR_1(fmt)      printf(fmt "\n")
#define _BAR_N(fmt, ...) printf(fmt "\n", __VA_ARGS__);
#define BAR(...) _SELECT(_BAR,__VA_ARGS__,N,N,N,N,1)(__VA_ARGS__)

int main(int argc, char *argv[]) {
    BAR("here is a log message");
    BAR("here is a log message with a param: %d", 42);
    return 0;
}

That's it.

就是这样。

As with other solutions this is limited to the number of arguments the macro. To support more, add more parameters to _SELECT, and more N arguments. The argument names count down (instead of up) to serve as a reminder that the count-based SUFFIX argument is provided in reverse order.

与其他解决方案一样,这仅限于宏的参数数量。要支持更多,请添加更多的参数到_SELECT,以及更多的N参数。参数名称倒数(而不是向上)以提醒您基于可数的后缀参数是按相反顺序提供的。

This solution treats 0 arguments as though it is 1 argument. So BAR() nominally "works", because it expands to _SELECT(_BAR,,N,N,N,N,1)(), which expands to _BAR_1()(), which expands to printf("\n").

这个解将0个参数视为1个参数。BAR()名义上“工作”,因为它扩展到_SELECT(_BAR, N,N,N,N, N,1)(),扩展到_BAR_1()(),扩展到printf(“\ N”)。

If you want, you can get creative with the use of _SELECT and provide different macros for different number of arguments. For example, here we have a LOG macro that takes a 'level' argument before the format. If format is missing, it logs "(no message)", if there is just 1 argument, it will log it through "%s", otherwise it will treat the format argument as a printf format string for the remaining arguments.

如果需要,可以使用_SELECT,并为不同数量的参数提供不同的宏。例如,在这里我们有一个日志宏,它在格式之前采用“level”参数。如果缺少格式,则记录“(无消息)”,如果只有一个参数,它将通过“%s”记录它,否则它将将把格式参数作为剩余参数的printf格式字符串处理。

#define _LOG_1(lvl)          printf("[%s] (no message)\n", #lvl)
#define _LOG_2(lvl,fmt)      printf("[%s] %s\n", #lvl, fmt)
#define _LOG_N(lvl,fmt, ...) printf("[%s] " fmt "\n", #lvl, __VA_ARGS__)
#define LOG(...) _SELECT(_LOG,__VA_ARGS__,N,N,N,2,1)(__VA_ARGS__)

int main(int argc, char *argv[]) {
    LOG(INFO);
    LOG(DEBUG, "here is a log message");
    LOG(WARN, "here is a log message with param: %d", 42);
    return 0;
}
/* outputs:
[INFO] (no message)
[DEBUG] here is a log message
[WARN] here is a log message with param: 42
*/

#7


-2  

The standard solution is to use FOO instead of BAR. There are a few weird cases of argument reordering it probably can't do for you (though I bet someone can come up with clever hacks to disassemble and reassemble __VA_ARGS__ conditionally based on the number of arguments in it!) but in general using FOO "usually" just works.

标准的解决方案是使用FOO而不是BAR。有一些奇怪的参数重新排序的情况可能对您没有帮助(尽管我打赌有人可以想出聪明的方法,根据其中的参数数量有条件地分解和重新组装__VA_ARGS__ !),但是一般使用FOO“通常”就可以了。

#1


49  

It is possible to avoid the use of GCC's ,##__VA_ARGS__ extension if you are willing to accept some hardcoded upper limit on the number of arguments you can pass to your variadic macro, as described in Richard Hansen's answer to this question. If you do not want to have any such limit, however, to the best of my knowledge it is not possible using only C99-specified preprocessor features; you must use some extension to the language. clang and icc have adopted this GCC extension, but MSVC has not.

如果您愿意接受可以传递给变量宏的参数数量的硬编码上限,那么可以避免使用GCC的#__VA_ARGS__扩展,正如Richard Hansen在回答这个问题时所描述的那样。但是,如果您不希望有任何这样的限制,就我所知,仅使用c99指定的预处理器特性是不可能的;你必须对语言做一些扩展。clang和icc已经采用了这个GCC扩展,但是MSVC没有。

Back in 2001 I wrote up the GCC extension for standardization (and the related extension that lets you use a name other than __VA_ARGS__ for the rest-parameter) in document N976, but that received no response whatsoever from the committee; I don't even know if anyone read it. In 2016 it was proposed again in N2023, and I encourage anyone who knows how that proposal is going to let us know in the comments.

早在2001年,我就在N976号文件中编写了GCC扩展以实现标准化(以及相关的扩展,允许您使用除__VA_ARGS__之外的名称作为rest-参数),但是没有得到委员会的任何响应;我甚至不知道有没有人读过。2016年,它在N2023年再次被提出,我鼓励任何知道这个建议将如何在评论中告诉我们的人。

#2


98  

There is an argument counting trick that you can use.

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

Here is one standard-compliant way to implement the second BAR() example in jwd's question:

下面是实现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;
}

This same trick is used to:

同样的技巧也适用于:

Explanation

The strategy is to separate __VA_ARGS__ into the first argument and the rest (if any). This makes it possible to insert stuff after the first argument but before the second (if present).

策略是将__VA_ARGS__分成第一个参数和其他参数(如果有的话)。这使得在第一个参数之后插入内容成为可能,但是在第二个参数之前插入内容(如果存在)。

FIRST()

This macro simply expands to the first argument, discarding the rest.

这个宏简单地扩展到第一个参数,去掉其余的。

The implementation is straightforward. The throwaway argument ensures that FIRST_HELPER() gets two arguments, which is required because the ... needs at least one. With one argument, it expands as follows:

实现比较简单。抛出的参数确保FIRST_HELPER()得到两个参数,这是必需的,因为…需要至少一个。有一个论点,它的扩展如下:

  1. FIRST(firstarg)
  2. 第一个(firstarg)
  3. FIRST_HELPER(firstarg, throwaway)
  4. FIRST_HELPER(firstarg一次性)
  5. firstarg
  6. firstarg

With two or more, it expands as follows:

有两个或两个以上,它的扩展如下:

  1. FIRST(firstarg, secondarg, thirdarg)
  2. 第一个(firstarg secondarg thirdarg)
  3. FIRST_HELPER(firstarg, secondarg, thirdarg, throwaway)
  4. FIRST_HELPER(firstarg secondarg thirdarg,脱口而出的)
  5. firstarg
  6. firstarg

REST()

This macro expands to everything but the first argument (including the comma after the first argument, if there is more than one argument).

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

The implementation of this macro is far more complicated. The general strategy is to count the number of arguments (one or more than one) and then expand to either REST_HELPER_ONE() (if only one argument given) or REST_HELPER_TWOORMORE() (if two or more arguments given). REST_HELPER_ONE() simply expands to nothing -- there are no arguments after the first, so the remaining arguments is the empty set. REST_HELPER_TWOORMORE() is also straightforward -- it expands to a comma followed by everything except the first argument.

这个宏的实现要复杂得多。一般的策略是计算参数的数量(一个或多个),然后扩展到REST_HELPER_ONE()(如果只给出一个参数)或REST_HELPER_TWOORMORE()(如果给出两个或多个参数)。REST_HELPER_ONE()只是扩展到什么都没有——在第一个参数之后没有参数,所以剩下的参数是空集。REST_HELPER_TWOORMORE()也很简单——它扩展到一个逗号,后面除了第一个参数以外的所有内容。

The arguments are counted using the NUM() macro. This macro expands to ONE if only one argument is given, TWOORMORE if between two and nine arguments are given, and breaks if 10 or more arguments are given (because it expands to the 10th argument).

使用NUM()宏计算参数。如果只给出一个参数,这个宏就会扩展到一个,如果给出两个或九个参数,就会给出两个或两个以上的参数,如果给出了10个或更多的参数(因为它扩展到第10个参数),那么就会中断。

The NUM() macro uses the SELECT_10TH() macro to determine the number of arguments. As its name implies, SELECT_10TH() simply expands to its 10th argument. Because of the ellipsis, SELECT_10TH() needs to be passed at least 11 arguments (the standard says that there must be at least one argument for the ellipsis). This is why NUM() passes throwaway as the last argument (without it, passing one argument to NUM() would result in only 10 arguments being passed to SELECT_10TH(), which would violate the standard).

NUM()宏使用select_10()宏来确定参数的数量。顾名思义,select_10()只是扩展到它的第10个参数。由于省略号的原因,select_10()至少需要传递11个参数(标准要求省略号必须至少有一个参数)。这就是NUM()作为最后一个参数传递的原因(没有它,将一个参数传递给NUM()只会导致将10个参数传递给select_10(),这违反了标准)。

Selection of either REST_HELPER_ONE() or REST_HELPER_TWOORMORE() is done by concatenating REST_HELPER_ with the expansion of NUM(__VA_ARGS__) in REST_HELPER2(). Note that the purpose of REST_HELPER() is to ensure that NUM(__VA_ARGS__) is fully expanded before being concatenated with REST_HELPER_.

REST_HELPER_ONE()或REST_HELPER_TWOORMORE()的选择是通过在REST_HELPER2()中扩展NUM(__VA_ARGS__)连接REST_HELPER_ helper_。注意,REST_HELPER()的目的是确保NUM(__VA_ARGS__)在与REST_HELPER_连接之前得到了充分的扩展。

Expansion with one argument goes as follows:

一个论点的展开如下:

  1. REST(firstarg)
  2. 其他(firstarg)
  3. REST_HELPER(NUM(firstarg), firstarg)
  4. REST_HELPER(NUM(firstarg)、firstarg)
  5. REST_HELPER2(SELECT_10TH(firstarg, TWOORMORE, TWOORMORE, TWOORMORE, TWOORMORE, TWOORMORE, TWOORMORE, TWOORMORE, TWOORMORE, ONE, throwaway), firstarg)
  6. REST_HELPER2(select_10 (firstarg, TWOORMORE, TWOORMORE, TWOORMORE, TWOORMORE, TWOORMORE, TWOORMORE, TWOORMORE, TWOORMORE, TWOORMORE, TWOORMORE, ONE, throwaway), firstarg)
  7. REST_HELPER2(ONE, firstarg)
  8. firstarg REST_HELPER2(一)
  9. REST_HELPER_ONE(firstarg)
  10. REST_HELPER_ONE(firstarg)
  11. (empty)
  12. (空的)

Expansion with two or more arguments goes as follows:

用两个或多个参数展开如下:

  1. REST(firstarg, secondarg, thirdarg)
  2. 其他(firstarg secondarg thirdarg)
  3. REST_HELPER(NUM(firstarg, secondarg, thirdarg), firstarg, secondarg, thirdarg)
  4. REST_HELPER(NUM(firstarg, secondarg, thirdarg), firstarg, secondarg, thirdarg)
  5. REST_HELPER2(SELECT_10TH(firstarg, secondarg, thirdarg, TWOORMORE, TWOORMORE, TWOORMORE, TWOORMORE, TWOORMORE, TWOORMORE, TWOORMORE, TWOORMORE, ONE, throwaway), firstarg, secondarg, thirdarg)
  6. REST_HELPER2(select_10 (firstarg, secondarg, thirdarg, TWOORMORE, TWOORMORE, TWOORMORE, TWOORMORE, TWOORMORE, TWOORMORE, TWOORMORE, TWOORMORE, TWOORMORE, TWOORMORE, ONE, throwaway), firstarg, second darg, thirdarg, thirdarg, thirdarg)
  7. REST_HELPER2(TWOORMORE, firstarg, secondarg, thirdarg)
  8. REST_HELPER2(TWOORMORE firstarg、secondarg thirdarg)
  9. REST_HELPER_TWOORMORE(firstarg, secondarg, thirdarg)
  10. REST_HELPER_TWOORMORE(firstarg secondarg thirdarg)
  11. , secondarg, thirdarg
  12. 、secondarg thirdarg

#3


15  

Not a general solution, but in the case of printf you could append a newline like:

不是一般的解决方案,但如果是printf,可以添加如下的换行:

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

I believe it ignores any extra args that aren't referenced in the format string. So you could probably even get away with:

我认为它忽略了格式字符串中没有引用的任何额外的args。所以你甚至可以这样做:

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

I can't believe C99 was approved without a standard way to do this. AFAICT the problem exists in C++11 too.

如果没有一个标准的方法,我无法相信C99被批准了。这个问题也存在于c++ 11中。

#4


9  

There is a way to handle this specific case using something like Boost.Preprocessor. You can use BOOST_PP_VARIADIC_SIZE to check the size of the argument list, and then conditionaly expand to another macro. The one shortcoming of this is that it can't distinguish between 0 and 1 argument, and the reason for this becomes clear once you consider the following:

有一种方法可以使用boot . preprocessor这样的东西来处理这种特殊情况。您可以使用boost_pp_variradic_size来检查参数列表的大小,然后将其扩展到另一个宏。它的一个缺点是它不能区分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

The empty macro argument list actually consists of one argument that happens to be empty.

空宏参数列表实际上由一个碰巧为空的参数组成。

In this case, we are lucky since your desired macro always has at least 1 argument, we can implement it as two "overload" macros:

在这种情况下,我们很幸运,因为所需的宏总是至少有一个参数,所以我们可以将其实现为两个“过载”宏:

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

And then another macro to switch between them, such as:

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

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

or

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

Whichever you find more readable (I prefer the first as it gives you a general form for overloading macros on the number of arguments).

无论您发现哪个更容易读(我更喜欢第一个,因为它给了您一个一般的格式,用于重载参数数量上的宏)。

It is also possible to do this with a single macro by accessing and mutating the variable arguments list, but it is way less readable, and is very specific to this problem:

通过访问和修改变量参数列表,也可以使用单个宏来实现这一点,但它的可读性较低,而且针对这个问题非常具体:

#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__)))) \
    /**/

Also, why is there no BOOST_PP_ARRAY_ENUM_TRAILING? It would make this solution much less horrible.

另外,为什么没有boost_pp_array_enum_tail ?它会让这个解变得不那么可怕。

Edit: Alright, here is a BOOST_PP_ARRAY_ENUM_TRAILING, and a version that uses it (this is now my favourite solution):

编辑:好的,这里有一个boost_pp_array_enum_tail,以及一个使用它的版本(这是我现在最喜欢的解决方案):

#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__)))) \
    /**/

#5


3  

I ran into a similar problem recently, and I do believe there's a solution.

我最近遇到了一个类似的问题,我相信有解决办法。

The key idea is that there's a way to write a macro NUM_ARGS to count the number of arguments which a variadic macro is given. You can use a variation of NUM_ARGS to build NUM_ARGS_CEILING2, which can tell you whether a variadic macro is given 1 argument or 2-or-more arguments. Then you can write your Bar macro so that it uses NUM_ARGS_CEILING2 and CONCAT to send its arguments to one of two helper macros: one which expects exactly 1 argument, and another which expects a variable number of arguments greater than 1.

关键思想是,有一种方法可以编写宏NUM_ARGS来计算变量宏给出的参数数量。您可以使用NUM_ARGS的变体来构建num_args_ing2,它可以告诉您一个变量宏是给定一个参数还是两个或更多的参数。然后,您可以编写Bar宏,以便它使用num_args_ing2和CONCAT将其参数发送给两个辅助宏中的一个:一个只需要一个参数,另一个参数的可变数量大于1。

Here's an example where I use this trick to write the macro UNIMPLEMENTED, which is very similar to BAR:

这里有一个例子,我使用这个技巧来编写未实现的宏,它与BAR非常相似:

STEP 1:

步骤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

STEP 1.5:

步骤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)

Step 2:

步骤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__)

STEP 3:

步骤3:

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

Where CONCAT is implemented in the usual way. As a quick hint, if the above seems confusing: the goal of CONCAT there is to expand to another macro "call".

CONCAT以通常的方式实现。作为一个简短的提示,如果上面的内容看起来令人困惑:CONCAT的目标是扩展到另一个宏“call”。

Note that NUM_ARGS itself isn't used. I just included it to illustrate the basic trick here. See Jens Gustedt's P99 blog for a nice treatment of it.

注意,NUM_ARGS本身没有使用。我只是用它来说明这里的基本技巧。请参阅Jens Gustedt的P99博客,以获得更好的处理。

Two notes:

两个笔记:

  • NUM_ARGS is limited in the number of arguments that it handles. Mine can only handle up to 20, although the number is totally arbitrary.

    NUM_ARGS处理的参数数量有限。我的只能处理20个,尽管这个数字是完全任意的。

  • NUM_ARGS, as shown, has a pitfall in that it returns 1 when given 0 arguments. The gist of it is that NUM_ARGS is technically counting [commas + 1], and not args. In this particular case, it actually works to our advantage. _UNIMPLEMENTED1 will handle an empty token just fine and it saves us from having to write _UNIMPLEMENTED0. Gustedt has a workaround for that as well, although I haven't used it and I'm not sure if it would work for what we're doing here.

    如图所示,NUM_ARGS在给定0参数时返回1。它的要点是,NUM_ARGS在技术上是计数[commas + 1],而不是args。在这种情况下,它实际上对我们有利。_UNIMPLEMENTED1可以很好地处理一个空的令牌,这样我们就不必写_UNIMPLEMENTED0。Gustedt也有一个变通方法,尽管我还没有使用它,我也不确定它是否适用于我们在这里做的事情。

#6


0  

This is the simplified version that I use. It is based upon the great techniques of the other answers here, so many props to them:

这是我使用的简化版本。它是建立在其他答案的伟大技术之上的,有那么多的支撑:

#define _SELECT(PREFIX,_5,_4,_3,_2,_1,SUFFIX,...) PREFIX ## _ ## SUFFIX

#define _BAR_1(fmt)      printf(fmt "\n")
#define _BAR_N(fmt, ...) printf(fmt "\n", __VA_ARGS__);
#define BAR(...) _SELECT(_BAR,__VA_ARGS__,N,N,N,N,1)(__VA_ARGS__)

int main(int argc, char *argv[]) {
    BAR("here is a log message");
    BAR("here is a log message with a param: %d", 42);
    return 0;
}

That's it.

就是这样。

As with other solutions this is limited to the number of arguments the macro. To support more, add more parameters to _SELECT, and more N arguments. The argument names count down (instead of up) to serve as a reminder that the count-based SUFFIX argument is provided in reverse order.

与其他解决方案一样,这仅限于宏的参数数量。要支持更多,请添加更多的参数到_SELECT,以及更多的N参数。参数名称倒数(而不是向上)以提醒您基于可数的后缀参数是按相反顺序提供的。

This solution treats 0 arguments as though it is 1 argument. So BAR() nominally "works", because it expands to _SELECT(_BAR,,N,N,N,N,1)(), which expands to _BAR_1()(), which expands to printf("\n").

这个解将0个参数视为1个参数。BAR()名义上“工作”,因为它扩展到_SELECT(_BAR, N,N,N,N, N,1)(),扩展到_BAR_1()(),扩展到printf(“\ N”)。

If you want, you can get creative with the use of _SELECT and provide different macros for different number of arguments. For example, here we have a LOG macro that takes a 'level' argument before the format. If format is missing, it logs "(no message)", if there is just 1 argument, it will log it through "%s", otherwise it will treat the format argument as a printf format string for the remaining arguments.

如果需要,可以使用_SELECT,并为不同数量的参数提供不同的宏。例如,在这里我们有一个日志宏,它在格式之前采用“level”参数。如果缺少格式,则记录“(无消息)”,如果只有一个参数,它将通过“%s”记录它,否则它将将把格式参数作为剩余参数的printf格式字符串处理。

#define _LOG_1(lvl)          printf("[%s] (no message)\n", #lvl)
#define _LOG_2(lvl,fmt)      printf("[%s] %s\n", #lvl, fmt)
#define _LOG_N(lvl,fmt, ...) printf("[%s] " fmt "\n", #lvl, __VA_ARGS__)
#define LOG(...) _SELECT(_LOG,__VA_ARGS__,N,N,N,2,1)(__VA_ARGS__)

int main(int argc, char *argv[]) {
    LOG(INFO);
    LOG(DEBUG, "here is a log message");
    LOG(WARN, "here is a log message with param: %d", 42);
    return 0;
}
/* outputs:
[INFO] (no message)
[DEBUG] here is a log message
[WARN] here is a log message with param: 42
*/

#7


-2  

The standard solution is to use FOO instead of BAR. There are a few weird cases of argument reordering it probably can't do for you (though I bet someone can come up with clever hacks to disassemble and reassemble __VA_ARGS__ conditionally based on the number of arguments in it!) but in general using FOO "usually" just works.

标准的解决方案是使用FOO而不是BAR。有一些奇怪的参数重新排序的情况可能对您没有帮助(尽管我打赌有人可以想出聪明的方法,根据其中的参数数量有条件地分解和重新组装__VA_ARGS__ !),但是一般使用FOO“通常”就可以了。