问题描述
如果我尝试编译以下代码:
template <typename... TArgs>
void Dummy(const TArgs &...args)
{
}
#define DUMMY(...) Dummy("Hello",##__VA_ARGS__)
int main()
{
DUMMY();
}
我收到以下编译错误:
g++ -std=c++17 -O3 -Wall main.cpp && ./a.out
main.cpp: In function 'int main()':
main.cpp:6:48: error: expected primary-expression before ')' token
6 | #define DUMMY(...) Dummy("Hello",##__VA_ARGS__)
| ^
main.cpp:10:5: note: in expansion of macro 'DUMMY'
10 | DUMMY();
| ^~~~~
https://coliru.stacked-crooked.com/a/c9217ba86e7d24bd
template <typename... TArgs>
void Dummy(const TArgs &...args)
{
}
#define DUMMY(dummy,...) Dummy(dummy,##__VA_ARGS__)
int main()
{
DUMMY(); // This is strange. Why does this compile?
DUMMY(1);
DUMMY(1,2);
DUMMY(1,2,3);
}
https://coliru.stacked-crooked.com/a/e30e14810d70f482
但是我不确定它是否正确,因为DUMMY
至少接受一个参数,但是我传递了零。
解决方法
使用零参数时,标准__VA_ARGS__
不会删除结尾的,
。您的##__VA_ARGS__
会删除多余的,
是GCC扩展名。
此GCC扩展名不起作用,因为您使用的是标准兼容模式-std=c++17
,而不是-std=gnu++17
。
关于C / C ++宏的一个重要事实是,不可能无参数地调用它们,因为允许宏参数为空的令牌序列。
因此,DUMMY()
仅使用一个空参数而不是零参数来调用宏DUMMY
。这解释了第二个示例为何起作用,也解释了第一个示例为何产生语法错误。
当,##__VA_ARGS__
没有任何元素时,GCC扩展名将从__VA_ARGS__
中删除逗号。但是,单个空参数与没有参数不同。当您将DUMMY
定义为#define DUMMY(...)
时,就保证了__VA_ARGS__
至少有一个参数,因此,
不会被删除。
***注意:如果您未使用--std
选项指定某些ISO标准,则GCC会对该规则做例外处理。在这种情况下,如果...
是唯一的宏参数,并且调用的参数为空,则,##__VA_ARGS__
确实删除逗号。 CPP manual in the Variadic Marcos section中对此进行了说明:
上面的解释对于唯一的宏参数是可变参数参数的情况是模棱两可的,因为试图区分根本没有参数是空参数还是缺失参数毫无意义。符合特定的C标准时,CPP保留逗号。否则,逗号将作为标准的扩展名被删除。
当DUMMY
为#define DUMMY(x,...)
时,如果仅使用一个参数调用__VA_ARGS
,则DUMMY
将为空,其中包括两个调用DUMMY()
(一个为空参数)和DUMMY(0)
(一个参数0
)。请注意,标准C和直到C ++ 20的C ++均不允许这种调用。他们要求至少有一个与省略号对应的参数(可能为空)。不过,GCC从未施加此限制,并且无论,##__VA_ARGS__
设置如何,GCC都将省略--std
逗号。
从C ++ 20开始,您可以使用__VA_OPT__
内置宏作为处理逗号(以及可能需要删除的任何其他标点符号)的更标准方法。 __VA_OPT__
还避免了上面带有空参数的问题,因为它使用了不同的标准:如果__VA_OPT__(x)
包含至少一个令牌,x
会扩展为__VA_ARGS__
;否则,它将扩展为空序列。因此,__VA_OPT__
可以按预期在此问题中的宏上工作。
我相信,至少在最新版本中,所有主流编译器现在都实现__VA_OPT__
。
由于某些原因(可能是GCC错误),如果仅使用https://github.com/guilherme-otran/heroku-buildpack-ffprobe.git
而不使用其他参数,那么#define DUMMY(...)
将无法按预期工作(如果{{1} }为空)。
仅当您使用##__VA_ARGS__
进行编译时,才如此。使用__VA_ARGS__
进行编译时,不会发生这种情况。但是无论如何,-std=c++17
是GCC扩展,带有-std=gnu++17
的代码完全不能与##__VA_ARGS__
一起编译。但是,除非您设置##__VA_ARGS__
标志,否则GCC允许在-std=c++17
模式下使用GCC扩展名。但是似乎GCC扩展在-std=c++17
和-pedantic
模式下的工作方式有所不同。
但是该问题可以解决:
-std=c++17
当您调用-std=gnu++17
时,#include <utility>
template <typename... TArgs>
void Dummy(const TArgs &...args)
{
}
namespace WA
{
class stub_t {};
stub_t ArgOrStub()
{
return {};
}
template <typename T>
auto ArgOrStub(T &&t) -> decltype( std::forward<T>(t) )
{
return std::forward<T>(t);
}
template <typename... TArgs>
void RemoveStubAndCallDummy(stub_t,TArgs &&...args)
{
Dummy(std::forward<TArgs>(args)...);
}
template <typename... TArgs>
void RemoveStubAndCallDummy(TArgs &&...args)
{
Dummy(std::forward<TArgs>(args)...);
}
}
#define DUMMY(first,...) WA::RemoveStubAndCallDummy( WA::ArgOrStub(first),##__VA_ARGS__ )
int main()
{
DUMMY();
}
参数将为空,经过预处理,我们将得到DUMMY()
,它将返回first
,随后将通过第一次重载来删除它WA::ArgOrStub()
。它体积庞大,但我无法找到更好的解决方案。