8

I'm creating a dll + supporting header for a C library; which then has a C++ implementation.
The header file exposes an enum, returned by some of the functions. Is this example guaranteed to produce ABI compatible libraries?

#ifdef __cplusplus
#define ENUM_DECL enum class
#else
#define ENUM_DECL enum
#endif

extern "C" {
    typedef ENUM_DECL Example { value1} Example;
}

Or will the class part of the declaration result in name mangling?

My understanding is that enum and enum class should be syntactical wrappers around an int (or the specified type if given) meaning that it shouldn't matter which one is used except to the usage of it.

Having built it, and used dependency walker to view the dll, I found that it uses the header to establish the return value; which isn't going to help demonstrate if it's compatible.

Naturally, I've asked a couple of AI's to help answer, and one reported that it was ABI compatible, and one said it wasn't.

8
  • 5
    Even if at the moment under the hood enum and enum class are implemented the same, they are not the same thing. Why not use a plain C enum only (at least for the interface) ? Commented Sep 3 at 12:01
  • The enum is to used throughout the implementation; and the whole gain of enum class is to protect against accidental casting. The implementation (being c++) would gain from that casting protection. Commented Sep 3 at 12:47
  • 2
    The C linkage declaration in that header fragment is not C compatible. It needs to be restricted to use when compiled as C++. Commented Sep 3 at 15:14
  • 9
    "Naturally, I've asked a couple of AI's to help answer" -- I don't think that's natural at all. It is deeply problematic to query an AI for responses that you are not prepared to validate yourself. You experienced one of the possible problems with that. Commented Sep 3 at 15:16
  • Closely related: ABI compatibility preservation with C++11 enum class. I hesitate to VTC as a dupe, however, because I'm not really satisfied with the answers to that. Commented Sep 3 at 15:32

2 Answers 2

5

In and later, you can specify the underlying type of an enum:

enum color : unsigned char

you can do the same for an enum class in

enum class color : unsigned char

An enum and an enum class with the same underlying type is in any sane implementation going to be ABI compatible. In both cases, they are guaranteed to be binary-compatible (size and layout) with the underlying type.

Without a specified underlying type, the compiler gets a lot of freedom as to what the underlying type is (in C++ at least it is intentionally underspecified). I wouldn't trust two different compilers, let alone a C and a C++ compiler, to guarantee the same result (maybe even if it was an enum in both cases).

Regardless, in any kind of stable ABI, you should be specifying the underlying type. Among other things this prevents the addition of new enumations that change it.

Sign up to request clarification or add additional context in comments.

1 Comment

Since the size can only be specified in C23 onwards - and "Regardless, in any kind of stable ABI" - it sounds like if there's any requirement to support any previous version of the C standard, one simply can not use enums to make ABI compatible code; which would force something like typedef colour int32_t + #define RED 0. Is that a fair statement?
2

The C23 standard (§6.2.7) states:

Moreover, two complete [...] enumerated types declared with the same tag are compatible if members satisfy the following requirements:

  • there shall be a one-to-one correspondence between their members such that each pair of corresponding members are declared with compatible types;
  • if one member of the pair is declared with an alignment specifier, the other is declared with an equivalent alignment specifier;
  • and, if one member of the pair is declared with a name, the other is declared with the same name

Additionally, §6.7.5.3.7 requires

If two enum specifiers that include an enum type specifier declare the same type, the underlying types shall be compatible.

By my understanding, an enum class constant Foo::bar and a C-style enumeration constant bar do not meet the same-name requirement, making the definitions formally incompatible.

Note that code that relies on the compiler deducing an underlying type may also not be portable. For example, for the declaration

enum binary_sizes {
    kibi =         0x400,
    mebi =      0x100000,
    gibi =    0x40000000,
    tebi = 0x10000000000
};

The underlying type might be long long int on an LLP64 implementation, long int on a LP64 implementation, or even int on an ILP64 implementation. Other examples would be deduced as different sizes on different platforms.

A pragmatic solution might be to declare the C enum in the C++ header, give the C++ wrapper a slightly-different tag, and add both a converting constructor from the C enum to the C++ class and from the C++ class to the C enum. This will allow them to be used nearly interchangeably in your C++ code. So you would be able to pass either value1 or Example::value1 to a function that expects either the enum or the enum class. Or you could use an explicit static_cast instead, without writing any additional code.

Comments

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.