I came across some unusual (to me at least...) behavior with C++'s enums. I've tried the following in Visual Studio 2008 and g++ version 4.4.3
#include <iostream>
using namespace std;
enum testEnum
{
// no zero enum
one = 1,
two = 2,
three = 3
};
int main(int argc, char *argv[])
{
testEnum e; // undefined value (may be zero, but thats just luck)
cout << "Uninitialized enum e = " << e << endl;
/*
testEnum e2(0); // error converting from int to enum
*/
testEnum e3(testEnum(0)); // forces zero !?!!?!?
cout << "zero enum e3 = " << e3 << endl; // prints '0'
testEnum e4(testEnum(9999)); // forces 9999 ?!?!?!?!
cout << "9999 enum e4 = " << e4 << endl; // prints '9999'
return 0;
}
The undefined value for e is as I expected, and I understand why you can't convert from an int to an enum (but you can go the other way).
I'm curious as to how the last two enums (e3 and e4) are allowed to compile and attain any value you want to give them.
Also, I found that this:
testEnum e();
Compiled in both studio and linux, and cout-ing in linux yielded '1' but in studio I got a linker error:
main.obj : error LNK2001: unresolved external symbol "enum testEnum __cdecl e2(void)"
In studio, I could do:
testEnum e = testEnum();
But cout-ing it yielded '0' not '1'
So my main question is how you can ram any value down an enum's throat like e3 and e4 in the above example. And if this is implementation dependent or not.
An enum is guaranteed to hold any value in the range between its smallest defined value and its largest, and is unspecified on how it handles numbers outside the range.
§7.2/3
...if not explicitly specified, the underlying type of a scoped enumeration type is int."§ 7.2/7
...for an enumeration where emin is the smallest enumerator and emax is the largest, the values of the enumeration are the values in the range bmin to bmax, defined as follows: Let K be 1 for a two’s complement representation and 0 for a one’s complement or sign-magnitude representation. bmax is the smallest value greater than or equal to max(|emin| − K, |emax|) and equal to 2M − 1, where M is a non-negative integer. bmin is zero if emin is non-negative and −(bmax + K) otherwise. The size of the smallest bit-field large enough to hold all the values of the enumeration type is max(M, 1) if bmin is zero and M + 1 otherwise.§ 7.2/10
An expression of arithmetic or enumeration type can be converted to an enumeration type explicitly. The value is unchanged if it is in the range of enumeration values of the enumeration type; otherwise the resulting enumeration value is unspecified.
Also note that testEnum e();
declares a function that returns a testEnum
This is caused by the Most Vexing Parse. Not sure why GCC decided that equaled one (maybe it got coerced into a pointer->bool somehow?) testEnum e = testEnum();
is a valid statement which default-initializes an testEnum
, which for primitives (and apparently enums
) means setting them to zero.
Note that this does not mean that enumerations may not take values outside of their defined range; as noted in footnote 96 of N4296:
This set of values is used to define promotion and conversion semantics for the enumeration type. It does not preclude an expression of enumeration type from having a value that falls outside this range.
I should also mention that it isn't particularly complicated to make a typesafe enum in C++03, there's just a few wierd tricks to it. http://en.wikibooks.org/wiki/More_C%2B%2B_Idioms/Type_Safe_Enum shows one way.
C++ enums are not type-safe. You can, as you say, ram any value down an enums throat. This feature is not implementation dependent, it is enshrined in the standard.
C++11 introduces strongly-typed enumerations which are declared with a different syntax so as not to break your old code with type unsafe enumerations.
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