According to §7.2/5 and §7.2/6 shouldn't the code below print 1 1
instead of 4 4
?
#include <iostream>
enum A { a = (char)1, b, c }; // underlying type is not fixed
int main() {
std::cout << sizeof(a) << ' ' << sizeof(A) << '\n';
}
Edit
From §7.2/5:
If the underlying type is not fixed, the type of each enumerator is the type of its initializing value:
— If an initializer is specified for an enumerator, the initializing value has the same type as the expression and the constant-expression shall be an integral constant expression (5.19).
If you don't explicitly define the underlying type, then compiler is free to choose an integral type which fits the values. To set the underlying type in C++11 you can use this:
enum A : char { a = 1, b, c };
^^^^^^
Your way will not force the compiler to use char
instead if int
.
This is implementation defined: the fact that all values of an enum
fit in, say, a uint8_t
does not force the compiler to pick a single-byte representation for the enumeration.
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
. (emphasis added)
In your case it appears that the compiler implementers choose an int
, which takes four bytes on your platform - a perfectly valid choice.
No. It has been the case ever since ANSI C that conforming compilers often use int
to store enums, even when all the values are small.
Before you say this is insane and it should use the smallest type that works (which by the way GCC will do if you use __attribute__((packed))
), think about ABI compatibility. If you release a library which uses an enum type, you would prefer that the size of that type not change. If all enums start life with 4 bytes, the likelihood is increased that simply relinking against an updated library will work.
The clause you quote, 7.2/5, describes the types of the enumerators. But the enumerators only form part of the definition of the enumeration. The underlying type of the enumeration is large enough to hold the values all enumerators, subject to 7.2/6:
It is implementation-defined which integral type is used as the underlying type except that the underlying type shall not be larger than
int
unless the value of an enumerator cannot fit in anint
orunsigned int
.
So it is guaranteed that your underlying type is no larger than int
(since int
can represent 0, 1 and 2). It is true that the type of your first enumerator is char
inside the enum definition, but all actual enum values are of type A
. To actually control the underlying type, use the enum-base syntax (e.g. enum A : char
), and to query it you can use the std::underlying_type
trait.
If you actually would like to see the effect of the enumerator's type in the definition, you can try something like this:
enum Foo { a = '\010', b = sizeof(a) };
std::cout << typeid(b).name() << "\n"; // some variant of "Foo"
std::cout << b << "\n"; // "1"
std::cout << sizeof(b) << "\n"; // implementation-defined, not greater
// than sizeof(int)
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With