tags:

views:

2230

answers:

4

I've been trying to read a bit of the C++ standard to figure out how enum's work. There's actually more there than I originally thought.

For a scoped enumeration, it's clear that the underlying type is int unless otherwise specified with an enum-base clause (it can be any integral type).

enum class color { red, green, blue};  // these are int

For unscoped enumerations, it seems like the underlying type can be any integral type that will work and that it won't be bigger than an int, unless it needs to be.

enum color { red, green, blue};  // underlying type may vary

Since the underlying type of unscoped enumarations are not standardized, what's the best way of dealing with serializing instances of one? So far, I've been converting to int when writing then serializing into an int and setting my enum variable in a switch when reading, but it seems a bit clunky. Is there a better way?

enum color { red, green, blue };
color c = red;
// to serialize
archive << (int)c;
// to deserialize
int i;
archive >> i;
switch(i) {
  case 0: c = red; break;
  case 1: c = green; break;
  case 2: c = blue; break;
}
+9  A: 

enum class is a C++0x feature, it is not present in C++03.

In standard C++, enumerations are not type-safe. They are effectively integers, even when the enumeration types are distinct. This allows the comparison between two enum values of different enumeration types. The only safety that C++03 provides is that an integer or a value of one enum type does not convert implicitly to another enum type. Additionally, the underlying integral type, the size of the integer, cannot be explicitly specified; it is implementation defined. Lastly, enumeration values are scoped to the enclosing scope. Thus, it is not possible for two separate enumerations to have matching member names. C++0x will allow a special classification of enumeration that has none of these issues. This is expressed using the enum class declaration

Examples (from the wikipedia article):

enum Enum1;                   //Illegal in C++ and C++0x; no size is explicitly specified.
enum Enum2 : unsigned int;    //Legal in C++0x.
enum class Enum3;             //Legal in C++0x, because enum class declarations have a default type of "int".
enum class Enum4: unsigned int; //Legal C++0x.
enum Enum2 : unsigned short;  //Illegal in C++0x, because Enum2 was previously declared with a different type.

As for the serialization part (which I think was not part of the original question), I prefer to create a helper class that translates enum items into their string equivalent (and back), as the names are usually more stable than the integer values they represent, as enum items can be (and sometimes are) reordered without changing the code behavior.

lothar
A: 

As to enum class color: Is this C++/CLI (C++ .NET) or future C++0x code?

For serialization, you could get the enum's size with sizeof(color) to know how many bytes to copy.

Dario
+3  A: 

I haven't read any C++0x stuff so I couldn't comment on that.

As for serializing, you don't need the switch when reading the enum back in - just cast it to the enum type.

However, I don't cast when writing to the stream. This is because I often like to write an operator<< for the enum so I can catch bad values being written, or I can then decide to write out a string instead.

enum color { red, green, blue };
color c = red;

// to serialize
archive << c;    // Removed cast

// to deserialize
int i;
archive >> i;
c = (color)i;    // Removed switch
markh44
Thanks for the idea of writing an operator<< for the enum. I should have thought of that myself.
criddell
In your comment, you mentioned writing an operator<< and I understand that your code above would work correctly with that overload in place. However, if << were not overloaded, one should cast c to an int before serializing, correct?
criddell
No I wouldn't cast to int because if you did that and then added the enum operator<< later, you have to find all places in the code that writes the enum and remove the cast.
markh44
+3  A: 

I decided to create a new answer because my old was so messy. Anyway just want to say something about C++1x, where you can get the underlying type of an enumeration using this one:

std::EnumerationType<E>::underlying_type

And for the sake of interest, the overload resolution idea. But please use names to store the enumeration, as proposed by @lothar.

The overload resolution stems from the fact that there exist one promotion from an enumeration to the first of int, unsigned int, long, unsigned long that can represent all values of its underlying type. A conversion to any other integer type is lower ranked and overload resolution won't prefer it.

char (& f(int) )[1];
char (& f(unsigned int) )[2];

char (& f(long) )[3];
char (& f(unsigned long) )[4];

char const* names[] = { 
    "int", "unsigned int", 
    "long", "unsigned long"
};

enum a { A = INT_MIN };
enum b { B = UINT_MAX };
enum c { C = LONG_MIN };
enum d { D = ULONG_MAX };

template<typename T> void print_underlying() {
    std::cout << names[sizeof(f(T()))-1] << std::endl;
}

int main() { 
    print_underlying<a>();
    print_underlying<b>();
    print_underlying<c>();
    print_underlying<d>();
}

And it prints this one here:

int
unsigned int
int
unsigned int

It's not of particular interest to this serialization problem (since the size of the data serialized is not of constant width, and this can cause problems when the enumeration and its underlying type is changed), but it is generally interesting to figure out a type storing the whole of an enumeration. Cheers!

Johannes Schaub - litb