【问题标题】:How to make a preprocessor macro greedy?如何使预处理器宏贪婪?
【发布时间】:2016-07-30 03:40:11
【问题描述】:

我们有以下预处理器宏。它用于帮助 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&lt;R 和S&gt;,Bar(并且它不会生成文档)。

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

【问题讨论】:

    标签: c++ macros c-preprocessor doxygen greedy


    【解决方案1】:

    你不会喜欢这样的:

    #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 逗号宏.c # 1 "逗号宏.c" # 1 "" # 1 "" # 1 "逗号宏.c" # 9 "逗号宏.c" typedef Foo 酒吧;

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

    #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 "逗号宏.c" # 1 "" # 1 "" # 1 "逗号宏.c" # 11 "逗号宏.c" typedef 条形;

    这看起来可以用 C99 风格的可变参数宏来实现。但是,这可能违反了 cmets 中讨论的可移植性要求,更不用说这是 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 "逗号宏.c" # 1 "" # 1 "" # 1 "逗号宏.c" # 9 "逗号宏.c" typedef 条形;

    【讨论】:

    • 那太丑了。我可以一直指望非贪婪匹配吗?如果是这样,我正在考虑反转X 和Y。棘手的一点似乎是:明确定义的内容与定义的实现内容。我需要它在几乎所有平台的编译器上工作,并且可以追溯到 1990 年代。这是一种特殊的地狱。
    • 预处理器会破坏没有用括号括起来的逗号上的宏参数列表。 (它计算括号标记,以便它们平衡)。方括号、大括号或尖括号无法识别;它们不为逗号提供保护。这与贪婪关系不大。
    • 关于可移植性,我只使用了C90预处理器语法。
    【解决方案2】:

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

    你可能能做的是

    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 的第一个参数周围放置一对额外的括号。

    【讨论】:

    • 我怀疑它会导致问题,因为左边的参数 X 被放在声明说明符的位置。在 C 和 C++ 样式声明中,声明说明符不带可选括号。但是,声明符会:int x; 和 int (x); 是同一回事。 (int) x; 看起来像演员表。
    • @Kaz 你能不能忍受 always 在可能包含模板参数的参数周围加上括号,and 只支持实现 C99 可变参数宏的编译器?如果是这样,那么有一个可行的方法。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2015-04-15
    • 1970-01-01
    • 2015-07-31
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多