How to Extend a Lexical Cast to Support Enumerated Types

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

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.

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

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.

C++ convert string to enum?

You're not using the convert_string function correctly.

typedef typename std::underlying_type::type safe_type;

This is determining the storage type for the enum's value (e.g., int, long long, etc).

Then this type is used to convert theString to temp which is a numeric type. Therefore, the istringstream works by converting a string representing a numeric value (e.g., "0", "1", etc) to a numeric value.

Given this information you should understand why your code isn't working but the the following code does.

Code

enum TestType
{
A1,
B2,
C3
};

template<typename T>
typename std::enable_if<std::is_enum<T>::value, bool>::type
convert_string(const std::string& theString, T& theResult)
{
typedef typename std::underlying_type<T>::type safe_type;

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

return isValid;
}

int main()
{
std::string s = "0"; // This is the value of A1
TestType tt;

std::cout << std::boolalpha << convert_string(s, tt) << "\n";
std::cout << std::boolalpha << (A1 == tt) << "\n";

return 0;
}

Output

true
true

Solution

To support your usage you'll need to do something like others have suggested in the comments (e.g., mapping of string representation to enum)

How can I partially specialize a class template for ALL enums?

use C++11 and SFINAE.

#include <type_traits>

template<typename T, typename = void>
struct Specialize
{
};

template<typename T>
struct Specialize<T, typename std::enable_if<std::is_enum<T>::value>::type>
{
void convert() { }
};

enum E
{
};

int main()
{
Specialize<E> spec;
spec.convert();
}

Without C++11 use boost::enable_if and boost::is_enum

Enum? extends interface

One option you have is to add any of the methods from Enum you need onto Fooable or create a new interface that extends Fooable and adds the Enum methods you need.

Example:

interface Fooable {
void someCommonMethod();
}

interface FooableEnum extends Fooable {
int ordinal();
}

enum E1 implements FooableEnum {
// Implement someCommonMethod.
// ordinal() is already implemented by default.
}

Once you've done this you can use FooableEnum as the parameter type in your method signature and not worry about any of the generic stuff.

Template specialization for enum

You can use std::enable_if with std::is_enum from <type_traits> to accomplish this.

In an answer to one of my questions, litb posted a very detailed and well-written explanation of how this can be done with the Boost equivalents.

Lexical cast from string to type

I like using locate, which works on built-in types:

>>> from pydoc import locate
>>> locate('int')
<type 'int'>
>>> t = locate('int')
>>> t('1')
1

...as well as anything it can find in the path:

>>> locate('datetime.date')
<type 'datetime.date'>
>>> d = locate('datetime.date')
>>> d(2015, 4, 23)
datetime.date(2015, 4, 23)

...including your custom types:

>>> locate('mypackage.model.base.BaseModel')
<class 'mypackage.model.base.BaseModel'>
>>> m = locate('mypackage.model.base.BaseModel')
>>> m()
<mypackage.model.base.BaseModel object at 0x1099f6c10>

function template for serialization of enums

There are two improvements to be made here: not always serializing as int (not all enums are), but as whatever the underlying type is. And, as your request, to only accept enums.

The latter is easily solved with std::enable_if and std::is_enum:

typename std::enable_if<std::is_enum<T>::value, Stream&>::type
operator<<( Stream& s, T const& value )

// and likewise for operator>>

And for the former, do the following inside the function:

Stream& operator<<( Stream& s, T const& value )
{
typedef typename std::underlying_type<T>::type safe_type;
s << static_cast<safe_type>(value);
}

// and likewise for operator>>

This requires C++0x.

If that's not an option, both enable_if and is_enum can be found within Boost. However, I think you'll need to make underlying_type yourself. (And of course, in the worse case you can do all three yourself, though is_enum can be a pain, if I recall correctly.)

how to throw boost bad lexical cast exception

Remove the first throw. boost::throw_exception is a function that simply performs on its own. More specifically, boost::throw_exception takes an exception as an argument, and uses a copy of that passed argument to throw an exception.

if(!Throw)
return boost::lexical_cast<std::string>(some_value);
else
boost::throw_exception(boost::bad_lexical_cast());

boost::throw_exception is actually part of the Boost.Exception library, which uses C++ in some clever ways to allow you to attach additional information to exceptions while retaining the original exception types. Take a look at the linked documentation for more information.

C++ string to enum

A std::map<std::string, MyEnum> (or unordered_map) could do it easily. Populating the map would be just as tedious as the switch statement though.

Edit: Since C++11, populating is trivial:

static std::unordered_map<std::string,E> const table = { {"a",E::a}, {"b",E::b} };
auto it = table.find(str);
if (it != table.end()) {
return it->second;
} else { error() }


Related Topics



Leave a reply



Submit