如何在C++中包含使用C++关键字作为标识符的C头文件?

74

我一直在使用C ++并使用clang ++进行编译。我想为我正在编写的X11程序包含<xcb / xkb.h>头文件。

不幸的是,此标题使用了explicit作为一些字段名称(例如第727行),而这是C ++中的关键字。

有没有办法解决这个问题?

xcb / xkb.h

// ...

#ifdef __cplusplus
extern "C" {
#endif

// ...

typedef struct xcb_xkb_set_explicit_t {
    xcb_keycode_t keycode;
    uint8_t       explicit;
} xcb_xkb_set_explicit_t;

// ...

#ifdef __cplusplus
}
#endif

// ...


32
针对 xcb 更新它们的头文件生成脚本,请提交一个 bug。在此处查看源代码:https://cgit.freedesktop.org/xcb/libxcb/tree/src/c_client.py。 - n. m.
16
@πάνταῥεῖ — extern "C" 不代表“将此代码编译为 C 语言”。请注意,此关键字仅用于指定特定函数应该使用 C 链接约定进行编译,而不是 C++ 链接约定。 - Pete Becker
4
这不是第一次发生了。早期版本的 X API 中有一个名为 new 的结构体成员,需要重命名,这导致现有代码出现问题。这就是为什么 C 语言已经停止添加关键字,除非以双下划线开头或由新的头文件声明。 - Davislor
1
不发布答案,因为这有点像黑客。您可以修改标题,并将变量重命名为 explicit_ 或其他名称。 - BЈовић
1
如果XCB项目不支持C++,那么这不是一个错误,如果他们只使用C,为什么他们要关心C++规则呢? - hanshenrik
显示剩余2条评论
4个回答

80

使用宏来重命名字段:

#ifdef __clang__
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wkeyword-macro"
#endif
#define explicit explicit_
#ifdef __clang__
#pragma clang diagnostic pop
#endif

#include <xcb/xkb.h>

#undef explicit

在标准的C++中,将关键字用作宏名称是不符合规范的,但GCC、Clang(使用#pragma)和MSVC可以接受。 (标准规定你“不应该”这样做,cppreference 澄清道这意味着“不符合规范”。)


21
@Peter Mhm,我在回答的结尾提到了这一点。有时候完全符合标准是不切实际的。 - HolyBlackCat
我认为这不是“符合性”的问题。 允许拒绝某些内容并不意味着编译器总是必须这样做。(有时会,例如重载解析歧义,以及ISO标准规定需要执行诊断的情况。只有在这种情况下,如果它选择接受该内容并执行操作,则实现才不是严格符合标准。)总之,我不确定@Peter的观点是什么,因为你的回答已经提到了这一点。如果他只是确认您的记忆,在ISO C++中这是非法的(或者至少不能保证有效),那么这当然是impl def'd。 - Peter Cordes
1
@PeterCordes 我的评论是因为回答中使用了“我记得”的限定词,并且在陈述“非法”时并不特别准确。使用预处理器重新定义语言关键字会导致未定义的行为,因为标准实际上并没有限制结果行为(这通常是一件相当困难的事情),包括不要求诊断。 - Peter
它的哪一部分是“非法”的? - user3840170
如果您要使用此方法,建议将其放在单独的.h文件中,例如xcb_wrap.hxcb_helper.h - LorenDB
显示剩余4条评论

28
如何在C++中包含一个使用C++关键字作为标识符的C头文件?
没有符合标准的解决方案可以包含这样的头文件。为了能够在C++中包含头文件,它必须是有效的C++代码。对于C头文件来说,它必须被写成C和C++的公共子集。
最理想的解决方案是修复该头文件以符合C++标准。一种符合标准的解决方法是在C++中编写符合C++标准的C++包装器(wrapper)。

20

(这不是对于确切问题的答案,但对于 C 和 C++ 接口的通用问题可能有用)

你可以尝试使用一个中间的 C 文件(.c 和 .h)来包装不兼容的头文件,并为你程序的其余部分提供一个 C++ 兼容的接口。如果该接口足够高级,则只需要在包装器 .c 文件中包含不兼容的头文件,以便 C++ 代码可以正常地包含包装器 .h 头文件。

根据具体情况,您可能需要进行一些简单的数据复制,但生成的 C 接口可能更适合您的特定需求。


这在实践中会如何运作?因为包装器无法包含它试图包装的文件。 - Chuu
4
@Chuu:这个封装器是用C编写的,而不是C++。因此,只要该名称未通过其头文件公开,它就可以调用例如名为requires()的函数(这是C ++20中的关键字)。然后,它可以在其头文件中公开一个名为foo_requires()的函数供C ++使用。 - thkala

4

一种解决方法是在您的 makefile 中添加一个生成步骤,将头文件复制并重命名字段,并包含该自动生成的头文件。

(在一般情况下这个做法很难甚至不可能实现,但在这种情况下,考虑到头文件不太可能对其他内容使用 explicit,即使只是简单的 (gnu) seds/\bexplicit\b/explicit_field/g 或类似的操作)就可以起作用。更棘手的部分是找出要复制的正确文件。)

确保复制的头文件的位置在您的 include 路径中原始头文件的位置之前,以防其他包含间接包含了该头文件。

仅更改字段名称不应对例如 ABI 产生任何影响,至少对于名义上兼容 C 的头文件来说。(在 C++ 中有些情况下重命名字段会影响某些事情。)


一个简单的替换比宏更容易出错。例如,extern some_explicit_func()也会意外地被替换。你需要匹配整个单词。 - phuclv
我添加了单词边界。谢谢! - TLW
(在C++中,有时重命名字段确实会影响事情的发生。)你能详细说明一下吗? - Joseph Sible-Reinstate Monica
1
@JosephSible-ReinstateMonica - SFINAE和编译时字段检测让事情变得“有趣”。例如,如果重命名的字段存在,则可能会出现不同行为的情况。(例如:https://godbolt.org/z/TsMTfb1M7)。这通常不是问题,但如果人们过度(滥用)编译时功能进行序列化等操作,则可能会出现问题。(当然,在C中也可以有类似的重命名冲突,但它们通常仅限于“糟糕,这无法编译”。) - TLW

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