How can I extend a lexical cast to support enumerated types?

I have the following function that will convert a string into a numeric data type:

template <typename T>
bool ConvertString(const std::string& theString, T& theResult)
{
    std::istringstream iss(theString);
    return !(iss >> theResult).fail();
}

This does not work for enumerated types, however, so I have done something like this:

template <typename T>
bool ConvertStringToEnum(const std::string& theString, T& theResult)
{
    std::istringstream iss(theString);
    unsigned int temp;
    const bool isValid = !(iss >> temp).fail();
    theResult = static_cast<T>(temp);
    return isValid;
}

(I'm making the assumption that theString has a valid value for the enumerated type; I'm using this mainly for simple serialization)

Is there a way to create a single function that combines both of these?

I've played a bit with the template arguments but haven't come up with anything; it'd just be nice not to have to call one function for enumerated types and another for everything else.

Thank you


Solution 1:

You have to do two steps. Finding an integral type large enough to store the values. You could use unsigned long, but the values could be negative. Then you could use long but the values could extend into the range of unsigned long. So there is not really a fit-it-all type.

There is a trick though, by using overload resolution. Here is it

template<typename T>
struct id { typedef T type; };

id<char[1]>::type &find_etype(int);
id<char[2]>::type &find_etype(unsigned int);
id<char[3]>::type &find_etype(long);
id<char[4]>::type &find_etype(unsigned long);

You can change it appropriately to cover also long long or unsigned long long if your implementation has support for that. Now, passing an enum type will prefer one of these over all the other ones - that's a type that can store all values of it. You just need to pass sizeof of the return type to some template.

template<int> struct get_etype;
template<> struct get_etype<1> { typedef int type; };
template<> struct get_etype<2> { typedef unsigned int type; };
template<> struct get_etype<3> { typedef long type; };
template<> struct get_etype<4> { typedef unsigned long type; };

Now, you can get a right type. All you need now is to see whether some type is an enumeration. How to do this is described in the book "C++ Templates - The complete Guide", and unfortunately is a whole lot of code. So i would use boost's is_enum. Putting it together, it could look like

template <typename T>
typename boost::disable_if< boost::is_enum<T>, bool>::type 
ConvertString(const std::string& theString, T& theResult)
{
    std::istringstream iss(theString);
    return !(iss >> theResult).fail();
}

template <typename T>
typename boost::enable_if< boost::is_enum<T>, bool>::type 
ConvertString(const std::string& theString, T& theResult)
{
    typedef typename get_etype<sizeof find_etype(theResult)>::type 
      safe_type;

    std::istringstream iss(theString);
    safe_type temp;
    const bool isValid = !(iss >> temp).fail();
    theResult = static_cast<T>(temp);
    return isValid;
}

Hope this helps.

Solution 2:

And just to "complete" the question, in C++0x we can just do this:

typedef typename std::underlying_type<T>::type safe_type;

In place of Johannes get_etype trick.