如何扩展词法转换以支持枚举类型?

我有以下函数可以将字符串转换为数字数据类型:

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;
}

(我假设 theString 具有枚举类型的有效值;我主要将其用于简单的序列化)

(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.

谢谢

推荐答案

你必须做两个步骤.找到一个足够大的整数类型来存储值.您可以使用 unsigned long,但值可能为负.然后你可以使用 long 但这些值可以扩展到 unsigned long 的范围内.因此,并没有真正适合所有人的类型.

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);

如果您的实现支持,您可以适当地更改它以涵盖 long longunsigned long long.现在,传递一个枚举类型会比所有其他类型更喜欢这些 - 这是一种可以存储它的所有值的类型.您只需要将返回类型的 sizeof 传递给某个模板.

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; };

现在,您可以获得正确的类型.您现在需要做的就是查看某个类型是否是枚举.如何做到这一点在C++ 模板 - 完整指南"一书中有描述,不幸的是代码很多.所以我会使用 boost 的 is_enum.把它放在一起,它可能看起来像

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;
}

希望这会有所帮助.

相关文章