您可能不会喜欢这个消息:
#define COMMA ,
#if defined(DOXYGEN_PROCESSING)
# define DOCUMENTED_TYPEDEF(x, y) class y : public x {};
#else
# define DOCUMENTED_TYPEDEF(x, y) typedef x y;
#endif
DOCUMENTED_TYPEDEF(Foo<R COMMA S>,Bar)
测试:
$ gcc -E comma-macro.c
# 1 "comma-macro.c"
# 1 "<built-in>"
# 1 "<command-line>"
# 1 "comma-macro.c"
# 9 "comma-macro.c"
typedef Foo<R , S> Bar;
在任何替换发生之前,宏参数列表都会先进行括号和逗号的解析。然后再将 x
参数中的 COMMA
替换,并将 x
替换到宏体中。此时,参数已经断开;COMMA
被替换为逗号标点符号不再具有相关性。不过,该逗号将会分隔由该宏生成的任何宏调用中出现的参数,因此如果这些参数需要受到保护,则需要使用更多的技巧。
你可以使用函数宏来隐藏
COMMA
,例如使用
PAIR
:
#define COMMA ,
#define PAIR(A, B) A COMMA B
#if defined(DOXYGEN_PROCESSING)
# define DOCUMENTED_TYPEDEF(x, y) class y : public x {};
#else
# define DOCUMENTED_TYPEDEF(x, y) typedef x y;
#endif
DOCUMENTED_TYPEDEF(PAIR(Foo<R, S>), Bar)
乍一看更有吸引力,但可能存在缺点。它更加模糊。读者会想知道
PAIR
后面是否有语义?而
COMMA
看起来过于晦涩难懂,对于那些与预处理器斗争过的人来说,其目的很可能是显而易见的。
关于
PAIR
,我们可以隐藏它,并最终得到类似于Zwol答案中的语法。但是,我们需要多个
DOCUMENTED_TYPEDEF
的变体。
另外,顺便说一下,让我们放弃无用的
COMMA
,因为在宏的右侧不需要它。
DOCUMENTED_TYPEDEF_2((<R, S>), Bar)
$ gcc -std=c90 -E -Wall -pedantic comma-macro.c
# 1 "comma-macro.c"
# 1 "<built-in>"
# 1 "<command-line>"
# 1 "comma-macro.c"
# 11 "comma-macro.c"
typedef <R, S> Bar;
这看起来可能可以使用 C99 风格的可变宏来完成。然而,这可能会违反评论中讨论的可移植性要求,更不用说这是 C++ 了。为了将来的读者,请注意:
#define PNEUMATIC_COMMA_GUN(A, ...) A, ## __VA_ARGS__
#if defined(DOXYGEN_PROCESSING)
# define DOCUMENTED_TYPEDEF(xv, y) class y : public PNEUMATIC_COMMA_GUN xv {};
#else
# define DOCUMENTED_TYPEDEF(xv, y) typedef PNEUMATIC_COMMA_GUN xv y;
#endif
DOCUMENTED_TYPEDEF((<R, S, T, L, N, E>), Bar)
$ gcc -std=c99 -E -Wall -pedantic comma-macro.c
# 1 "comma-macro.c"
# 1 "<内置>"
# 1 "<命令行>"
# 1 "comma-macro.c"
# 9 "comma-macro.c"
定义了一个变量Bar,类型为<R, S, T, L, N, E>,涉及到的技术为it。
X
和Y
。棘手的问题似乎是:什么是明确定义的,而什么是实现定义的。我需要它在几乎所有平台上的编译器上都能工作,并回退到 1990 年代。这是一种特殊的地狱。 - jww