C语言 GCC ##__VA_ARGS__ 技巧的标准替代方案?
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/5588855/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
Standard alternative to GCC's ##__VA_ARGS__ trick?
提问by jwd
There is a well-knownproblemwith empty args for variadic macros in C99.
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:
BAR()根据C99标准,上面的使用确实是不正确的,因为它会扩展为:
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 fmtis 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.
这会自动向我的 BAR() 日志语句添加换行符,假设fmt始终是双引号 C 字符串。它不会将换行符打印为单独的 printf(),如果日志记录是行缓冲的并且异步来自多个源,这将是有利的。
采纳答案by zwol
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.
,##__VA_ARGS__如果您愿意接受可以传递给可变参数宏的参数数量的一些硬编码上限,则可以避免使用 GCC 的扩展名,如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 年,我__VA_ARGS__在文档 N976 中编写了 GCC 标准化扩展(以及允许您使用除其余参数以外的名称的相关扩展),但没有收到委员会的任何回应;我什至不知道有没有人读过。2016 年,它在N2023 中再次被提出,我鼓励任何知道该提案将如何在评论中告诉我们的人。
回答by Richard Hansen
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:
这是BAR()在 jwd 的问题中实现第二个示例的一种符合标准的方法:
#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:
同样的技巧用于:
- count the number of arguments
- expand differently depending on the number of arguments
- append to
__VA_ARGS__
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()
FIRST()
This macro simply expands to the first argument, discarding the rest.
这个宏只是扩展到第一个参数,丢弃其余的。
The implementation is straightforward. The throwawayargument ensures that FIRST_HELPER()gets two arguments, which is required because the ...needs at least one. With one argument, it expands as follows:
实现很简单。该throwaway参数确保FIRST_HELPER()获得两个参数,这是必需的,因为...至少需要一个。有一个参数,它扩展如下:
FIRST(firstarg)FIRST_HELPER(firstarg, throwaway)firstarg
FIRST(firstarg)FIRST_HELPER(firstarg, throwaway)firstarg
With two or more, it expands as follows:
有两个或更多,它扩展如下:
FIRST(firstarg, secondarg, thirdarg)FIRST_HELPER(firstarg, secondarg, thirdarg, throwaway)firstarg
FIRST(firstarg, secondarg, thirdarg)FIRST_HELPER(firstarg, secondarg, thirdarg, throwaway)firstarg
REST()
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 ONEif only one argument is given, TWOORMOREif between two and nine arguments are given, and breaks if 10 or more arguments are given (because it expands to the 10th argument).
使用NUM()宏计算参数。ONE如果只给出一个参数,TWOORMORE如果给出2 到 9 个参数,则此宏扩展为,如果给出 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 throwawayas 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_10TH()宏来确定参数的个数。顾名思义,SELECT_10TH()简单地扩展到它的第 10 个参数。因为省略号,SELECT_10TH()需要传递至少 11 个参数(标准说必须至少有一个省略号的参数)。这就是为什么NUM()通过throwaway作为最后一个参数(如果没有它,传递一个参数NUM()将导致只有10传递到参数SELECT_10TH(),这将违反标准)。
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()orREST_HELPER_TWOORMORE()是通过REST_HELPER_与NUM(__VA_ARGS__)in的扩展连接来完成的REST_HELPER2()。请注意, 的目的REST_HELPER()是确保NUM(__VA_ARGS__)在与 连接之前完全展开REST_HELPER_。
Expansion with one argument goes as follows:
一个参数的扩展如下:
REST(firstarg)REST_HELPER(NUM(firstarg), firstarg)REST_HELPER2(SELECT_10TH(firstarg, TWOORMORE, TWOORMORE, TWOORMORE, TWOORMORE, TWOORMORE, TWOORMORE, TWOORMORE, TWOORMORE, ONE, throwaway), firstarg)REST_HELPER2(ONE, firstarg)REST_HELPER_ONE(firstarg)- (empty)
REST(firstarg)REST_HELPER(NUM(firstarg), firstarg)REST_HELPER2(SELECT_10TH(firstarg, TWOORMORE, TWOORMORE, TWOORMORE, TWOORMORE, TWOORMORE, TWOORMORE, TWOORMORE, TWOORMORE, ONE, throwaway), firstarg)REST_HELPER2(ONE, firstarg)REST_HELPER_ONE(firstarg)- (空的)
Expansion with two or more arguments goes as follows:
带有两个或更多参数的扩展如下:
REST(firstarg, secondarg, thirdarg)REST_HELPER(NUM(firstarg, secondarg, thirdarg), firstarg, secondarg, thirdarg)REST_HELPER2(SELECT_10TH(firstarg, secondarg, thirdarg, TWOORMORE, TWOORMORE, TWOORMORE, TWOORMORE, TWOORMORE, TWOORMORE, TWOORMORE, TWOORMORE, ONE, throwaway), firstarg, secondarg, thirdarg)REST_HELPER2(TWOORMORE, firstarg, secondarg, thirdarg)REST_HELPER_TWOORMORE(firstarg, secondarg, thirdarg), secondarg, thirdarg
REST(firstarg, secondarg, thirdarg)REST_HELPER(NUM(firstarg, secondarg, thirdarg), firstarg, secondarg, thirdarg)REST_HELPER2(SELECT_10TH(firstarg, secondarg, thirdarg, TWOORMORE, TWOORMORE, TWOORMORE, TWOORMORE, TWOORMORE, TWOORMORE, TWOORMORE, TWOORMORE, ONE, throwaway), firstarg, secondarg, thirdarg)REST_HELPER2(TWOORMORE, firstarg, secondarg, thirdarg)REST_HELPER_TWOORMORE(firstarg, secondarg, thirdarg), secondarg, thirdarg
回答by Marsh Ray
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:
我相信它会忽略格式字符串中未引用的任何额外参数。所以你甚至可以逃脱:
#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 在没有标准方法的情况下获得批准。AFAICT 这个问题也存在于 C++11 中。
回答by DRayX
There is a way to handle this specific case using something like Boost.Preprocessor. You can use BOOST_PP_VARIADIC_SIZEto 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:
有一种方法可以使用Boost.Preprocessor 之类的东西来处理这种特定情况。您可以使用BOOST_PP_VARIADIC_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:
在这种情况下,我们很幸运,因为您想要的宏总是至少有 1 个参数,我们可以将它实现为两个“重载”宏:
#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_TRAILING?这将使这个解决方案不那么可怕。
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_TRAILING,以及一个使用它的版本(这是我现在最喜欢的解决方案):
#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__)))) \
/**/
回答by User123abc
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_ARGSto count the number of arguments which a variadic macro is given. You can use a variation of NUM_ARGSto 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 Barmacro so that it uses NUM_ARGS_CEILING2and CONCATto 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_ARGSto build的变体NUM_ARGS_CEILING2,它可以告诉您一个可变参数宏是被赋予 1 个参数还是 2 个或更多参数。然后,你可以写你的Bar,以便它使用宏NUM_ARGS_CEILING2和CONCAT其中一个期望的是1周的说法,而另一个预期的参数个数可变大于1:到它的参数发送给两个辅助宏之一。
Here's an example where I use this trick to write the macro UNIMPLEMENTED, which is very similar to BAR:
这是我使用此技巧编写宏的示例UNIMPLEMENTED,它非常类似于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 的目标是扩展到另一个宏“调用”。
Note that NUM_ARGS itself isn't used. I just included it to illustrate the basic trick here. See Jens Gustedt's P99 blogfor 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, 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 处理的参数数量有限。我的最多只能处理 20 个,尽管这个数字是完全任意的。
如图所示,NUM_ARGS 有一个缺陷,即当给定 0 个参数时它返回 1。其要点是 NUM_ARGS 在技术上计算 [逗号 + 1],而不是 args。在这种特殊情况下,它实际上对我们有利。_UNIMPLEMENTED1 可以很好地处理空令牌,它使我们不必编写 _UNIMPLEMENTED0。Gustedt 也有一个解决方法,虽然我没有使用过它,我不确定它是否适用于我们在这里所做的事情。
回答by SimonW
A very simple macro I'm using for debug printing:
我用于调试打印的一个非常简单的宏:
#define __DBG_INT(fmt, ...) printf(fmt "%s", __VA_ARGS__);
#define DBG(...) __DBG_INT(__VA_ARGS__, "\n")
int main() {
DBG("No warning here");
DBG("and we can add as many arguments as needed. %s", "nice!");
return 0;
}
No matter how many arguments are passed to DBG there are no c99 warning.
无论有多少参数传递给 DBG,都没有 c99 警告。
The trick is __DBG_INTadding a dummy param so ...will always have at least one argument and c99 is satisfied.
诀窍是__DBG_INT添加一个虚拟参数,因此...将始终至少有一个参数并且满足 c99。
回答by ?eurobur?
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 Narguments. The argument names count down (instead of up) to serve as a reminder that the count-based SUFFIXargument is provided in reverse order.
与其他解决方案一样,这仅限于宏的参数数量。要支持更多,请向_SELECT和更多N参数添加更多参数。参数名称倒计时(而不是向上)以提醒基于计数的SUFFIX参数以相反的顺序提供。
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,1)(),扩展到_BAR_1()(),扩展到printf("\n")。
If you want, you can get creative with the use of _SELECTand 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并为不同数量的参数提供不同的宏。例如,这里我们有一个 LOG 宏,它在格式之前接受一个“级别”参数。如果缺少格式,它会记录“(无消息)”,如果只有 1 个参数,它会通过“%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
*/
回答by PSkocik
In your situation (at least 1 argument present, never 0), you can define BARas BAR(...), use Jens Gustedt'sHAS_COMMA(...)to detect a comma and then dispatch to BAR0(Fmt)or BAR1(Fmt,...)accordingly.
在您的情况下(至少存在 1 个参数,从不为 0),您可以定义BAR为BAR(...),使用Jens Gustedt'sHAS_COMMA(...)检测逗号,然后分派到BAR0(Fmt)或BAR1(Fmt,...)。
This:
这个:
#define HAS_COMMA(...) HAS_COMMA_16__(__VA_ARGS__, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0)
#define HAS_COMMA_16__(_0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, ...) _15
#define CAT_(X,Y) X##Y
#define CAT(X,Y) CAT_(X,Y)
#define BAR(.../*All*/) CAT(BAR,HAS_COMMA(__VA_ARGS__))(__VA_ARGS__)
#define BAR0(X) printf(X "\n")
#define BAR1(X,...) printf(X "\n",__VA_ARGS__)
#include <stdio.h>
int main()
{
BAR("here is a log message");
BAR("here is a log message with a param: %d", 42);
}
compiles with -pedanticwithout a warning.
编译时-pedantic没有警告。
回答by l4m2
C (gcc), 762 bytes
C (gcc), 762 字节
#define EMPTYFIRST(x,...) A x (B)
#define A(x) x()
#define B() ,
#define EMPTY(...) C(EMPTYFIRST(__VA_ARGS__) SINGLE(__VA_ARGS__))
#define C(...) D(__VA_ARGS__)
#define D(x,...) __VA_ARGS__
#define SINGLE(...) E(__VA_ARGS__, B)
#define E(x,y,...) C(y(),)
#define NONEMPTY(...) F(EMPTY(__VA_ARGS__) D, B)
#define F(...) G(__VA_ARGS__)
#define G(x,y,...) y()
#define STRINGIFY(...) STRINGIFY2(__VA_ARGS__)
#define STRINGIFY2(...) #__VA_ARGS__
#define BAR(fmt, ...) printf(fmt "\n" NONEMPTY(__VA_ARGS__) __VA_ARGS__)
int main() {
puts(STRINGIFY(NONEMPTY()));
puts(STRINGIFY(NONEMPTY(1)));
puts(STRINGIFY(NONEMPTY(,2)));
puts(STRINGIFY(NONEMPTY(1,2)));
BAR("here is a log message");
BAR("here is a log message with a param: %d", 42);
}
Assumes:
假设:
- No arg contain comma or bracket
- No arg contain
A~G(can rename to hard_collide ones)
- 没有 arg 包含逗号或括号
- 没有 arg 包含
A~G(可以重命名为 hard_collide 的)
回答by R.. GitHub STOP HELPING ICE
The standard solution is to use FOOinstead 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“通常”只是工作。

