Re: GLogLevelFlags enum and g_log

Sorry to respond late on this. Let's look at what the standard actually says and then figure out where the difference matters. Bold in the standard quotes is mine.

First, C enums are different from C++ enums. From C99 secdion

Each enumerated type shall be compatible with char, a signed integer type, or an unsigned integer type. The choice of type is implementation defined, but shall be capable of representing the values of all the members of the enumeration.

From C++03, section 7.2/5:

The underlying type of an enumeration is an integral type that can represent all the enumerator values defined in the enumeration. It is implementation-defined which integral type is used as the underlying type for an enumeration except that the underlying type shall not be larger than int unless the value of an enumerator cannot fit in an int or unsigned int. If the enumerator-list is empty, the underlying type is as if the enumeration had a single enumerator with value 0. The value of sizeof() applied to an enumeration type, an object of enumeration type, or an enumerator, is the value of sizeof() applied to the underlying type.

So in both cases an enum can be smaller than an int according to implementation-defined criteria. The more substantive difference is that in C++ an enum can be larger than an int.

There are very few places where this creates problems in practice.

In practice, the "smaller than int" issue isn't a concern at parameter passing, where parameters are widened to a full machine word in any case. Parameter passing can be an issue for "larger than int" parameters when prototypes are not declared. It's exactly the same problem as passing long long where an int is expected. Since we all use prototypes in C++ these days (we do, right?), that's not a big issue in practice.

In addition, both C and C++ have silent widening and narrowing of integral types, so if a small enumeration gets widened to an int somewhere and then gets stored back into a variable having enumerated type all will be well so long as no "stray" bits have been set.

Then there are structure alignment and offset assumptions. The right solution to that generally is to make use of the standard macros alignof(), sizeof(), and offsetof() pervasively and let the compiler solve those. I don't see (offhand) what value the static assertion about enumeration size is giving us in regards to this issue, since the real problem is that the code hasn't used the right portability mechanism more generally.

Next there are "unholy overlays", by which I mean places where somebody has tucked items of unspecified size and alignment into a union from which they try to re-extract them using some other "leg" of the union. THAT's just bad code, and it needs to be fixed as we find it.

Finally, we get to the enum extension issue that started the present discussion. That's a lurking ABI compatibility problem. And a subtle one. Nice catch!

The proposed static assertion will at least serve to tell us if we ever encounter a compiler that might trigger the unholy union problem and/or the "hidden layout assumptions" problem and/or the enum extension problem. So that's all good.

Meanwhile, there is a preemptive way to force the compiler to choose "at least int" as the enum size:

enum GLogLevel {
   ... existing, expected values ...
   _require_int = INT_MAX

This works because the compiler is now required to choose an underlying integral type that will hold INT_MAX. The problem with doing this is that it will break ABI compatibility on any platform that currently encodes GLogLevel as a char. I'm actually not aware of any compiler that does this, but it would probably be a good idea to check before making this change and accidentally breaking things.


[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]