C++: Parse enums and convert them to other types elegantly

Quite often, we need to to read a value from some source (file, socket, database) and convert it to an enum. In order to define the conversion in a single place, in C++, we usually define a function like this one:

enum MyEnum{ VALUE_A, VALUE_B, VALUE_C };

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

But where do we define this function? Our first idea is to make it a private method of the class which uses the enum, but this is not possible if the enum is used by more than one class. In those cases, we have no choice but to define the function as a free function inside a namespace.

C#'s enums can have methods and extension methods, which would be the most natural and object oriented solution. Nevertheless, since we're talking C++ now, we can use templates to solve this problem in a generic way, for every enum, and use template specialization to define the specific mapping for each enum. This can be done by defining the following template class:

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

When we run into an enum we wish to parse, we specialize EnumParser's constructor and fill m_EnumMap with the desired mapping:

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

Finally, when we wish to perform the parsing, we instantiate EnumParser and invoke ParseSomeEnum:

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

If we wanted to implement the reverse mapping, that is, convert the enum to a certain type (usually std::string), we can add a second std::map with the inverse mapping to EnumParse (or define it in a subclass of EnumParser). That would make sense if the mapping were asymmetrical; if it's symmetrical, using just a boost::bimap (like a std::map but values can be used as keys to get the keys) would be ideal.

Comments

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