C++/enumuration values
Expert: Ralph McArdell - 6/12/2009
QuestionHi
could you plz tell me that are enumuration values coerced to integer?or are any other types coerced to an enumuration type?
Thanx
Bita
AnswerShort answers are: Maybe and no, although coerce is probably not the correct term here.
Longer answer:
The values of an enumerated type (a.k.a. an enumeration) are called 'enumerators' in the ISO C++ standard.
During the defining of an enumerated type the type of an enumerator is the type of its initialiser value, or if there is no initialiser value the type of the preceding enumerator if that type can represent the incremented value of the preceding enumerator, otherwise it is an 'unspecified integral type sufficient to contain the incremented value'. If the first enumerator has no initialiser then it also has an 'unspecified integral type'. (the quoted parts are from the ISO C++ standard, 1998 edition).
So for example:
unsigned char const BigestUChar(255); // assumes 8-bit unsigned char
enum ExampleEnumType
{ first_enumerator // No initialiser, type is an 'unspecified integral type' (value is 0)
, second_enumerator = 10 // 10 is a literal int so type of enumerator is int
, third_enumerator // Value is 11 (10+1), enumerator type is int as 11 fits in an int
, forth_enumerator = 1000L // 1000L is a literal long int so type of enumerator is long int
, fifth_enumerator = BigestUChar // BigestUChar is unsigned char so type of enumerator is unsigned char
, sixth_enumerator // Value is 256 (255+1); type is 'unspecified integral type sufficient
}; // to contain the incremented value' as 256 cannot be represented by
// 8-bit unsigned char
The full text of the paragraph in the ISO C++ standard containing this information (section 7.2 paragraph 4) is as follows:
"Each enumeration defines a type that is different from all other types.
Following the closing brace of an enum specifier, each enumerator has
the type of its enumeration. Prior to the closing brace, 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. If no initializer is specified for the first enumerator,
the type is an unspecified integral type. Otherwise the type is the same as
the type of the initializing value of the preceding enumerator unless the
incremented value is not representable in that type, in which case the type
is an unspecified integral type sufficient to contain the incremented value."
OK so what happens when the definition is completed - after the closing brace of the enum specifier?
Well basically the integral type used for the enumerated type as a whole (called in the standard 'the underlying type') is one that can represent all of the enumerator values that are defined in the enumeration. This type is then used for all enumerators of the enumeration. The standard goes on the state that:
"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."
So that answers what values enumeration type enumerators have. However as they are part of a distinct type is not clear so far how they interact with other types. For this we look at the C++ standard conversion rules, where we find under integral conversions, that "An rvalue of an enumeration type can be converted to an rvalue of an integer type." Which basically means that enumerators will implicitly be converted to integer types for us by the compiler - of course the receiving integer type must be able to represent the underlying value of the enumerator.
However, the inverse is not true: there are _no_ such implicit conversions of integers to enumeration types.
To convert a value of an integer (or other) type to an enumerated type requires an explicit conversion (i.e. a type cast - preferably a static_cast if one has to be used at all).
Hence, whilst we can say:
int intValue = third_enumerator;
We cannot say (following on from the previous example):
ExampleEnumType enumValue = intValue;
We have to use an explicit conversion using a type cast in the latter case:
ExampleEnumType enumValue = static_cast<ExampleEnumType>(intValue);
Of course type casting should be avoided if at all possible and their use is a red flag to anyone reading the code that something a little out of the ordinary or possibly dodgy is going on.
Another possibility would be to convert between objects of user defined class types and enumerations using user defined conversions (both single argument constructors and conversion operator member functions).
For example consider a class that can be used to convert to and from ExampleEnumType values and to std::string values. A quick and dirty implementation might look like so:
class ExampleEnumTypeToString
{
ExampleEnumType value_;
public:
ExampleEnumTypeToString( ExampleEnumType value )
: value_(value)
{
}
operator ExampleEnumType() const { return value_; }
operator std::string() const
{
switch (value_)
{
case first_enumerator: return "1st enumerator";
case second_enumerator: return "2nd enumerator";
case third_enumerator: return "3rd enumerator";
case forth_enumerator: return "4th enumerator";
case fifth_enumerator: return "5th enumerator";
case sixth_enumerator: return "6th enumerator";
default: return "##Unknown enumerator##";
}
}
};
We can use the constructor to convert from ExampleEnumType values to ExampleEnumTypeToString objects and by using the operator ExampleEnumType() conversion operator from ExampleEnumTypeToString objects to ExampleEnumType enumeration values. We can also convert from ExampleEnumTypeToString objects to std::strings using the operator std::string() conversion operator. We can use ExampleEnumTypeToString like so:
void Fn( ExampleEnumTypeToString const & v )
{
std::string description( v ); // convert from ExampleEnumTypeToString to std::string
ExampleEnumType enumerator( v ); // convert from ExampleEnumTypeToString to ExampleEnumType
std::cout << description << " has value " << enumerator << std::endl;
}
int main()
{
int intValue = third_enumerator;
ExampleEnumType enumValue = static_cast<ExampleEnumType>(intValue);
// Call Fn with ExampleEnumType enumerator values, which are converted to
// temporary ExampleEnumTypeToString objects using the single argument
// constructor
Fn( fifth_enumerator );
Fn( enumValue );
}
Hope this answers what you were asking about.