tags:

views:

130

answers:

4

I would expect the following code snippet to complain about trying to assign something other that 0,1,2 to a Color variable. But the following does compile and I get the output

Printing:3

3

Can anybody explain why? Is enum not meant to be a true user-defined type? Thanks.

enum Color { blue=0,green=1,yellow=2};

void print_color(Color x);

int main(){
    Color x=Color(3);

    print_color(x);
    std::cout << x << std::endl;

    return 0;
}

void print_color(Color x)
{
    std::cout << "Printing:" << x << std::endl;
}
A: 

Enum in C++ is more of a set of named integer constants than a true type, from compile-time checking point of view. However, the C++ standard has this to say [dcl.enum]:

9 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.

"Unspecified" is slightly better than the usual "undefined behavior".

Arkadiy
"Unspecified" means that any given compiler has to be consistent about what it does, even if the behavior is not documented. Contrast to "undefined" in which the compiler need not even been consistent about its own behavior.
Tyler McHenry
@Tyler, there is no need to be consistent for unspecified (see the order of evaluation of arguments for a case where implementations aren't necessarily consistent), but it should be a value. With undefined behavior, anything can happen, especially the worse when optimizers eager to take advantage of anything undefined come into play.
AProgrammer
+4  A: 

Since you manually cast the 3 to Color, the compiler will allow you to do that. If you tried to initialize the variable x with a plain 3 without a cast, you would get a diagnostic.

Note that the range of values an enumeration can store is not limited by the enumerators it contains. It's the range of values of the smallest bitfield that can store all enumerator values of the enumeration. That is, the range of your enumeration type is 0..3:

00
01
10
11

The value 3 is thus still in range, and so the code is valid. Had you cast a 4, then the resulting value would be left unspecified by the C++ Standard.

In practice, the implementation has to chose an underlying integer type for the enumeration. The smallest type it can choose is char, but which is still able to at least store values ranging up to 127. But as mentioned, the compiler is not required to convert a 4 to a value of 4, because it's outside the range of your enumeration.


I figure i should post some explanation on the difference of "underlying type" and "range of enumeration values". The range of values for any type is the smallest and largest value of that type. The underlying type of an enumeration must be able to store the value of any enumerator (of course) - and two enumerations that have the same underlying type are layout compatible (this allows some flexibility in case a type mismatch occurs).

So while the underlying type is meant to fix the object representation (alignment and size), the values of the enumeration is defined as follows in 7.2/6

For an enumeration where emin is the smallest enumerator and emax is the largest, the values of the enumeration are the values of the underlying type in the range bmin to bmax, where bmin and bmax are, respectively, the smallest and largest values of the smallest bit-field that can store emin and emax . It is possible to define an enumeration that has values not defined by any of its enumerators.

[Footnote: On a two’s-complement machine, bmax is the smallest value greater than or equal to max (abs(emin) − 1 ,abs(emax)) of the form 2M−1; bmin is zero if emin is non-negative and −(bmin+1) otherwise.]

Johannes Schaub - litb
But if tried initializing x with 0,1,2 I woud still get a diagnostic. I guess to play it safe I have to only use enum symbols rather than setting ints.
Pradyot
The diagnostic is done because all of 0, 1 and 2 have type int. The cast forces the compiler to generate a value of type `Color`. The compiler does not lookup all enumerators and check whether `0` equals any of them - after all, assigning `0` might be accidental - most programmers would probably use the enumerator instead of the `0` if they knew that there exists one.
Johannes Schaub - litb
+1  A: 

Color(3) is a cast, with the same semantic as (Color)3, it isn't a constructor. Note that you can also use static_cast<Color>(3) for the same conversion but you can't use Color x(3).

AProgrammer
A: 

Both the C and the C++ standards are kind of confusing on the subject of enums. Both insist that enums are "distinct types" but then both treat them as the underlying integral type. C++ even refers to an italic term "underlying type" which is only sort-of defined when introducing wchar_t.

In summary, wchar_t and enum types are "distinct" but simply mapped to an underlying integral type chosen by the implementation, and this is no doubt due to the need to be compatible with historical enum which was definitely just an int.

Modern compilers typically have options to add more type-like behavior to enums, turning on warnings and errors for various misuses. These can't be a default because they elect non-conforming behavior.

DigitalRoss
The underlying type of an enum in C++ is defined in 7.2/5 (I paraphrased the standard in my comment under Arkadiy's answer). In C++, enumerations are distinct type. They can be implicitly converted to integers but an integer need to be explicitly converted to them. In C, the enumerator have type int, and the enum type is compatible with an implementation-defined integer type.
AProgrammer