如何正确扩展宏?

时间:2017-10-23 03:50:10

标签: c++ macros c-preprocessor c++14 variadic-macros

我需要能够扩展一个宏来构建一个我用于我的应用程序的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

1 个答案:

答案 0 :(得分:1)

C预处理器(MSVC ++,MSVC)的Microsoft Visual Studio版本具有一个特殊的实体概念,否则该实体将是一系列多个令牌被分块到单个令牌中。这在扩展可变参数宏时特别起作用; __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, 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__))

在这种特殊情况下都适用。