gcc 未能扩展某些宏
gcc failing to expand some macros
我正在使用第三方 UI 库开发一个程序,其函数形式为 Vbox(void *first, ...)
。这些用作布局函数并采用任意数量的参数。列表的末尾由检测到的第一个 NULL 定义。这意味着我需要记住以 NULL 结束我的列表,这是我经常做不到的事情。
所以我创建了一些辅助宏,它们应该展开以在我的列表中附加一个 NULL。
它们的形式是:
#define UtlVbox(first, ...) Vbox(first, ##__VA_ARGS__, NULL)
__VA_ARGS__
之前的 ##
用于去掉前面的逗号,以防 __VA_ARGS
为空。
我需要 first
以防盒子实际初始化为空 (Vbox(NULL)
):在这些情况下,用户必须显式添加 NULL,因为我无法摆脱,
在 __VA_ARGS__
之后(因为 ##
hack 只有在逗号在 ##
之前才有效,而不是在之后),所以用户必须给出一个明确的 NULL,这将导致以下扩展:Vbox(NULL, NULL)
,这有点多余但很好。
总体上效果很好,但我遇到了一个我不太理解的奇怪情况。
以下面的文件为例:
// expand.c
void* Vbox(void* first, ...);
void* Hbox(void* first, ...);
#define UtlVbox(first, ...) Vbox(first, ##__VA_ARGS__, NULL)
#define UtlHbox(first, ...) Hbox(first, ##__VA_ARGS__, NULL)
static void* Test()
{
return UtlHbox(
Foo,
UtlVbox(
UtlHbox(Bar)));
}
如果我 运行 gcc -E expand.c
,我得到以下输出:
# 1 "expand.c"
# 1 "<built-in>"
# 1 "<command-line>"
# 1 "expand.c"
void* Vbox(void* first, ...);
void* Hbox(void* first, ...);
static void* Test()
{
return Hbox(Foo, Vbox(UtlHbox(Bar), NULL), NULL);
}
除最里面的 UtlHbox 外,所有内容都按预期精确扩展,由于某种原因未扩展,因此在编译时抛出错误。 (此外,由于没有任何相关的#include,因此在此示例中未扩展 NULL)。在 VC12(Visual Studio 2013)中,编译正常。
这里发生了什么?这是##
操作的不同含义之间的冲突吗?有什么办法可以解决吗?
我使用的是 GCC 4.6.3,但我尝试在 GodBolt 上使用 GCC 7.1 编译它并得到相同的结果。
经过一些研究,我开始认为我遇到了 known problem in GCC。
GCC 似乎自己出了问题。如果我创建第三个宏
#define UtlZbox(first, ...) Zbox(first , ##__VA_ARGS__, NULL)
并将上面示例中的内部 UtlHbox 替换为这个新宏,输出正确:
static void* Test()
{
return Hbox(Foo, Vbox(Zbox(Bar, NULL), NULL), NULL);
}
当可变参数宏在其自身的另一个实例中重复时,GCC 似乎自我绊倒了。
我做了一些其他测试(修改宏以简化可视化):
#define UtlVbox(first, ...) V(first,##__VA_ARGS__)
#define UtlHbox(first, ...) H(first,##__VA_ARGS__)
int main()
{
// HHH
UtlHbox(UtlHbox(UtlHbox(1)));
UtlHbox(UtlHbox(UtlHbox(2, 1)));
UtlHbox(UtlHbox(2, UtlHbox(1)));
UtlHbox(2, UtlHbox(UtlHbox(1)));
UtlHbox(3, UtlHbox(2, UtlHbox(1)));
// HHV
UtlHbox(UtlHbox(UtlVbox(1)));
UtlHbox(UtlHbox(UtlVbox(2, 1)));
UtlHbox(UtlHbox(2, UtlVbox(1)));
UtlHbox(2, UtlHbox(UtlVbox(1)));
UtlHbox(3, UtlHbox(2, UtlVbox(1)));
// HVH
UtlHbox(UtlVbox(UtlHbox(1)));
UtlHbox(UtlVbox(UtlHbox(2, 1)));
UtlHbox(UtlVbox(2, UtlHbox(1)));
UtlHbox(2, UtlVbox(UtlHbox(1)));
UtlHbox(3, UtlVbox(2, UtlHbox(1)));
// VHH
UtlVbox(UtlHbox(UtlHbox(1)));
UtlVbox(UtlHbox(UtlHbox(2, 1)));
UtlVbox(UtlHbox(2, UtlHbox(1)));
UtlVbox(2, UtlHbox(UtlHbox(1)));
UtlVbox(3, UtlHbox(2, UtlHbox(1)));
return 0;
}
这里是 Godbolt's output,用 GCC 7.1 编译(在我的机器上用 4.6.3 编译得到相同的输出):
成功的转换用绿色箭头标记,失败的标记为红色。问题似乎是当带有可变参数的宏 X 被放置在 X 的另一个实例的可变参数中的任何位置时(即使作为其他宏 Y 的参数(可变参数或非可变参数))。
最后一个测试块(标记为// Failures...
)重复了之前所有失败的案例,只是用 UtlZbox 替换了扩展失败的宏。这样做会导致在每种情况下都进行适当的扩展,除了 UtlZbox 被放置在另一个 UtlZbox 的可变参数中的情况。
这不是错误;这是 "blue paint".
In VC12 (Visual Studio 2013), things compile just fine.
顺便提一下...Visual Studio 的预处理器是非标准的。
I've bumped into an odd situation I can't quite understand.
...在这里我可以提供帮助。首先,让我们回顾一下预处理器的一般工作规则。
大纲
像宏一样的函数展开有很多步骤,我们可以称之为
- 参数识别
- 参数替换
- 字符串化和粘贴
- 正在重新扫描并进一步替换
在参数识别过程中,您只需将形式参数与调用的参数进行匹配。对于可变参数宏,标准 要求可变参数本身具有一个或多个调用参数。
...作为 gnu 扩展(您正在使用),我们可以将变化部分映射到无参数。我将其称为 null。请注意,这与 empty(和占位符标记)不同;特别是,如果我们 #define FOO(x,...)
,那么调用 FOO(z)
会将 __VA_ARGS__
设置为 null;相比之下,FOO(z,)
会将其设置为 empty.
在参数替换期间,您应用替换列表;在替换列表中,您可以用调用的参数替换形式参数。在这样做之前,任何被调用的 not 参数被字符串化并且 not 参与粘贴运算符(粘贴的左侧或右侧)已完全展开。
接下来应用字符串化和粘贴,顺序不限。
执行完上述步骤后,在重新扫描和进一步替换步骤中还会进行最后一次扫描。作为一项特殊规则,在扫描特定宏的过程中,您不再被允许展开同一个宏。标准行话是"blue paint";该宏被标记(或 "painted blue")用于此扩展。整个扫描完成后,宏为 "unpainted".
解释
让我们以您的第一个示例为例,但我将稍微更改一下:
#define UtlVbox(first, ...) Vbox(first, ##__VA_ARGS__, NULL)
#define UtlHbox(first, ...) Hbox(first, ##__VA_ARGS__, NULL)
#define foomacro Foo
UtlHbox(foomacro,UtlVbox(UtlHbox(Bar)))
这里我只是把 "C" 去掉,只关注预处理器。我还更改了调用宏 foomacro
来突出显示某些内容的调用。下面是 UtlHbox 调用的扩展方式。
我们从参数识别开始。 UtlHbox
有形式参数 first
和 ...
;调用有参数 foomacro
和 UtlVbox(UtlHbox(Bar))
。所以 first
是 foomacro
而 __VA_ARGS__
是 UtlVbox(UtlHbox(Bar))
.
接下来我们使用替换列表执行参数替换,即:
Hbox(<b>first</b>, ##<b><code>__VA_ARGS__
, NULL)
...所以我们将 first
替换为 foomacro
在 foomacro
展开后 ; __VA_ARGS__
和 UtlVbox(UtlHbox(Bar))
字面上的 。后一种情况不同,因为在这个替换列表中,__VA_ARGS__
是粘贴运算符的参与者(即右侧);因此,它不会展开。所以我们得到这个:
Hbox(Foo, ## UtlVbox(UtlHbox(Bar)))
接下来我们进行字符串化和粘贴,得到:
Hbox(Foo, UtlVbox(UtlHbox(Bar)))
接下来我们对UtlHbox
应用重新扫描和进一步替换。所以我们将 UtlHbox
涂成蓝色,然后计算该字符串。你可能已经看到自己在这里遇到麻烦了,但为了完成我会继续。
在重新扫描和进一步替换的过程中,我们发现UtlVbox
,这是另一个宏。这会产生宏 UtlVbox
.
的第二级评估
二级参数识别中,first
为UtlHbox(Bar)
; __VA_ARGS__
是 null.
第二层参数替换,我们看UtlVbox
的替换列表,即:
Vbox(first, ##__VA_ARGS__, NULL)
由于 first
未被字符串化或粘贴,我们在替换它之前评估调用的参数 UtlHbox(Bar)
。 但是由于UtlHbox被涂成蓝色,我们无法将其识别为宏。同时,__VA_ARGS__
为空。所以我们简单地得到:
Vbox(UtlHbox(Bar), ## <i>null</i>, NULL)
在粘贴时的第二层,我们将放置标记粘贴到带有 null 的逗号右侧;这会触发逗号省略规则的 gnu 扩展,因此生成的粘贴会删除逗号,我们得到:
Vbox(UtlHbox(Bar), NULL)
第二层重扫替换,我们把UtlVbox
涂成蓝色,然后再重扫这块。由于 UtlHbox
still 涂成蓝色,因此 still 未被识别为宏。由于没有别的是宏,扫描完成。
所以退出一个级别,我们结束了这个:
Hbox(Foo, Vbox(UtlHbox(Bar), NULL))
...在继续之前,完成每个的重新扫描和替换,我们取消绘制 UtlVbox
和 UtlHbox
。
解决方案
Is there any way to solve this?
嗯,请注意有两个级别的扩展;一个发生在参数替换期间,另一个发生在重新扫描和替换期间。前者发生在蓝色油漆应用之前,并且它可以无限递归:
#define BRACIFY(NAME_) { NAME_ }
BRACIFY(BRACIFY(BRACIFY(BRACIFY(BRACIFY(Z)))) BRACIFY(X))
...将愉快地扩展到:
{ { { { { Z } } } } { X } }
这看起来像你想要做的。但是 "argument substitution" 评估仅在您的参数未进行字符串化或粘贴时才会发生。所以这里真正让你丧命的是 gnu 逗号省略功能;您对它的使用涉及将粘贴运算符应用于 __VA_ARGS__
;这使您在参数替换期间扩展的各种参数不合格。相反,它们只会在重新扫描和替换期间展开,并且在 那个 阶段,您的宏被涂成蓝色。
所以解决方案就是简单地避免省略逗号。在您的 案例中,这实际上非常简单。让我们仔细看看:
#define UtlVbox(first, ...) Vbox(first, ##__VA_ARGS__, NULL)
#define UtlHbox(first, ...) Hbox(first, ##__VA_ARGS__, NULL)
所以你想让UtlVbox(a)
变成Vbox(a, NULL)
,UtlVbox(a, b)
变成Vbox(a, b, NULL)
。那么就这样做怎么样?
#define UtlVbox(...) Vbox(__VA_ARGS__, NULL)
#define UtlHbox(...) Hbox(__VA_ARGS__, NULL)
现在这个:
UtlHbox(UtlHbox(UtlHbox(1)));
UtlHbox(UtlHbox(UtlHbox(2, 1)));
UtlHbox(UtlHbox(2, UtlHbox(1)));
UtlHbox(2, UtlHbox(UtlHbox(1)));
UtlHbox(3, UtlHbox(2, UtlHbox(1)));
UtlHbox(UtlHbox(UtlVbox(1)));
UtlHbox(UtlHbox(UtlVbox(2, 1)));
UtlHbox(UtlHbox(2, UtlVbox(1)));
UtlHbox(2, UtlHbox(UtlVbox(1)));
UtlHbox(3, UtlHbox(2, UtlVbox(1)));
UtlHbox(UtlVbox(UtlHbox(1)));
UtlHbox(UtlVbox(UtlHbox(2, 1)));
UtlHbox(UtlVbox(2, UtlHbox(1)));
UtlHbox(2, UtlVbox(UtlHbox(1)));
UtlHbox(3, UtlVbox(2, UtlHbox(1)));
UtlVbox(UtlHbox(UtlHbox(1)));
UtlVbox(UtlHbox(UtlHbox(2, 1)));
UtlVbox(UtlHbox(2, UtlHbox(1)));
UtlVbox(2, UtlHbox(UtlHbox(1)));
UtlVbox(3, UtlHbox(2, UtlHbox(1)));
...扩展为:
Hbox(Hbox(Hbox(1, NULL), NULL), NULL);
Hbox(Hbox(Hbox(2, 1, NULL), NULL), NULL);
Hbox(Hbox(2, Hbox(1, NULL), NULL), NULL);
Hbox(2, Hbox(Hbox(1, NULL), NULL), NULL);
Hbox(3, Hbox(2, Hbox(1, NULL), NULL), NULL);
Hbox(Hbox(Vbox(1, NULL), NULL), NULL);
Hbox(Hbox(Vbox(2, 1, NULL), NULL), NULL);
Hbox(Hbox(2, Vbox(1, NULL), NULL), NULL);
Hbox(2, Hbox(Vbox(1, NULL), NULL), NULL);
Hbox(3, Hbox(2, Vbox(1, NULL), NULL), NULL);
Hbox(Vbox(Hbox(1, NULL), NULL), NULL);
Hbox(Vbox(Hbox(2, 1, NULL), NULL), NULL);
Hbox(Vbox(2, Hbox(1, NULL), NULL), NULL);
Hbox(2, Vbox(Hbox(1, NULL), NULL), NULL);
Hbox(3, Vbox(2, Hbox(1, NULL), NULL), NULL);
Vbox(Hbox(Hbox(1, NULL), NULL), NULL);
Vbox(Hbox(Hbox(2, 1, NULL), NULL), NULL);
Vbox(Hbox(2, Hbox(1, NULL), NULL), NULL);
Vbox(2, Hbox(Hbox(1, NULL), NULL), NULL);
Vbox(3, Hbox(2, Hbox(1, NULL), NULL), NULL);
我正在使用第三方 UI 库开发一个程序,其函数形式为 Vbox(void *first, ...)
。这些用作布局函数并采用任意数量的参数。列表的末尾由检测到的第一个 NULL 定义。这意味着我需要记住以 NULL 结束我的列表,这是我经常做不到的事情。
所以我创建了一些辅助宏,它们应该展开以在我的列表中附加一个 NULL。
它们的形式是:
#define UtlVbox(first, ...) Vbox(first, ##__VA_ARGS__, NULL)
__VA_ARGS__
之前的 ##
用于去掉前面的逗号,以防 __VA_ARGS
为空。
我需要 first
以防盒子实际初始化为空 (Vbox(NULL)
):在这些情况下,用户必须显式添加 NULL,因为我无法摆脱,
在 __VA_ARGS__
之后(因为 ##
hack 只有在逗号在 ##
之前才有效,而不是在之后),所以用户必须给出一个明确的 NULL,这将导致以下扩展:Vbox(NULL, NULL)
,这有点多余但很好。
总体上效果很好,但我遇到了一个我不太理解的奇怪情况。
以下面的文件为例:
// expand.c
void* Vbox(void* first, ...);
void* Hbox(void* first, ...);
#define UtlVbox(first, ...) Vbox(first, ##__VA_ARGS__, NULL)
#define UtlHbox(first, ...) Hbox(first, ##__VA_ARGS__, NULL)
static void* Test()
{
return UtlHbox(
Foo,
UtlVbox(
UtlHbox(Bar)));
}
如果我 运行 gcc -E expand.c
,我得到以下输出:
# 1 "expand.c"
# 1 "<built-in>"
# 1 "<command-line>"
# 1 "expand.c"
void* Vbox(void* first, ...);
void* Hbox(void* first, ...);
static void* Test()
{
return Hbox(Foo, Vbox(UtlHbox(Bar), NULL), NULL);
}
除最里面的 UtlHbox 外,所有内容都按预期精确扩展,由于某种原因未扩展,因此在编译时抛出错误。 (此外,由于没有任何相关的#include,因此在此示例中未扩展 NULL)。在 VC12(Visual Studio 2013)中,编译正常。
这里发生了什么?这是##
操作的不同含义之间的冲突吗?有什么办法可以解决吗?
我使用的是 GCC 4.6.3,但我尝试在 GodBolt 上使用 GCC 7.1 编译它并得到相同的结果。
经过一些研究,我开始认为我遇到了 known problem in GCC。
GCC 似乎自己出了问题。如果我创建第三个宏
#define UtlZbox(first, ...) Zbox(first , ##__VA_ARGS__, NULL)
并将上面示例中的内部 UtlHbox 替换为这个新宏,输出正确:
static void* Test()
{
return Hbox(Foo, Vbox(Zbox(Bar, NULL), NULL), NULL);
}
当可变参数宏在其自身的另一个实例中重复时,GCC 似乎自我绊倒了。
我做了一些其他测试(修改宏以简化可视化):
#define UtlVbox(first, ...) V(first,##__VA_ARGS__)
#define UtlHbox(first, ...) H(first,##__VA_ARGS__)
int main()
{
// HHH
UtlHbox(UtlHbox(UtlHbox(1)));
UtlHbox(UtlHbox(UtlHbox(2, 1)));
UtlHbox(UtlHbox(2, UtlHbox(1)));
UtlHbox(2, UtlHbox(UtlHbox(1)));
UtlHbox(3, UtlHbox(2, UtlHbox(1)));
// HHV
UtlHbox(UtlHbox(UtlVbox(1)));
UtlHbox(UtlHbox(UtlVbox(2, 1)));
UtlHbox(UtlHbox(2, UtlVbox(1)));
UtlHbox(2, UtlHbox(UtlVbox(1)));
UtlHbox(3, UtlHbox(2, UtlVbox(1)));
// HVH
UtlHbox(UtlVbox(UtlHbox(1)));
UtlHbox(UtlVbox(UtlHbox(2, 1)));
UtlHbox(UtlVbox(2, UtlHbox(1)));
UtlHbox(2, UtlVbox(UtlHbox(1)));
UtlHbox(3, UtlVbox(2, UtlHbox(1)));
// VHH
UtlVbox(UtlHbox(UtlHbox(1)));
UtlVbox(UtlHbox(UtlHbox(2, 1)));
UtlVbox(UtlHbox(2, UtlHbox(1)));
UtlVbox(2, UtlHbox(UtlHbox(1)));
UtlVbox(3, UtlHbox(2, UtlHbox(1)));
return 0;
}
这里是 Godbolt's output,用 GCC 7.1 编译(在我的机器上用 4.6.3 编译得到相同的输出):
成功的转换用绿色箭头标记,失败的标记为红色。问题似乎是当带有可变参数的宏 X 被放置在 X 的另一个实例的可变参数中的任何位置时(即使作为其他宏 Y 的参数(可变参数或非可变参数))。
最后一个测试块(标记为// Failures...
)重复了之前所有失败的案例,只是用 UtlZbox 替换了扩展失败的宏。这样做会导致在每种情况下都进行适当的扩展,除了 UtlZbox 被放置在另一个 UtlZbox 的可变参数中的情况。
这不是错误;这是 "blue paint".
In VC12 (Visual Studio 2013), things compile just fine.
顺便提一下...Visual Studio 的预处理器是非标准的。
I've bumped into an odd situation I can't quite understand.
...在这里我可以提供帮助。首先,让我们回顾一下预处理器的一般工作规则。
大纲
像宏一样的函数展开有很多步骤,我们可以称之为
- 参数识别
- 参数替换
- 字符串化和粘贴
- 正在重新扫描并进一步替换
在参数识别过程中,您只需将形式参数与调用的参数进行匹配。对于可变参数宏,标准 要求可变参数本身具有一个或多个调用参数。
...作为 gnu 扩展(您正在使用),我们可以将变化部分映射到无参数。我将其称为 null。请注意,这与 empty(和占位符标记)不同;特别是,如果我们 #define FOO(x,...)
,那么调用 FOO(z)
会将 __VA_ARGS__
设置为 null;相比之下,FOO(z,)
会将其设置为 empty.
在参数替换期间,您应用替换列表;在替换列表中,您可以用调用的参数替换形式参数。在这样做之前,任何被调用的 not 参数被字符串化并且 not 参与粘贴运算符(粘贴的左侧或右侧)已完全展开。
接下来应用字符串化和粘贴,顺序不限。
执行完上述步骤后,在重新扫描和进一步替换步骤中还会进行最后一次扫描。作为一项特殊规则,在扫描特定宏的过程中,您不再被允许展开同一个宏。标准行话是"blue paint";该宏被标记(或 "painted blue")用于此扩展。整个扫描完成后,宏为 "unpainted".
解释
让我们以您的第一个示例为例,但我将稍微更改一下:
#define UtlVbox(first, ...) Vbox(first, ##__VA_ARGS__, NULL)
#define UtlHbox(first, ...) Hbox(first, ##__VA_ARGS__, NULL)
#define foomacro Foo
UtlHbox(foomacro,UtlVbox(UtlHbox(Bar)))
这里我只是把 "C" 去掉,只关注预处理器。我还更改了调用宏 foomacro
来突出显示某些内容的调用。下面是 UtlHbox 调用的扩展方式。
我们从参数识别开始。 UtlHbox
有形式参数 first
和 ...
;调用有参数 foomacro
和 UtlVbox(UtlHbox(Bar))
。所以 first
是 foomacro
而 __VA_ARGS__
是 UtlVbox(UtlHbox(Bar))
.
接下来我们使用替换列表执行参数替换,即:
Hbox(<b>first</b>, ##<b><code>__VA_ARGS__
, NULL)
...所以我们将 first
替换为 foomacro
在 foomacro
展开后 ; __VA_ARGS__
和 UtlVbox(UtlHbox(Bar))
字面上的 。后一种情况不同,因为在这个替换列表中,__VA_ARGS__
是粘贴运算符的参与者(即右侧);因此,它不会展开。所以我们得到这个:
Hbox(Foo, ## UtlVbox(UtlHbox(Bar)))
接下来我们进行字符串化和粘贴,得到:
Hbox(Foo, UtlVbox(UtlHbox(Bar)))
接下来我们对UtlHbox
应用重新扫描和进一步替换。所以我们将 UtlHbox
涂成蓝色,然后计算该字符串。你可能已经看到自己在这里遇到麻烦了,但为了完成我会继续。
在重新扫描和进一步替换的过程中,我们发现UtlVbox
,这是另一个宏。这会产生宏 UtlVbox
.
二级参数识别中,first
为UtlHbox(Bar)
; __VA_ARGS__
是 null.
第二层参数替换,我们看UtlVbox
的替换列表,即:
Vbox(first, ##__VA_ARGS__, NULL)
由于 first
未被字符串化或粘贴,我们在替换它之前评估调用的参数 UtlHbox(Bar)
。 但是由于UtlHbox被涂成蓝色,我们无法将其识别为宏。同时,__VA_ARGS__
为空。所以我们简单地得到:
Vbox(UtlHbox(Bar), ## <i>null</i>, NULL)
在粘贴时的第二层,我们将放置标记粘贴到带有 null 的逗号右侧;这会触发逗号省略规则的 gnu 扩展,因此生成的粘贴会删除逗号,我们得到:
Vbox(UtlHbox(Bar), NULL)
第二层重扫替换,我们把UtlVbox
涂成蓝色,然后再重扫这块。由于 UtlHbox
still 涂成蓝色,因此 still 未被识别为宏。由于没有别的是宏,扫描完成。
所以退出一个级别,我们结束了这个:
Hbox(Foo, Vbox(UtlHbox(Bar), NULL))
...在继续之前,完成每个的重新扫描和替换,我们取消绘制 UtlVbox
和 UtlHbox
。
解决方案
Is there any way to solve this?
嗯,请注意有两个级别的扩展;一个发生在参数替换期间,另一个发生在重新扫描和替换期间。前者发生在蓝色油漆应用之前,并且它可以无限递归:
#define BRACIFY(NAME_) { NAME_ }
BRACIFY(BRACIFY(BRACIFY(BRACIFY(BRACIFY(Z)))) BRACIFY(X))
...将愉快地扩展到:
{ { { { { Z } } } } { X } }
这看起来像你想要做的。但是 "argument substitution" 评估仅在您的参数未进行字符串化或粘贴时才会发生。所以这里真正让你丧命的是 gnu 逗号省略功能;您对它的使用涉及将粘贴运算符应用于 __VA_ARGS__
;这使您在参数替换期间扩展的各种参数不合格。相反,它们只会在重新扫描和替换期间展开,并且在 那个 阶段,您的宏被涂成蓝色。
所以解决方案就是简单地避免省略逗号。在您的 案例中,这实际上非常简单。让我们仔细看看:
#define UtlVbox(first, ...) Vbox(first, ##__VA_ARGS__, NULL)
#define UtlHbox(first, ...) Hbox(first, ##__VA_ARGS__, NULL)
所以你想让UtlVbox(a)
变成Vbox(a, NULL)
,UtlVbox(a, b)
变成Vbox(a, b, NULL)
。那么就这样做怎么样?
#define UtlVbox(...) Vbox(__VA_ARGS__, NULL)
#define UtlHbox(...) Hbox(__VA_ARGS__, NULL)
现在这个:
UtlHbox(UtlHbox(UtlHbox(1)));
UtlHbox(UtlHbox(UtlHbox(2, 1)));
UtlHbox(UtlHbox(2, UtlHbox(1)));
UtlHbox(2, UtlHbox(UtlHbox(1)));
UtlHbox(3, UtlHbox(2, UtlHbox(1)));
UtlHbox(UtlHbox(UtlVbox(1)));
UtlHbox(UtlHbox(UtlVbox(2, 1)));
UtlHbox(UtlHbox(2, UtlVbox(1)));
UtlHbox(2, UtlHbox(UtlVbox(1)));
UtlHbox(3, UtlHbox(2, UtlVbox(1)));
UtlHbox(UtlVbox(UtlHbox(1)));
UtlHbox(UtlVbox(UtlHbox(2, 1)));
UtlHbox(UtlVbox(2, UtlHbox(1)));
UtlHbox(2, UtlVbox(UtlHbox(1)));
UtlHbox(3, UtlVbox(2, UtlHbox(1)));
UtlVbox(UtlHbox(UtlHbox(1)));
UtlVbox(UtlHbox(UtlHbox(2, 1)));
UtlVbox(UtlHbox(2, UtlHbox(1)));
UtlVbox(2, UtlHbox(UtlHbox(1)));
UtlVbox(3, UtlHbox(2, UtlHbox(1)));
...扩展为:
Hbox(Hbox(Hbox(1, NULL), NULL), NULL);
Hbox(Hbox(Hbox(2, 1, NULL), NULL), NULL);
Hbox(Hbox(2, Hbox(1, NULL), NULL), NULL);
Hbox(2, Hbox(Hbox(1, NULL), NULL), NULL);
Hbox(3, Hbox(2, Hbox(1, NULL), NULL), NULL);
Hbox(Hbox(Vbox(1, NULL), NULL), NULL);
Hbox(Hbox(Vbox(2, 1, NULL), NULL), NULL);
Hbox(Hbox(2, Vbox(1, NULL), NULL), NULL);
Hbox(2, Hbox(Vbox(1, NULL), NULL), NULL);
Hbox(3, Hbox(2, Vbox(1, NULL), NULL), NULL);
Hbox(Vbox(Hbox(1, NULL), NULL), NULL);
Hbox(Vbox(Hbox(2, 1, NULL), NULL), NULL);
Hbox(Vbox(2, Hbox(1, NULL), NULL), NULL);
Hbox(2, Vbox(Hbox(1, NULL), NULL), NULL);
Hbox(3, Vbox(2, Hbox(1, NULL), NULL), NULL);
Vbox(Hbox(Hbox(1, NULL), NULL), NULL);
Vbox(Hbox(Hbox(2, 1, NULL), NULL), NULL);
Vbox(Hbox(2, Hbox(1, NULL), NULL), NULL);
Vbox(2, Hbox(Hbox(1, NULL), NULL), NULL);
Vbox(3, Hbox(2, Hbox(1, NULL), NULL), NULL);