如何使预处理器宏变得贪婪?

How to make a preprocessor macro greedy?

我们有以下预处理器宏。它用于帮助 Doxygen 文档,因为 Doxygen 在 C++ 和一些模板类型定义方面存在问题:

#if defined(DOXYGEN_PROCESSING)
# define DOCUMENTED_TYPEDEF(x, y) class y : public x {};
#else
# define DOCUMENTED_TYPEDEF(x, y) typedef x y;
#endif

X 是非模板或只有一个模板参数时效果很好。但是,如果 X 是一个有多个参数的模板:

DOCUMENTED_TYPEDEF(Foo<R,S>,Bar);

然后它会导致编译错误,因为字符串被拆分为 Foo<RS>,Bar(并且它不会生成文档)。

如何使预处理器宏变得贪婪?

无法更改预处理器解析宏参数的方式。不在括号内的逗号始终分隔宏参数。

可能能做的是

DOCUMENTED_TYPEDEF((Foo<R,S>), Bar);

当然,这只有在内部括号可以出现在宏的扩展中时才有效。我不记得这是否会在您显示的上下文中引起问题。

如果可以要求 C99 可变参数宏,您可以使用它们来去掉多余的括号:

#define STRIP_PARENS(...) __VA_ARGS__
#if defined(DOXYGEN_PROCESSING)
# define DOCUMENTED_TYPEDEF(x, y) class y : public STRIP_PARENS x {};
#else
# define DOCUMENTED_TYPEDEF(x, y) typedef STRIP_PARENS x y;
#endif

DOCUMENTED_TYPEDEF((Foo<R,S>), Bar);

但现在您总是 必须在 DOCUMENTED_TYPEDEF.

的第一个参数周围放置一对额外的括号

你不会喜欢这个的:

#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;

在进行任何替换之前,宏参数列表会针对括号和逗号进行解析。然后COMMAx参数中被替换,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)

乍一看比较吸引人,但可能也有缺点。它更加混乱。 reader 想知道,PAIR 背后是否有语义?然而 COMMA 看起来太迟钝而没有语义,而且它的目的对于任何因与预处理器战斗而留下伤疤的人来说可能立即显而易见。

关于 PAIR,我们也许可以隐藏它,并以 Zwol 的回答中的语法结束。但是我们需要 DOCUMENTED_TYPEDEF.

的多个变体

另外,顺便把宏右边不需要的没用的COMMA去掉:

#define PAIR(A, B) A, B

#if defined(DOXYGEN_PROCESSING)
# define DOCUMENTED_TYPEDEF_2(x2, y) class y : public PAIR x2 {};
#else
# define DOCUMENTED_TYPEDEF_2(x2, y) typedef PAIR x2 y;
#endif

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 "<built-in>"
# 1 "<command-line>"
# 1 "comma-macro.c"
# 9 "comma-macro.c"
typedef <R, S, T, L, N, E> Bar;