views:

284

answers:

5

Hello,

Is there a way to serialize enums automatically as int? Every time I define a new enum and write

std::stringstream stream;
stream << myenum1;
stream >> myenum2;

the compiler complains that the operators << and >> are not defined. Do you know a way to tell the compiler to treat enums as plain int's?

What makes the problem harder is that, actually, the serialization is inside a template. Something like this:

template <typename T>
void serialize(const T& value)
{
    std::stringstream stream;
    stream << value;
}

So I cannot add any casts :( Maybe I can specialize it somehow?

Thank you.

+1  A: 

Did you try casting the enums to integers, using, e.g., static_cast<int>(enumX)?

Little Bobby Tables
I cannot cast it since it is inside of a templated code. I've updated the question to be more clear.
FireAphis
The underlying type of an enumeration is not necessarily `int`. Check [this](http://stackoverflow.com/questions/1122096) for more details.
Kirill V. Lyadvinsky
@FireAphis: Could you not have a second type that you pass to the template that is the type you should cast to?
jeffamaphone
+1  A: 

Do you know a way to tell the compiler to treat enums as plain int's?

This is not possible (at least for >>), as it would be not type safe (assigning int to enum is not type safe).

One thing you can do is to provide your overloads of operators >> and << for each enum, which will do the casting for you. This is something you will do for each enum once, which sounds a lot better than having to the cast on each place where you use >>/<< (which comes especially handy here as you cannot edit the places where those operators are used).

Suma
That is exactly what I want to avoid - adding an operator each time I add an enum. Isn't there a generic way to do it?
FireAphis
Using SFINAE you might be able to provide some operator template which would use is_enum to detect the enums - see http://msdn.microsoft.com/en-us/library/bb982983.aspx. If and how exactly this would work is something I would have to think a lot harder, which I currently am not able to do.
Suma
A: 

It'll get better with C++0x, since you'll be able to state the underlying type (for example unsigned short).

For now you have to use a convention, for example assuming that all your enum values can be stored within an int you can serialize and deserialize it as int and then convert it back.

The other issue is of course your serialization routine:

std::stringstream stream;
stream << 1 << 2;
int a;
stream >> a;
assert(a == 12);

Usually serialization involves taking some binary representation of a known size (or serializing the size along it). If you don't want to deal with it yourself, there are libraries:

  • Boost.Serialization
  • Google.ProtocolBuffers

And both have plain text and binary representation (and plain text is way easier to debug).

Matthieu M.
+3  A: 

You can find out if the type is enum with boost's TypeTraits (is_enum).

Then you can combine this with enable_if / disable_if:

template <class T>
typename boost::disable_if<boost::is_enum<T> >::type serialize(const T&);

template <class T>
typename boost::enable_if<boost::is_enum<T> >::type serialize(const T&); //use casts here

However, it will not be possible to check if a given value is valid for a given enum (unless you write a specific overload for the given enum type).

UncleBens
You could check validity using another template trait
Autopulated
@Autopulated: How would that look like? (Also, aren't ORed enum constants also valid values for an enum?)
UncleBens
Well, it depends how your using your enums, but broadly speaking, overload the trait for each type, and use something like a max_value enum value in each enum type. The advantage of using a trait is that you can have a default that allows everything to be accepted, or accepts a predefined range as you like.
Autopulated
+2  A: 

For output, the << operator will promote any enum type to the closest fit of int or long. (§5.8/1, 4.5/2) You shouldn't be having any problems, and I couldn't produce any with GCC or Comeau. Correct me if I'm wrong.

For input, you can write a template function and disable implicit instantiation by hiding it inside an only-explicitly instantiated class.

template< class E >
struct enum_traits {
    friend std::istream &operator>> ( std::istream &is, E &e ) {
        long i; // or intmax_t, whatever
        std::istream &r = is >> i;
        e = E( i );
        return r;
    }
};

enum my_enum { a, b, c };
template class enum_traits< my_enum >; // explicit instantiation
   // "matching declaration" per 7.3.1.2/3:
std::istream &operator>> ( std::istream &, my_enum & );

This passes GCC and Comeau. I can't find a way to wrangle namespaces and ADL to make it any more convenient. (Only alternate solution involves a using declaration for every enumerator.)

But the two boilerplate declarations can be wrapped in a macro, or in any case they're better than writing the whole function over again!

Potatoswatter