假设我们有一个#define FOO(x,y) something
.
我想构造这样的宏#define BAR
来BAR(x)(y)
调用FOO(x,y)
. 如果可能的话,我该怎么做?
我尝试了以下操作:
#define BAR(x) FOO(x, BAR_
#define BAR_(y) y)
Run Code Online (Sandbox Code Playgroud)
但得到:
Run Code Online (Sandbox Code Playgroud)error: unterminated argument list invoking macro "FOO"
这是一个 MCVE:
#include <iostream>
#define FOO(x,y) std::cout << x << y << '\n';
#define BAR(x) FOO(x, BAR0
#define BAR0(y) y)
#define STR(...) STR0(__VA_ARGS__)
#define STR0(...) #__VA_ARGS__
int main()
{
std::cout << STR( BAR(1)(2) );
}
Run Code Online (Sandbox Code Playgroud)
另一种尝试:
#define BAR FOO BAR0
#define BAR0(x) (x, BAR1
#define BAR1(y) y)
Run Code Online (Sandbox Code Playgroud)
这个可以编译,但FOO (1, 2)
最终没有展开。
MCVE:
#include <iostream>
#define FOO(x,y) std::cout << x << y << '\n';
#define BAR FOO BAR0
#define BAR0(x) (x, BAR1
#define BAR1(y) y)
#define STR(...) STR0(__VA_ARGS__)
#define STR0(...) #__VA_ARGS__
int main()
{
// Prints `FOO (1, 2)`, but I want `std::cout << 1 << 2 << '\n';`
std::cout << STR( BAR(1)(2) );
}
Run Code Online (Sandbox Code Playgroud)
让我们从这里开始:
#define BAR(x) FOO(x, BAR_
#define BAR_(y) y)
#define FOO(x,y) foo{x|y}
BAR(1)(2)
Run Code Online (Sandbox Code Playgroud)
请注意,我只会使用预处理器来调试预处理器(当我可以简单地调用预处理器时,为什么我需要构建 C++ 程序,无论如何它都会调用预处理器?(这是修辞性的 o/c;我是只是在这里讲一个故事))。
CPP 是这样看待这个问题的。我们看BAR
; 这是一个类似函数的宏。有趣,但除非我们看到左括号,否则不可操作。接下来,我们看到...一个左括号。现在我们已经取得进展了……我们需要确定它的论点。所以我们继续扫描:BAR(1)
...那里...那是匹配的左括号...看起来我们正在用一个参数调用。 BAR
事实证明它是用一个参数定义的,所以这很有效。
现在我们执行参数替换...所以我们注意到 的BAR
替换列表x
以非字符串化、非粘贴的方式提到了它的参数 ( )。这意味着我们应该评估相应的参数 ( 1
),这很容易......这就是它本身。然后,该评估结果将替换替换列表中的参数,因此我们有FOO(1, BAR_
。现在我们已经完成了参数替换。
接下来我们要做的就是重新扫描并进一步更换。所以我们重新扫描... FOO
,啊,是的。这是一个类似函数的宏......FOO(
并且它正在被调用。现在我们已经取得进展了……我们需要确定它的论点。所以,我们继续扫描:FOO(1, BAR_(2)
……突然我们到达了文件的末尾。啊?那是不对的。 FOO
正在被调用;它应该有一个匹配的括号。
您可能天真地认为BAR_(2)
应该调用它,但这不是宏的工作原理。它们评估从外到内,而“in”(又名参数标记)仅评估替换列表中是否提及该参数,其中所述提及不是字符串化或粘贴。
请注意,如果FOO
不是类似函数的宏,这个故事将会走向完全不同的方向。在这种情况下,FOO(
只是预处理器不关心的标记......所以当它看到 时BAR_(2)
,它将调用一个宏。但还有另一个“骗局”:如果在FOO
没有实际调用宏的情况下被传递FOO
,则标记也将被跳过。这两种情况,FOO(1, 2)
最终都会产生,这就是你想要的。但是,如果您想将其作为类似函数的宏进行计算,那么您只有一个选择。 FOO
你需要第二次通过;第一个传递实际上允许调用序列中的第二个参数来构建宏,而此传递不得允许FOO
被调用。需要第二遍来调用它。
嗯,这很简单:
#define DELAY()
#define BAR(x) FOO DELAY() (x, BAR_
#define BAR_(y) y)
#define FOO(x,y) foo{x|y}
#define EVAL(...) __VA_ARGS__
BAR(1)(2)
EVAL(BAR(1)(2))
Run Code Online (Sandbox Code Playgroud)
这是不同之处(第一行)。BAR
在 的参数替换之后,它的替换列表现在FOO DELAY() (1, BAR_
不再只是FOO(1, BAR_
。现在,在重新扫描期间,它仍然会看到FOO
,这仍然很有趣......但它看到的下一个标记是DELAY
,而不是左括号。因此,本次传递中的 CPP 决定不调用FOO
并传递它。完全DELAY()
展开后,不会产生任何结果,然后它只会看到(1, BAR_
;前三个只是令牌。 BAR_
然而,这是一个宏调用,故事也是如此......所以扩展的结果BAR(1)(2)
是产生标记FOO(1, 2)
,没有错误。但这并不能评价FOO
。
然而EVAL
, 接受BAR(1)(2)
作为参数。它的替换列表提到了它的“参数”(不同的 arg 变体),因此BAR(1)(2)
经过充分评估,生成FOO(1, 2)
. 然后FOO(1, 2)
重新扫描,这是FOO
实际被调用的时候。