C++递归宏在MSVC下无法编译的原因及修复方案
MSVC编译C++20宏代码失败的原因分析与修复
这段C++20代码用于生成枚举类型及对应的字符串转换函数,在GCC、Clang等编译器下可正常编译,但使用MSVC编译时出现宏参数不足、语法错误等问题。
原代码
/* compile with: c++ -std=c++20 -Wall -Werror make_enum.cc -o make_enum */ #include <iostream> #define PARENS () // Rescan macro tokens 256 times #define EXPAND(arg) EXPAND1(EXPAND1(EXPAND1(EXPAND1(arg)))) #define EXPAND1(arg) EXPAND2(EXPAND2(EXPAND2(EXPAND2(arg)))) #define EXPAND2(arg) EXPAND3(EXPAND3(EXPAND3(EXPAND3(arg)))) #define EXPAND3(arg) EXPAND4(EXPAND4(EXPAND4(EXPAND4(arg)))) #define EXPAND4(arg) arg #define FOR_EACH(macro, ...) \ __VA_OPT__(EXPAND(FOR_EACH_HELPER(macro, __VA_ARGS__))) #define FOR_EACH_HELPER(macro, a1, ...) \ macro(a1) \ __VA_OPT__(FOR_EACH_AGAIN PARENS (macro, __VA_ARGS__)) #define FOR_EACH_AGAIN() FOR_EACH_HELPER #define ENUM_CASE(name) case name: return #name; #define MAKE_ENUM(type, ...) \ enum type { \ __VA_ARGS__ \ }; \ constexpr const char * \ to_cstring(type _e) \ { \ using enum type; \ switch (_e) { \ FOR_EACH(ENUM_CASE, __VA_ARGS__) \ default: \ return "unknown"; \ } \ } MAKE_ENUM(MyType, ZERO, ONE, TWO, THREE); void test(MyType e) { std::cout << to_cstring(e) << " = " << e << std::endl; } int main() { test(ZERO); test(ONE); test(TWO); test(THREE); } /* Local Variables: c-macro-pre-processor: "c++ -x c++ -std=c++20 -E -" End: */
编译报错信息
1>make_enum.cc 1>C:\Users\Thomas\source\repos\outoftouch\outoftouch\make_enum.cc(40,1): warning C4003: not enough arguments for function-like macro invocation 'FOR_EACH_HELPER' 1>C:\Users\Thomas\source\repos\outoftouch\outoftouch\make_enum.cc(40,1): warning C4003: not enough arguments for function-like macro invocation 'ENUM_CASE' 1>C:\Users\Thomas\source\repos\outoftouch\outoftouch\make_enum.cc(40,1): error C2059: syntax error: 'case' 1>C:\Users\Thomas\source\repos\outoftouch\outoftouch\make_enum.cc(40,1): error C2065: 'ENUM_CASE': undeclared identifier 1>C:\Users\Thomas\source\repos\outoftouch\outoftouch\make_enum.cc(40,1): error C3861: 'FOR_EACH_HELPER': identifier not found 1>C:\Users\Thomas\source\repos\outoftouch\outoftouch\make_enum.cc(40,1): error C2059: syntax error: ')' 1>C:\Users\Thomas\source\repos\outoftouch\outoftouch\make_enum.cc(40,1): error C2146: syntax error: missing ';' before identifier 'default' 1>C:\Users\Thomas\source\repos\outoftouch\outoftouch\make_enum.cc(40,1): warning C4065: switch statement contains 'default' but no 'case' labels 1>Done building project "outoftouch.vcxproj" -- FAILED.
原因分析
MSVC预处理器对C++20的__VA_OPT__和宏递归展开的处理逻辑与GCC/Clang存在差异。原代码中通过FOR_EACH_AGAIN PARENS这种间接调用宏的方式触发递归,MSVC在扫描时未能正确识别该递归逻辑,导致宏展开中断,后续的FOR_EACH_HELPER和ENUM_CASE宏无法被正确展开,最终出现参数不足、语法错误等问题。
修复方法
调整宏的递归展开方式,将间接调用改为直接的宏展开,确保MSVC能正确识别递归逻辑。移除原有的FOR_EACH_AGAIN和PARENS宏,直接在FOR_EACH_HELPER中递归调用自身并通过EXPAND确保完全展开。
修复后的完整代码
/* compile with: cl /std:c++20 /W4 make_enum.cc /Fe:make_enum.exe */ #include <iostream> // Rescan macro tokens 256 times #define EXPAND(arg) EXPAND1(EXPAND1(EXPAND1(EXPAND1(arg)))) #define EXPAND1(arg) EXPAND2(EXPAND2(EXPAND2(EXPAND2(arg)))) #define EXPAND2(arg) EXPAND3(EXPAND3(EXPAND3(EXPAND3(arg)))) #define EXPAND3(arg) EXPAND4(EXPAND4(EXPAND4(EXPAND4(arg)))) #define EXPAND4(arg) arg #define FOR_EACH(macro, ...) \ __VA_OPT__(EXPAND(FOR_EACH_HELPER(macro, __VA_ARGS__))) #define FOR_EACH_HELPER(macro, a1, ...) \ macro(a1) \ __VA_OPT__(EXPAND(FOR_EACH_HELPER(macro, __VA_ARGS__))) #define ENUM_CASE(name) case name: return #name; #define MAKE_ENUM(type, ...) \ enum type { \ __VA_ARGS__ \ }; \ constexpr const char * \ to_cstring(type _e) \ { \ using enum type; \ switch (_e) { \ FOR_EACH(ENUM_CASE, __VA_ARGS__) \ default: \ return "unknown"; \ } \ } MAKE_ENUM(MyType, ZERO, ONE, TWO, THREE); void test(MyType e) { std::cout << to_cstring(e) << " = " << e << std::endl; } int main() { test(ZERO); test(ONE); test(TWO); test(THREE); }
内容的提问来源于stack exchange,提问作者checkbord spy
相关产品推荐
相关产品推荐

