如何强制额外的预处理器宏扫描

Posted

技术标签:

【中文标题】如何强制额外的预处理器宏扫描【英文标题】:How to force additional preprocessor macro scan 【发布时间】:2017-10-13 13:32:57 【问题描述】:

我有以下代码:

#define FOO_BAR x
#define FOO(x) FOO_BAR

我确实希望 FOO(2) 扩展到 2,但我得到的是 x。我尝试使用EXPAND 宏来强制进行额外扫描:

#define FOO_BAR x
#define EXPAND(x) x
#define FOO(x) EXPAND(FOO_BAR)

注意,这是故意的,FOO_BAR 不接受 x 作为参数。基本上,我不能将 x 传递给FOO_BAR

但它也不起作用。有什么想法吗?

我希望它适用于任何编译器(MSVC、gcc、clang)。

我到底想要完成什么

我的最终目标是为 OpenGL 创建类型安全的枚举。所以,我需要从我的安全枚举映射到不安全的枚举。所以我有类似的东西:

enum class my_enum 
    foo,
    bar


GLenum my_enum2gl(my_enum e) 
    switch (e) 
    case my_enum::foo: return GL_FOO;
    case my_enum::bar: return GL_BAR;
    
    return GL_NONE;

由于我很懒,我做了一些preprocessor magic。并将其实现为:

#define PP_IMPL_ENUM_VALUE(enum_pair) __PP_EVAL(__PP_IMPL_ENUM_VALUE enum_pair) 
#define __PP_IMPL_ENUM_VALUE(cpp_enum, gl_enum) cpp_enum,

#define PP_IMPL_CONVERT(enum_pair) __PP_EVAL(__PP_IMPL_CONVERT enum_pair) 
#define __PP_IMPL_CONVERT(cpp_enum, gl_enum) case name::cpp_enum: return gl_enum;

#define DEF_STATE_ENUM(name, ...)                       \
    enum name                                          \
        PP_FOR_EACH(PP_IMPL_ENUM_VALUE, ##__VA_ARGS__)  \
    ;                                                  \
    namespace detail                                   \
    GLenum name ## 2gl(name e)                         \
        switch(e)                                      \
            __PP_EVAL(PP_FOR_EACH(PP_IMPL_CONVERT, ##__VA_ARGS__)) \
        default:                                        \
            assert(!"Unknown value");                   \
            return GL_NONE;                             \
                                                       \
                                                       \


DEF_STATE_ENUM(my_enum,
    (foo, GL_FOO),
    (bar, GL_BAR)
)

问题在于__PP_IMPL_CONVERT 使用了未展开的name。将x 传递给FOO_BAR 意味着我将一些额外的参数传递给PP_FOR_EACH 的函子。

【问题讨论】:

我唯一能建议的是使用一些外部预处理器。您不能在 C 中执行此操作。 你得到 x 因为那是你定义的 FOO_BAR 。您不能将无参数宏扩展为要作为参数传入的值。 C != C++。仅使用您正在使用的语言进行标记,除非两者确实相关。 @tambre,两者都是相关的,我很确定任何答案都适用于两种语言。 你的问题是问如何做一些不可能做的事情; FOO 中的 x 是一个参数(在参数替换期间扩展),FOO_BAR 中的 xjust x,两者永远不会混合。 Boost 预处理器的FOR_EACH 构造每个都采用非常适合此用例的数据参数。如果你有一个PP_FOR_EACH 变体可以做到那个,你可以在这里使用它。你愿意只使用 boost 预处理器库吗? 【参考方案1】:

你需要了解

    预处理器在将参数替换为宏的扩展之前将参数完全扩展为每个类似函数的宏,除非它们是 ### 预处理运算符的操作数(在这种情况下,它们不会被扩展完全)。

    通过执行宏扩展修改输入预处理标记序列后,预处理器自动重新扫描结果以执行进一步的宏扩展。

那么,在你的主要例子中,给定

#define FOO_BAR x
#define FOO(x) FOO_BAR

和宏调用

FOO(2)

,预处理器第一个宏扩展参数2,保持不变,然后用它的扩展替换宏调用。由于展开式实际上并没有首先使用参数,所以初始结果是

FOO_BAR

然后预处理器重新扫描它,将FOO_BAR识别为类对象宏的标识符,并用它的扩展替换它,产生

x

,正如您所观察到的。这是符合标准的 C 预处理器的正常和预期行为,据我所知,C++ 对其预处理器具有等效规范。

插入一个EXPAND()宏调用也无济于事,因为问题不是扩展宏失败,而是宏扩展的时间和上下文。归根结底,当宏 FOO(x) 的替换文本不使用宏参数 x 时,与该参数关联的实际参数对扩展结果没有影响,这应该不足为奇。

我无法完全解决您的实际代码,因为它集中依赖于您未提供其定义的宏 PP_FOR_EACH()。大概该宏的名称传达了要点,但正如您所见,细节很重要。但是,如果您实际上了解您的 PP_FOR_EACH 宏实际上是如何工作的,那么我敢打赌您可以想出一个接受附加前导参数的变体,通过它您可以将(相同的)name 传达给每个扩展。

或者,这是X Macros 发明的那种问题。我看到 cmets 已经提出了这种替代方案。您甚至可以(稍加注意)构建一个使用 X 宏inside 的解决方案,以保留您现在拥有的***界面。

【讨论】:

以上是关于如何强制额外的预处理器宏扫描的主要内容,如果未能解决你的问题,请参考以下文章

仅为某些架构定义预处理器宏

如何从预处理器宏中识别平台/编译器?

#if 和 #ifdef Objective-C 预处理器宏有啥区别?

预处理器宏更改两个目标

如何在 Rust 的 FFI 中使用 C 预处理器宏?

Xcode:测试与调试预处理器宏