好吧,我想我已经弄清楚发生了什么事。编译器不实现 Prosser 的算法,就这样。尽管普罗瑟的算法确实解决了特定的问题已知缺陷 http://open-std.org/jtc1/sc22/wg21/docs/cwg_active.html#268在语言规范中,它没有解释实际编译器扩展宏的方式。
似乎有两种禁用位在起作用,而不是隐藏集:每个宏禁用位和每个令牌禁用位。
宏的每个宏禁用位M
在重新扫描阶段设置M
正在扩容,一旦扩容就会被清除M
做完了。
如果在任何时候M
被禁用预处理器处理一个实例M
(无论语法上是否对扩展有效),它标记了特定的M
令牌已禁用。编译器在禁用令牌时不仅会避免扩展令牌,而且还会在任何上下文中永久禁用对令牌扩展的任何考虑,即使在之后M
的扩展已经完成。
让我们看一些更简单的例子:
#define ID(arg) arg
#define LPAREN (
#define F_AGAIN() F
#define F() f F_AGAIN LPAREN)()
F() // => f F_AGAIN ()()
ID(F()) // => f f F_AGAIN ()()
ID(ID(F())) // => f f f F_AGAIN ()()
#define G() g G LPAREN)()
G() // => g G ()()
ID(G()) // => g G ()()
ID(ID(G())) // => g G ()()
首先考虑一下扩张过程中发生了什么G()
。它扩展到令牌列表g G LPAREN)()
并且在重新扫描期间,G
该令牌列表中的内容已永久禁用。现在,无论您因传递令牌列表而重新扫描多少次ID
, the G
永远无法扩展。
接下来考虑发生了什么F()
。它扩展到令牌列表f F_AGAIN LPAREN)()
。在重新扫描期间,这会扩展为f F_AGAIN ()()
。因为F_AGAIN
当前不是禁用的宏,这些输出标记都不会被禁用。所以现在在任何重新扫描ID
macro, F_AGAIN
会被扩展一次,也会导致F
扩大一次。
在这种背景下,实际上可以理解语言规范 https://timsong-cpp.github.io/cppwp/n4861/cpp.rescan#3:
如果在替换列表扫描期间找到要替换的宏的名称(不包括源文件的其余预处理标记),则不会替换它。
此外,如果任何嵌套替换遇到被替换的宏的名称,则不会替换它。
这些未替换的宏名称预处理标记不再可用于进一步替换,即使稍后在宏名称预处理标记本来会被替换的上下文中(重新)检查它们也是如此。
我猜想与我的直觉相混淆的部分是“它没有被替换”听起来如此无害——特别是在宏无论如何都不会被替换的上下文中,例如因为它是一个类似函数的宏,后面没有一个开放括号(
。然后,“令牌不再可用于进一步替换”中的被动语态听起来像是只是在描述前一句话的结果,而规范的真正含义是“编译器主动毒害该令牌并禁止其再次扩展” ”。