如何在可变宏参数上使用C++预处理器字符串化?

12

我猜这个问题的答案是否定的,但如果有方法的话那就太好了。为了澄清,假设我有以下宏:

#define MY_VARIADIC_MACRO(X...) // Does some stuff here in the macro definition

我想做的是在将X变量传递给可变参数函数之前对X中的所有变量进行字符串化;关键字在于“before”。我意识到在宏定义内部没有真正访问各个参数的方法,但是是否有一种方式可以将所有参数转换为字符串,例如以下内容:

#define MY_VARIADIC_MACRO(X...) some_variadic_function("some string", #X)
3个回答

22

您可以使用各种递归宏技术来处理可变参数宏。例如,您可以定义一个NUM_ARGS宏,用于计算可变参数宏的参数数量:

#define _NUM_ARGS(X100, X99, X98, X97, X96, X95, X94, X93, X92, X91, X90, X89, X88, X87, X86, X85, X84, X83, X82, X81, X80, X79, X78, X77, X76, X75, X74, X73, X72, X71, X70, X69, X68, X67, X66, X65, X64, X63, X62, X61, X60, X59, X58, X57, X56, X55, X54, X53, X52, X51, X50, X49, X48, X47, X46, X45, X44, X43, X42, X41, X40, X39, X38, X37, X36, X35, X34, X33, X32, X31, X30, X29, X28, X27, X26, X25, X24, X23, X22, X21, X20, X19, X18, X17, X16, X15, X14, X13, X12, X11, X10, X9, X8, X7, X6, X5, X4, X3, X2, X1, N, ...)   N

#define NUM_ARGS(...) _NUM_ARGS(__VA_ARGS__, 100, 99, 98, 97, 96, 95, 94, 93, 92, 91, 90, 89, 88, 87, 86, 85, 84, 83, 82, 81, 80, 79, 78, 77, 76, 75, 74, 73, 72, 71, 70, 69, 68, 67, 66, 65, 64, 63, 62, 61, 60, 59, 58, 57, 56, 55, 54, 53, 52, 51, 50, 49, 48, 47, 46, 45, 44, 43, 42, 41, 40, 39, 38, 37, 36, 35, 34, 33, 32, 31, 30, 29, 28, 27, 26, 25, 24, 23, 22, 21, 20, 19, 18, 17, 16, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1)

有了这个,你就可以编写一个FOREACH宏,用于展开列表中每个元素的另一个宏:

#define EXPAND(X)       X
#define FIRSTARG(X, ...)    (X)
#define RESTARGS(X, ...)    (__VA_ARGS__)
#define FOREACH(MACRO, LIST)    FOREACH_(NUM_ARGS LIST, MACRO, LIST)
#define FOREACH_(N, M, LIST)    FOREACH__(N, M, LIST)
#define FOREACH__(N, M, LIST)   FOREACH_##N(M, LIST)
#define FOREACH_1(M, LIST)  M LIST
#define FOREACH_2(M, LIST)  EXPAND(M FIRSTARG LIST) FOREACH_1(M, RESTARGS LIST)
#define FOREACH_3(M, LIST)  EXPAND(M FIRSTARG LIST) FOREACH_2(M, RESTARGS LIST)
        :

这将让你能够定义一个将其每个参数转换为字符串的宏:

#define STRINGIFY(X)    #X
#define MY_VARIADIC_MACRO(...)    FOREACH(STRINGIFY, (__VA_ARGS__))

我知道那些递归技巧,但正如上面所看到的,它们很繁琐,而且不幸的是,你在编译时限制了参数的数量。我很快会更新我的答案,详细介绍那个解决方案,它不那么繁琐,也没有预定义的最大限制,更易于维护。它可能是所有情况下的最佳解决方案,但对于我需要的东西,它比上面的答案更好。谢谢你的建议! - Hazok
第二个代码块末尾的冒号有什么特殊含义吗? - miguel
那是一个竖直省略号,用于省略许多更多的“FOREACH_”宏(可能高达“FOREACH_100”),以允许如此大的循环。 - Chris Dodd

11

好的,我本来不想自己回答这个问题,但是我想出了一个还算不错的解决方案,有点像Mark Wilkins的回答和我在问题中提到的例子的结合。

可以将整个可变集合(variadic set)转换为字符串,这样字符串中就包括了分隔逗号。以下是一个快速示例:

#define MY_VARIADIC_MACRO(X...) printf(#X)

使用上述宏可以让您知道传递给宏的整个参数集合被字符串化。

之后,您可以定义一个函数来使用逗号作为分隔符对这些参数进行标记化,从而通过使用可变宏获得一组标记化的字符串:

#define MY_VARIADIC_MACRO(X...) tokenize_my_arguments(#X)

现在,实际上不再需要将可变参数宏调用可变参数函数,我可以很好地遍历我的常量C字符串数组,而不是通过va_arg进行迭代。

*新编辑中的新内容*

根据Tim的评论,以下是解决方案的详细信息。由于匆忙完成并且必须从我正在处理的内容中移植,因此请谅解任何错误。此外,它不是粘贴解决方案,因为它仅输出参数的字符串化,以演示POC,但应足以演示功能。

尽管此解决方案需要一些运行时计算,但可变参数宏通常调用可变参数函数并需要通过va_args进行迭代,因此迭代发生在查找标记时,尽管可能会牺牲一些性能。然而,考虑到可维护性、通用性和易于实现,这似乎是目前最佳选择:

#define VARIADIC_STRINGIFY(_ARGUMENTS_TO_STRINGIFY...) Variadic_Stringification_Without_Variadic_Function(#_ARGUMENTS_TO_STRINGIFY)

void Variadic_Stringification_Without_Variadic_Function (const char* _stringified_arguments)
{
    strcpy(converted_arguments, _stringified_arguments);

    for(char* token = strtok(converted_arguments, ","); token != 0x0; token = strtok(0x0, ","))
        std::cout << token << std::endl;
}

此外,这个功能的另一个好处是我可以轻松地找到参数的数量,而不必通过宏传递某种类型的信息来确定列表中参数的结束位置。 - Hazok
有趣的问题,但在将其标记为解决方案之前,请发布您实际解决此问题的代码。我相信这对其他人也会很有用。 - Tim
@Tim 好的,我已经测试了一个简单的案例,并将其制作成更好的示例...有一些期限,我会在明天(2011年5月11日)或后天发布。 - Hazok
@Tim 已更新详细信息。 - Hazok
+1. 看起来是个不错的解决方案。我没有意识到 C99 支持可变参数宏,太好了。我正在使用一个不支持它的微软编译器(至少不支持那种形式)。 - Mark Wilkins

3

这可能与您想要的不太相关,但以下类似的内容可能会帮到您:

#define MY_VARIADIC_MACRO(X) printf( "%s\n", #X )

接下来,您可以按以下方式使用它。将参数括在括号中,以便它显示为宏的一个参数。

MY_VARIADIC_MACRO(( var1, var2, "string" ));

然后,您可以让宏调用一些函数来去除外部括号或可能解析给定字符串。


这个几乎快到位了,但是要的是不需要括号...可恶的回车太快了,需要记住Shift-Return。我认为上面的答案非常接近我正在努力寻找的解决方案。我会给它投票支持,但无法将其标记为主要答案,因为它并不能完全满足要求。 - Hazok
@Zach:我也猜到了。我很想看看怎么做。我的理解是这不可能,但我希望被证明是错的。 - Mark Wilkins
我在上面发布了我的答案,我会采用它,感到非常兴奋,解决了我面临的很多问题。 - Hazok

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接