[英]Variadic macros with zero arguments doesn't compile even with ##__VA_ARGS__
如果我嘗試編譯以下代碼:
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__
不會刪除 trailing 。 你##__VA_ARGS__
其刪除多余的,
是一個GCC擴展。
此 GCC 擴展不起作用,因為您使用的是標准兼容模式-std=c++17
,而不是-std=gnu++17
。
關於 C/C++ 宏的一個重要事實是,不可能在沒有參數的情況下調用它們,因為宏參數可以是空標記序列。
因此, DUMMY()
使用單個空參數而不是零參數調用宏DUMMY
。 這解釋了為什么第二個例子有效,也解釋了為什么第一個例子會產生語法錯誤。
當__VA_ARGS__
沒有元素時, ##__VA_ARGS__
GCC 擴展會從, ##__VA_ARGS__
刪除逗號。 但是單個空參數與沒有參數不同。 當您將DUMMY
定義為#define DUMMY(...)
,您保證__VA_ARGS__
至少有一個參數,因此,
不會被刪除。
***注意:如果您沒有使用--std
選項指定某些 ISO 標准,GCC 會對該規則進行例外處理。 在這種情況下,如果...
是唯一的宏參數並且調用具有空參數,則,##__VA_ARGS__
確實會刪除逗號。 CPP 手冊中的 Variadic Marcos 部分對此進行了說明:
上面的解釋對於唯一的宏參數是可變參數參數的情況是模棱兩可的,因為試圖區分根本沒有參數是空參數還是缺失參數是沒有意義的。 CPP 在符合特定 C 標准時保留逗號。 否則,逗號將作為標准的擴展而刪除。
當DUMMY
為#define DUMMY(x, ...)
,如果DUMMY
僅使用一個參數調用,則__VA_ARGS
將為空,其中包括調用DUMMY()
(一個空參數)和DUMMY(0)
(一個參數, 0
)。 請注意,標准 C 和 C++ 直到 C++20,都不允許這種調用; 它們要求至少有一個(可能是空的)參數對應於省略號。 不過,GCC 從未強加過此限制,無論--std
設置如何,GCC 都會省略帶有,##__VA_ARGS__
的逗號。
從 C++20 開始,您可以使用__VA_OPT__
內置宏作為處理逗號(以及可能需要刪除的任何其他標點符號)的更標准方法。 __VA_OPT__
也避免了上面用空參數提出的問題,因為它使用了不同的標准: __VA_OPT__(x)
如果__VA_ARGS__
包含至少一個標記,則擴展為x
; 否則,它將擴展為空序列。 因此, __VA_OPT__
將按預期對這個問題中的宏進行工作。
我相信所有主要的編譯器現在都實現了__VA_OPT__
,至少在它們的最新版本中是這樣。
由於某些原因(這可能是 GCC 錯誤),如果您只使用#define DUMMY(...)
而沒有其他參數,那么##__VA_ARGS__
將無法按預期工作(如果__VA_ARGS__
為空,它將不會刪除逗號)。
僅當您使用-std=c++17
編譯時,這才是正確的。 當您使用-std=gnu++17
編譯時,不會發生這種情況。 但無論如何##__VA_ARGS__
是GCC 擴展,帶有##__VA_ARGS__
的代碼##__VA_ARGS__
不能用-std=c++17
編譯。 但是 GCC 允許在-std=c++17
模式下使用 GCC 擴展,除非您設置-pedantic
標志。 但似乎 GCC 擴展在-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()
它將返回stub_t
稍后將被RemoveStubAndCallDummy
的第一次重載RemoveStubAndCallDummy
。 它很笨重,但我找不到更好的解決方案。
C++20 引入了__VA_OPT__
作為一種在 arguments 大於零的情況下可選地擴展可變參數宏中標記的方法。
這消除了對##__VA_ARGS__
GCC 擴展的需要。 如果您可以使用該版本的標准,那應該是一個優雅的、編譯器無關的解決方案。
序列
__VA_OPT__(x)
僅在可變參數宏的替換列表中合法,如果__VA_ARGS__
非空則展開為 x,如果為空則展開為空。
所以你可以簡單地做:
#define DUMMY(...) Dummy("Hello" __VA_OPT__(,) __VA_ARGS__)
這是有關__VA_OPT__
的精彩博客文章(以及更多關於預處理器宏的內容): https://www.scs.stanford.edu/~dm/blog/va-opt.html
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.