74

I've been using C++ and compiling with clang++. I would like to include the <xcb/xkb.h> header for an X11 program I am writing.

Unfortunately this header uses explicit for some field names (such as line 727) and that is a keyword in C++.

Is there anyway to deal with this?

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

// ...

Brian61354270
  • 8,690
  • 4
  • 21
  • 43
CLearner
  • 767
  • 4
  • 10
  • 32
    File a bug against xcb to update their header generating script. See the source [here](https://cgit.freedesktop.org/xcb/libxcb/tree/src/c_client.py). – n. m. could be an AI May 09 '22 at 20:15
  • 16
    @πάνταῥεῖ — `extern "C"` doesn’t mean “compile this code as if it were C. – Pete Becker May 09 '22 at 20:18
  • 4
    Not the first time this has happened. Early versions of the X API had a `struct` member named `new`. It needed to be renamed, breaking existing code. This is why C has stopped adding keywords, unless they begin with a double underscore or are declared by a new header file. – Davislor May 11 '22 at 00:39
  • 1
    Not posting an answer, since this is kind of a hack. You can modify the header, and rename the variable to `explicit_` or something. – BЈовић May 11 '22 at 14:01
  • 1
    @n.1.8e9-where's-my-sharem. if the xcb project doesn't support c++ then it's not a bug, why should they care about c++ rules if they only use C? – hanshenrik May 13 '22 at 04:24
  • @hanshenrik Look at their source. – n. m. could be an AI May 13 '22 at 07:13
  • I am struggling to make sense of the situation : The code says "#ifdef __cplusplus" and then uses "uint8_t explicit;" which will not compile in C++. **Was it not tested against even one C++ code base ?** If NO, fixing this variable might not solve all other issues encountered. If YES, how did it PASS ? OP should look into that & use that same Solution. – Prem May 16 '22 at 08:15

4 Answers4

80

Use a macro to rename the fields:

#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

Using a keyword as a macro name is ill-formed in standard C++, but GCC, Clang (with pragma), and MSVC do accept it. (The standard says you "shall not" do it, cppreference clarifies that it means "ill-formed".)

HolyBlackCat
  • 78,603
  • 9
  • 131
  • 207
  • In both C and C++, language keywords are not available for redefinition. It might work with a number of compilers, but compilers (or their preprocessors) are free to reject it. And the meanings of pragmas are, by definition, implementation-defined. – Peter May 10 '22 at 03:44
  • 21
    @Peter Mhm, I mentioned that at the end of the answer. Sometimes the full conformance is not practical. – HolyBlackCat May 10 '22 at 06:28
  • I don't think it's a matter of "conformance". Being allowed to reject something doesn't always mean a compiler is *required* to. (Sometimes it is, like overload resolution ambiguity IIRC, and cases where the ISO standard says a diagnostic is required. Only then is an implementation not strictly conforming if it chooses to accept it anyway and do something with it.) Anyway, I'm not sure what @Peter's point was, since your answer already mentions that caveat. If it was just confirming your memory that it's illegal in ISO C++ (or at least not guaranteed to work), of course this is impl def'd – Peter Cordes May 10 '22 at 20:56
  • 1
    @PeterCordes My comment was because the answer qualified the comment with "IIRC", and was not particularly accurate in stating "illegal". Redefining language keywords using the preprocessor results in undefined behaviour, since the standards don't actually constrain the resultant behaviour (which would be a pretty tough thing to do, in general) - and that includes not requiring diagnostics. – Peter May 11 '22 at 01:40
  • @Peter: The first half of my comment was directed at HolyBlackCat, in response to their comment; I was guessing that they thought the standard ruled out accepting this. But if it's fully UB, that's good: compilers are then allowed to define the behaviour if they wish. (Only the bit at the end was partly directed at you; it works as a clarification, but the tone seemed like an objection. Especially the part pointing out that `#pragma clang ...` is implementation defined seemed uselessly pedantic.) Anyway, thanks for the eventual clarification that ISO C++ leaves the behaviour undefined. – Peter Cordes May 11 '22 at 01:47
  • @PeterCordes I'd be surprised if a compiler vendor explicitly defined valid uses for redefining keywords. The general case is probably impossible to get right, given the preprocessor's power. And I'm skeptical that a vendor would go through the keywords and say "OK; here is a list you may redefine, but only with standard identifier tokens which are not in turn keywords or standard library identifiers or ..." In effect, given the relative independence of most preprocessors, it's trivially OK unless you do something crazy or stupid, but an official blessing is unlikely. – Peter - Reinstate Monica May 11 '22 at 06:25
  • @Peter-ReinstateMonica: Right, in practice all you're going to find is *de-facto* definition of the behaviour, by not going out of its way to break things with an implementation that works as-if by two actually-separate passes to pre-process and then compile. That still defines the behaviour, in that version of that compiler, if you look into how it's implemented to be sure it's not just a "happens to work" that could break with different source. Usually one should avoid relying on such de-facto not-breaking-your-code when possible, but here it's not like assuming an optimizer won't do xyz. – Peter Cordes May 11 '22 at 08:34
  • Which part of it is ‘illegal’? – user3840170 May 11 '22 at 15:21
  • And if you are going to use this method, you might want to put it in a separate .h file, e.g. `xcb_wrap.h` or `xcb_helper.h`. – LorenDB May 14 '22 at 23:04
28

How can I include a C header that uses a C++ keyword as an identifier in C++?

There is no standard-conforming solution to be able to include such header. In order to be able to include a header in C++, it must be written in valid C++. In case of a C header, it would thus have to be written in common subset of C and C++.

The ideal solution is to fix the header to be valid C++. A standard-conforming workaround is to write a C++ conforming wrapper in C.

eerorika
  • 232,697
  • 12
  • 197
  • 326
20

(Not an answer to the exact question, but possibly useful for the general problem of interfacing C & C++)

How about using an intermediate set of C files (.c & .h) to wrap the incompatible header and provide a C++-compatible interface to the rest of your program? If that interface is high-level enough, then you would only need to include the incompatible header file in the wrapper .c file only, allowing the wrapper .h header file to be included in C++ code normally.

Depending on the details, you might end up doing some trivial copying of data, but the resulting C interface might be much more suited to your specific needs.

thkala
  • 84,049
  • 23
  • 157
  • 201
  • How would this work in practice? Since the wrapper can't include the file it's trying to wrap. – Chuu May 11 '22 at 12:10
  • 4
    @Chuu: the wrapper is written in C, not C++. So it can call e.g. a function named `requires()` (which is a keyword in C++20), as long as that name is not exposed via its header file. It can then expose a function named `foo_requires()` in its header for use in C++. – thkala May 11 '22 at 12:21
4

One solution is to add a generation step to your makefile that copies the header and renames the field, and include said autogenerated header instead.

(This is between difficult to impossible to do in the general case, but in this case given that the header isn't likely using explicit for anything else even just a simple (gnu) sed (s/\bexplicit\b/explicit_field/g or somesuch) would work. The trickier bit is figuring out the correct file to copy.)

Make sure that the location of the copied header is before the original header in your include path, in case something else in your includes indirectly includes the header.

Just changing a field name shouldn't have any effects on e.g. ABI, at least for a nominally-C-compatible header. (There are cases in C++ where renaming a field does affect things.)

TLW
  • 1,373
  • 9
  • 22
  • a plain replacement like that is more prone to errors than macros. For example `extern some_explicit_func()` would also be unexpectedly replaced. You need to match the whole word instead – phuclv May 13 '22 at 01:15
  • I added word boundaries. Thanks! – TLW May 13 '22 at 23:30
  • "(There are cases in C++ where renaming a field does affect things.)" Can you elaborate on this? – Joseph Sible-Reinstate Monica May 15 '22 at 00:19
  • 1
    @JosephSible-ReinstateMonica - SFINAE and compile-time field detection makes things 'fun'. For instance, something having different behavior if the renamed field exists is possible. (For an example: https://godbolt.org/z/TsMTfb1M7 ). Not "typically" a problem, but can show up if people are heavily (ab)using compile-time features for e.g. serialization. (Of course you can have 'similar' rename collisions in C too, but they are typically limited to 'whoops, this failed to compile'.) – TLW May 15 '22 at 03:59