如何正确扩展宏?
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
是一个标记,但在 这个 点它并不重要。在此处的参数识别期间,class
与 2
匹配,...
与 Renderer, Object
匹配。在参数替换期间,这变得有些奇怪:
DERIVED##count( Renderer, Object )
这看起来没什么害处,但奇怪的是 Renderer, Object
合起来是 一个令牌 。那里的逗号不作为分隔符处理。稍后可以看到其中的含义......在我们浏览粘贴之后:
DERIVED2( Renderer, Object )
...然后调用 DERIVED2
。这里,参数标识匹配 T1
和 Renderer, Object
。 T2
悬空;它不匹配。这会产生预处理器错误。
此处的一般经验法则是在替换列表中使用 __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__))
在这种特殊情况下都有效。
我需要能够扩展一个宏来构建我用于我的应用程序的 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
是一个标记,但在 这个 点它并不重要。在此处的参数识别期间,class
与 2
匹配,...
与 Renderer, Object
匹配。在参数替换期间,这变得有些奇怪:
DERIVED##count( Renderer, Object )
这看起来没什么害处,但奇怪的是 Renderer, Object
合起来是 一个令牌 。那里的逗号不作为分隔符处理。稍后可以看到其中的含义......在我们浏览粘贴之后:
DERIVED2( Renderer, Object )
...然后调用 DERIVED2
。这里,参数标识匹配 T1
和 Renderer, Object
。 T2
悬空;它不匹配。这会产生预处理器错误。
此处的一般经验法则是在替换列表中使用 __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__))
在这种特殊情况下都有效。