This question already has an answer here:
Say, we have
enum E
{
Foo = 0,
Bar = 1
};
Now, we do
enum E v = ( enum E ) 2;
And then
switch ( v )
{
case Foo:
doFoo();
break;
case Bar:
doBar();
break;
default:
// Is the compiler required to honor this?
doOther();
break;
}
Since the switch above handles every possible listed value of the enum, is it allowed for the compiler to optimize away the default
branch above, or otherwise have an unspecified or undefined behavior in the case the value of enum is not in the list?
As I am expecting that the behavior should be similar for C and C++, the question is about both languages. However, if there's a difference between C and C++ for that case, it would be nice to know about it, too.
C++ situation
In C++, each enum has an underlying integral type. It can be fixed, if it is explicitly specified (ex: enum test2 : long { a,b};
) or if it is int
by default in the case of a scoped enum (ex: enum class test { a,b };
):
[dcl.enum]/5: Each enumeration defines a type that is different from all other types. Each enumeration also has an underlying type. (...) if not explicitly specified, the underlying type of a scoped enumeration type is int. In these cases, the underlying type is said to be fixed.
In the case of an unscoped enum where the underlying type was not explicitely fixed (your example), the standard gives more flexibility to your compiler:
[dcl.enum]/7: For an enumeration whose underlying type is not fixed, the underlying type 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 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.
Now a very tricky thing: the values that can be held by an enum variable depends on whether or not the underlying type is fixed:
if it's fixed, "the values of the enumeration are the values of the underlying type."
otherwhise, it is the integral values within the minimum and the maximum of the smallest bit-field that can hold the smallest enumerator and the largest one.
You are in the second case, although your code will work on most compilers, the smalest bitfield has a size of 1 and so the only values that you can for sure hold on all compliant C++ compilers are those between 0 and 1...
Conclusion: If you want to ensure that the value can be set to 2, you either have to make your enum a scoped enum, or explicitly indicate an underlying type.**
More reading:
- SO question on how to check if an enum value is valid
- article on avoiding enum out-of-rang in secure coding.
- Stroutstrup's plaidoyer for scoped enum over unscoped ones
C situation
The C situation is much simpler (C11):
6.2.5/16: An enumeration comprises a set of named integer constant values. Each distinct enumeration constitutes a different enumerated type.
So basically, it is an int:
6.7.2.2./2 The expression that defines the value of an enumeration constant shall be an integer constant expression that has a value representable as an int.
With the following restriction:
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.
In C an enum
type is an integer type large enough to hold all the enum
constants:
(C11, 6.7.2.2p4) "Each enumerated type shall be compatible with char, a signed integer type, or an unsigned integer type. The choice of type is implementation-defined,110) but shall be capable of representing the values of all the members of the enumeration".
Let's say the selected type for enum E
is _Bool
. A _Bool
object can only store the values 0
and 1
. It's not possible to have a _Bool
object storing a value different than 0
or 1
without invoking undefined behavior.
In that case the compiler is allowed to assume that an object of the enum E
type can only hold 0
or 1
in a strictly conforming program and is so allowed to optimize out the default
switch case.
C++Std 7.2.7 [dcl.enum]:
It is possible to define an enumeration that has values not defined by any of its enumerators.
So, you can have enumeration values which are not listed in enumerator list.
But in your specific case, the 'underlying type' is not 'fixed' (7.2.5). The specification doesn't say which is the underlying type in that case, but it must be integral. Since char is the smallest such type, we can conclude that there are other values of the enum which are not specified in the enumerator list.
Btw, I think that the compiler can optimize your case when it can determine that there are no other values ever assigned to v, which is safe, but I think there are no compilers which are that smart yet.
Also, 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.
In C enumerators have type int
. Thus any integer value can be assigned to an object of the enumeration type.
From the C Standard (6.7.2.2 Enumeration specifiers)
3 The identifiers in an enumerator list are declared as constants that have type int and may appear wherever such are permitted.
In C++ enumerators have type of the enumeration that defines it. In C++ you should either expliicitly to specify the underlaying type or the compiler calculates itself the maximum allowed value.
From the C++ Standard (7.2 Enumeration declarations)
5 Each enumeration defines a type that is different from all other types. Each enumeration also has an underlying type. The underlying type can be explicitly specified using enum-base; if not explicitly specified, the underlying type of a scoped enumeration type is int. In these cases, the underlying type is said to be fixed. Following the closing brace of an enum-specifier, each enumerator has the type of its enumeration.
Thus in C any possible value of a enum is any integer value. The compiler may not optimize a switch removing the default label.
In C and C++, this can work.
Same code for both:
#include <stdio.h>
enum E
{
Foo = 0,
Bar = 1
};
int main()
{
enum E v = (enum E)2; // the cast is required for C++, but not for C
printf("v = %d\n", v);
switch (v) {
case Foo:
printf("got foo\n");
break;
case Bar:
printf("got bar\n");
break;
default:
printf("got \n", v);
break;
}
}
Same output for both:
v = 2
got default
In C, an enum
is an integral type, so you can assign an integer value to it without casting. In C++, an enum
is its own type.
来源:https://stackoverflow.com/questions/33812998/is-it-allowed-for-an-enum-to-have-an-unlisted-value