如何在同一个 "call stack" 中两次使用宏列表?

How to use a macro list twice in the same "call stack"?

我将信息存储在这样的宏列表中:

#define MYLIST(XX) \
  XX(1, hello)     \
  XX(2, world)     \
  ...

现在我想在同一个 "call stack" 中两次使用这个宏。这是一个愚蠢的例子:

#define BB(i,n) i +
#define AA(i,n) i + (MYLIST(BB) 0) +
int foo = MYLIST(AA) 0;

然而,这不起作用,因为 MYLIST does not expand 第二次:

int foo = 1 + (MYLIST(BB) 0) + 2 + (MYLIST(BB) 0) + 0;

有没有办法在同一个 "call stack" 中使用 MYLIST 两次 - 或者解决方法 - 使用我现有的列表?

以下代码将起作用:

#define EVAL(...) __VA_ARGS__
#define EVAL2(...) EVAL(__VA_ARGS__)
#define EMPTY()
#define DELAYED_CALL(F, ...) F EMPTY()(__VA_ARGS__)

#define BB(i,n) i +
#define AA(i,n) i + (MYLIST(BB) 0) +

#define MYLIST(XX) \
    DELAYED_CALL(XX, 1, hello) \
    DELAYED_CALL(XX, 2, world)

int foo = EVAL2(MYLIST(AA)) 0;

输出:int foo = 1 + (1 + 2 + 0) + 2 + (1 + 2 + 0) + 0;

不幸的是,我对为什么这行得通没有深刻的理解;我只是尝试了一些在这种情况下往往会有所帮助的技巧。但我可以解释一些。

有时宏被标记为 "not to be expanded further"。该标志通常在您开始展开时设置,并在展开完成后取消设置。这往往会阻止递归。

当宏扩展为通常是类似函数的宏调用的标记时,有时我们已经过了扩展它们的阶段。

我们可以通过将宏的扩展延迟到标志不会引起任何问题的点来绕过第一个问题,方法是将第二个宏添加到 create 宏调用当它被评估时。这就是 DELAYED_CALL 所做的。但是在这样做的时候,我们 运行 进入了第二个问题,所以我们必须添加一些对 EVAL 的调用来使宏被重新扫描(函数类宏的参数总是被扫描,所以将一系列标记传递给仅回显其参数的类似函数的宏将导致重新扫描)。

有时我们需要重新扫描几次才能使一切正常。对于 EVAL(EVAL(X))EVAL2(X) 只是 shorthand。有时需要更多的评估。

下面的代码使发生的事情更清楚一些。请注意 MYLIST2 版本需要少一个 EVAL;这是因为 AA 调用了 MYLIST,而 MYLIST2 设置了违规标志。

#define EVAL(...) __VA_ARGS__
#define EVAL2(...) EVAL(__VA_ARGS__)
#define EVAL3(...) EVAL2(__VA_ARGS__)
#define EMPTY()
#define DELAYED_CALL(F, ...) F EMPTY()(__VA_ARGS__)

#define BB(i,n) i +
#define AA(i,n) i + (MYLIST(BB) 0) +

#define MYLIST(XX) \
    DELAYED_CALL(XX, 1, hello) \
    DELAYED_CALL(XX, 2, world)

#define MYLIST2(XX) \
    XX(1, hello) \
    XX(2, world)

% MYLIST
int foo = MYLIST(AA) 0;
int foo = EVAL(MYLIST(AA)) 0;
int foo = EVAL2(MYLIST(AA)) 0;

% MYLIST2
int foo = MYLIST2(AA) 0;
int foo = EVAL(MYLIST2(AA)) 0;
int foo = EVAL2(MYLIST2(AA)) 0;

这个输出将是:

% MYLIST
int foo = AA (1, hello) AA (2, world) 0;
int foo = 1 + (BB (1, hello) BB (2, world) 0) + 2 + (BB (1, hello) BB (2, world) 0) + 0;
int foo = 1 + (1 + 2 + 0) + 2 + (1 + 2 + 0) + 0;

% MYLIST2
int foo = 1 + (BB (1, hello) BB (2, world) 0) + 2 + (BB (1, hello) BB (2, world) 0) + 0;
int foo = 1 + (1 + 2 + 0) + 2 + (1 + 2 + 0) + 0;
int foo = 1 + (1 + 2 + 0) + 2 + (1 + 2 + 0) + 0;

(% 符号没什么特别的。我只是想要在输出中显示的注释,并且 C 风格的注释会在预处理过程中被删除。)

Further reading。这篇文章的作者比我更明白这一点。