如何正确扩展宏?

how to properly expand a macro?

我需要能够扩展一个宏来构建我用于我的应用程序的 typedef。该宏构建了一个简单的 typedef。我的问题是 __VA_ARGS__(即你会在调用的后面失去参数吗?)如何在传递给大量宏时起作用,以及如何知道何时需要进行另一次扫描以强制获得正确的结果,因为我认为这可能是创建高阶 DERIVED 宏时出现问题的根源。

#define DERIVED0()          rtti::impl::BaseTypedefList<rtti::impl::null>
#define DERIVED1(T1)        rtti::impl::BaseTypedefList<T1, DERIVED0()>
#define DERIVED2(T1, T2)    rtti::impl::BaseTypedefList<T1, DERIVED1(T2)>
#define BUILD(count, ...) DERIVED##count( __VA_ARGS__ )

// inside the classes
#define CLASS_BODY(count, ...) typedef BUILD(count, __VA_ARGS__)    BaseClassList;

// example usages
CLASS_BODY(0)                   // WORKS
CLASS_BODY(1, MeshRenderer)     // WORKS
CLASS_BODY(2, Renderer, Object) // ERROR

Microsoft Visual Studio 版本的 C 预处理器(MSVC++、MSVC)有一个特殊的实体概念,否则将是一系列多个标记被分块到单个标记中。这在扩展可变参数宏时特别有用; __VA_ARGS__ 始终作为单个标记展开,即使该展开包含逗号。此行为是 Microsoft 预处理器特有的。

特别是,在调用 CLASS_BODY(2, Renderer, Object) 期间,您正在调用:

 BUILD(2, Renderer, Object)

从技术上讲,Renderer, Object 是一个标记,但在 这个 点它并不重要。在此处的参数识别期间,class2 匹配,...Renderer, Object 匹配。在参数替换期间,这变得有些奇怪:

DERIVED##count( Renderer, Object )

这看起来没什么害处,但奇怪的是 Renderer, Object 合起来是 一个令牌 。那里的逗号不作为分隔符处理。稍后可以看到其中的含义......在我们浏览粘贴之后:

DERIVED2( Renderer, Object )

...然后调用 DERIVED2。这里,参数标识匹配 T1Renderer, ObjectT2悬空;它不匹配。这会产生预处理器错误。

此处的一般经验法则是在替换列表中使用 __VA_ARGS__ 的任何地方应用扩展步骤,至少如果您依赖多个参数被解析为多个预处理器标记(除非您实际上想要这种行为的一些奇怪原因,但在这种情况下,您将被锁定在 MSVS 特性中)。 99% 的时间这种形式的间接寻址都有效:

#define EVAL(...) __VA_ARGS__
#define BUILD(count, ...) EVAL(DERIVED##count( __VA_ARGS__ ))

有时您可能不得不这样做:

#define CALL(X,Y) X Y
#define BUILD(count, ...) CALL(DERIVED##count,( __VA_ARGS__))

在这种特殊情况下都有效。