2009-10-06 18 views
21

tengo la siguiente función que convierte una cadena en un tipo de datos numéricos:¿Cómo puedo extender un molde léxico para admitir tipos enumerados?

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

Esto no funciona para los tipos enumerados, sin embargo, por lo que he hecho algo como esto:

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

(Estoy asumiendo que theString tiene un valor válido para el tipo enumerado; lo estoy usando principalmente para la serialización simple)

¿Hay alguna manera de crear una sola función que combine ambos?

He jugado un poco con los argumentos de la plantilla pero no he encontrado nada; sería agradable no tener que llamar a una función para tipos enumerados y otra para todo lo demás.

Gracias

Respuesta

42

Tienes que hacer dos pasos. Encontrar un tipo integral lo suficientemente grande como para almacenar los valores. Puede usar unsigned long, pero los valores pueden ser negativos. Entonces podría usar long pero los valores podrían extenderse al rango de unsigned long. Entonces no hay realmente un tipo apto para todo.

Sin embargo, hay un truco al usar la resolución de sobrecarga. Aquí es

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

Usted puede cambiar de manera apropiada para cubrir también long long o unsigned long long si su aplicación tiene soporte para eso. Ahora, al pasar un tipo enum, preferirá uno de estos sobre todos los demás; es un tipo que puede almacenar todos los valores del mismo. Solo necesita pasar sizeof del tipo de devolución a alguna plantilla.

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

Ahora, puede obtener un tipo correcto. Todo lo que necesita ahora es ver si algún tipo es una enumeración. Cómo hacer esto se describe en el libro "Plantillas C++ - La guía completa", y desafortunadamente es una gran cantidad de código. Entonces usaría boost is_enum. Poniéndolo juntos, podría verse como

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

Espero que esto ayude.

+0

+1. También estaba a punto de escribir un comentario sobre por qué esto no estaba en la biblioteca estándar, hasta que leí la respuesta de GMan a continuación. – Jon

+0

boost ya no es necesario ya que std :: enable_if y std :: is_enum proporcionan estas funcionalidades en C++ 11 – moala

10

Y sólo para "completa" la cuestión, en C++ 0x sólo podemos hacer esto:

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

En lugar de Johannes get_etype truco.

+2

Awesome; que característica tan conveniente. Yo era tan novato cuando hice esta pregunta ... Quiero decir, todavía soy un novato, pero entonces era mucho más novato. –

Cuestiones relacionadas