You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.09 17:30:43