C++: Parsear enums y convertirlos a otros tipos de forma elegante

Muchas veces queremos leer algún valor de cierta fuente (archivo, socket, base de datos) y convertirlo a un enum. Para no andar repitiendo el código de esa conversión, típicamente creamos una función de este estilo:

enum MyEnum{ VALUE_A, VALUE_B, VALUE_C };

MyEnum ParseMyEnum( const std::string& sMyEnum ){
   //Conversion
}

¿Y dónde ponemos esta función? La primera idea es ponerla como método privado de la clase que usa el enum, pero esto no sirve si el enum es usado por más de una clase. En esos casos, no nos queda más alternativa que definir la función fuera de toda clase, dentro de un namespace.

Los enums de C# pueden tener métodos, lo cual sería la solución más natural y orientada a objetos. Sin embargo, en C++ podemos usar templates para resolver este problema de forma genérica para todo enum, y usar especialización de templates para definir el mapeo específico de cada enum. Para ello, definimos el siguiente template:

#pragma once

#include <map>
#include <string>

template <typename EnumType, typename TypeToParse=std::string>
class EnumParser{
   std::map<TypeToParse, EnumType> m_EnumMap;
public:
   EnumParser(){}

   EnumType ParseSomeEnum( const TypeToParse &value ){
      std::map<TypeToParse, EnumType>::const_iterator iValue = m_EnumMap.find(value);
      if ( iValue  == m_EnumMap.end() )
         throw std::runtime_error("");
      return iValue->second;
   }
};

Cuando nos topemos con un enum que querramos parsear, primero definimos una especialización del constructor de EnumParser e inicializamos m_EnumMap con el mapeo deseado:

EnumParser<CSerialPort::StopBits, int>::EnumParser(){
   //Windows convention (see DCB structure)
   m_EnumMap[0] = CSerialPort::STOP_BITS_ONE;
   m_EnumMap[1] = CSerialPort::STOP_BITS_ONEPOINTFIVE;
   m_EnumMap[2] = CSerialPort::STOP_BITS_TWO;
}

EnumParser<GeneratorLib::EFilter>::EnumParser(){
   m_EnumMap["Molybdenum"] = GeneratorLib::FLT_MOL;
   m_EnumMap["Rhodium_25"] = GeneratorLib::FLT_ROD_25;
   m_EnumMap["Rhodium_50"] = GeneratorLib::FLT_ROD_50;
}

Cuando querramos parsear, instanciamos EnumParser y llamamos a ParseSomEnum:

EnumParser<CSerialPort::StopBits,int> enumParser;
CSerialPort::SConfig sConfig(
   iBaudRate,
   enumParser.ParseSomeEnum(iStopBits),
   iParity, iDataBits
);

Si deseamos implementar también el mapeo inverso, o sea convertir un valor del enum a cierto tipo (típicamente std::string), podemos agregar otro std::map con el mapeo inverso a EnumParser, si es que el mapeo es asimétrico, o bien usar boost::bimap (es como un map donde los valores pueden usarse como claves también, puede simularse con dos std::map) si el mapeo es simétrico.

Comments

  1. Muy bueno Darío, alto blog te armaste! Hoy justo pensaba en como era esa regla que decia que uno podía tener ¿6? cosas al mismo tiempo en la cabeza, regla de ¿...? jaja
    Abrazoo!

    ReplyDelete
  2. Hola Tomás, ¿todo bien? Es la regla de Miller, también conocida como la regla de 7 más-menos 2: http://en.wikipedia.org/wiki/The_Magical_Number_Seven,_Plus_or_Minus_Two.

    Cuando tenga tiempo voy a postear una crítica del Visual 2012. ¡Salute!

    ReplyDelete
  3. Ahh Miller! Me salía Millman jeje, nada que ver. Todo bien! vos? como anda esa DicomLibrary? :D Epa, como pasa el tiempo, será leída! Yo ultimamente estoy usando una ide de JetBrains (WebStorm), muy recomendable!
    Saludos querido!

    ReplyDelete

Post a Comment

Popular posts from this blog

VB.NET: Raise base class events from a derived class

Apache Kafka - I - High level architecture and concepts

Upgrading Lodash from 3.x to 4.x