String to enum in C++
Asked Answered
P

10

19

Is there a way to associate a string from a text file with an enum value?

The problem is: I have a few enum values stored as string in a text file which I read on the fly on meeting some condition... Now I want to assign the read value to an enum.

What is the most effective way to do so? It doesn't need to be the simplest approach.

Prototype answered 7/4, 2009 at 17:2 Comment(0)
I
32

You can set up a map that you can use over and over:

template <typename T>
class EnumParser
{
    map <string, T> enumMap;
public:
    EnumParser(){};

    T ParseSomeEnum(const string &value)
    { 
        map <string, T>::const_iterator iValue = enumMap.find(value);
        if (iValue  == enumMap.end())
            throw runtime_error("");
        return iValue->second;
    }
};

enum SomeEnum
{
    Value1,
    Value2
};
EnumParser<SomeEnum>::EnumParser()
{
    enumMap["Value1"] = Value1;
    enumMap["Value2"] = Value2;
}

enum OtherEnum
{
    Value3, 
    Value4
};
EnumParser<OtherEnum>::EnumParser()
{
    enumMap["Value3"] = Value3;
    enumMap["Value4"] = Value4;
}

int main()
{
    EnumParser<SomeEnum> parser;
    cout << parser.ParseSomeEnum("Value2");
}
Infrequency answered 7/4, 2009 at 17:5 Comment(7)
Why not using an hasmap ? It'd make the lookup more efficient ? Well , it must tried and measure effectiveness as it may depend on the number of elements in your enum... With big enum, hasmap could make it a quicker way to get the value, isn't it?Whereas
hash_map isn't part of the standard (and unordered_map was only added in TR1). Also, there's likely to be very little difference between hash_map and map unless you start having a very large number of enum values. In any case, it's an easy switch to make if profiling indicates something's too slowInfrequency
hash map would be very inefficient for most enums that have a very limited number of values. In fact linear search may be more efficient than a hash-map if the enumeration only the normal 8 or so values. If it has 2 or 3, linear search will probably beat std::map too.Cnut
if I also need to process values that are not in enum should I just catch runtime_error? or it's better to modify example somehow?Frontal
What if the enum has no name (it is from a lib, so I cannot add the name)?Helotry
Does this not leave you with a tightly coupled EnumParser, that know all about different enums all over your system?Hodgkin
@Hodgkin You do have a tightly coupled EnumParser<enum> for each enum, but EnumParser itself is loosely coupled. Without reflection, or some sort of external tool, I don't believe you can't do much better.Infrequency
D
9
std::map< string, enumType> enumResolver;
Dexamethasone answered 7/4, 2009 at 17:5 Comment(0)
E
3

I agree with many of the answers that std::map is the easiest solution.

If you need something faster, you can use a hash map. Perhaps your compiler already offers one, such as hash_map or the upcoming standard unordered_map, or you can get one from boost. When all the strings are known ahead of time, perfect hashing can be used as well.

Emanate answered 7/4, 2009 at 20:51 Comment(2)
I disagree that hash_map will be faster in general, however if your names are "fixed" you can use some special property you know about them, e.g. they are all unique in the 3rd letter, or some other combination. Then switch or whatever on the value you generate. If you are processing some kind of feed with large numbers of stringified values which will fall into a limited set, this may give you performance benefits, which is what I guess you mean by perfect hashing.Cnut
@CashCow, there's no need to guess what I meant by "perfect hashing", the phrase contains a link to Wikipedia where it's defined. I will agree with you that you can't make blanket assumptions about speed, and probably should have said so in the answer.Emanate
F
3

Accepted answer doesn't contain full listing. I'm adding EnumParser.h which I created from the accepted answer, hope it can help

#include <string>
#include <map>

using namespace std;

template <typename T> class EnumParser
{
    map<string, T> enumMap;
public:
    EnumParser(){};

    T ParseSomeEnum(const string &value)
    { 
        typename map <string, T>::const_iterator iValue = enumMap.find(value);
        if (iValue  == enumMap.end())
            throw runtime_error("");
        return iValue->second;
    }
};

Usage is simple:

enum FieldType
{
    Char,
    Integer,
    Long,
    Fixed,
    Price,
    Date,
    Time
};

EnumParser<FieldType>::EnumParser()
{
    enumMap["Char"] = Char;
    enumMap["Integer"] = Integer;
    enumMap["Long"] = Long;
    enumMap["Fixed"] = Fixed;
    enumMap["Price"] = Price;
    enumMap["Date"] = Date;
    enumMap["Time"] = Time;
}

use:

 EnumParser<FieldType> fieldTypeParser;
 FieldType val = fieldTypeParser.ParseSomeEnum(stringValue)
Frontal answered 21/9, 2013 at 16:30 Comment(4)
I think it's better to integrate your .h into the accepted answer, so it will be easy for other people to find the whole solution in one place ;)Newfangled
I had to add 'typename' in front of the map <string, T>::const_iterator declaration to get rid of an invalid declaration compiler error. Otherwise works great.Cale
in gcc I also have to add template<> in front of EnumParser<FieldType>::EnumParser()Frontal
Where do you put EnumParser<FieldType>::EnumParser() ? outside of the header? inside the cpp for whatever is using EnumParser ?Hodgkin
C
2

Using a std::map raises the question: how does the map get initialised? I would rather use a function:

enum E { A, B };

E f( const std::string & s ) {
   if ( s == "A" ) {
      return A;
    }
    else if ( s == "B" ) {
      return B;
    }
    else {
      throw "Your exception here";
    }
}
Covalence answered 7/4, 2009 at 17:18 Comment(5)
how is initializing the map a problem?Vincenty
You have to guarantee it gets done before you use it to map the strings. This is possible, but only by writing a function.Covalence
My inclination is toward map, since look-up is fast, but Neil, your answer did get me thinking: enums are known at compile-time, as are their string representations. Is there an efficient, scalable way to implement all of this at compile-time?Haviland
veefu, the whole point is to do it at runtime. That is, for an unknown value of type Enum, retrieve its std::string representation. If all you want to support is an enum passed as template parameter, you can avoid the map overhead via template specialization. I don't see the usefulness though.Vincenty
"scalable" is not really an issue since most enums have less than 10 elements. For the same reason I think an hashmap would have too much overhead for the benefit.Rida
G
2

Look at Boost.Bimap, it provides bidirectional associations between two sets of values. You can also choose the underlying container.

Gasperoni answered 8/4, 2009 at 1:1 Comment(0)
C
1

Is this what you want? The initialization is straight, and no instantiation is needed.

usage:

enum SomeEnum
{
    ENUM_ONE,
    ENUM_TWO,
    ENUM_THREE,
    ENUM_NULL
};

DEFINE_PAIRLIST(CEnumMap, SomeEnum)

INIT_PAIRLIST(CEnumMap)=
{
        {"One", ENUM_ONE},
        {"Two", ENUM_TWO},
        {"Three", ENUM_THREE},
        {"", ENUM_NULL}
};

main{
    // Get enum from string
    SomeEnum i = CEnumMap::findValue("One");

    // Get string from enum
    SomeEnum eee = ENUM_ONE;
    const char* pstr = CEnumMap::findKey(eee);
    ...
}

library:

template <class T>
struct CStringPair
{
    const char* _name;
    T _value;
};

template <class T, class Derived>
struct CStringPairHandle
{
    typedef CStringPair<T> CPair;
    static const CStringPair<T> * getPairList(){
        return Derived::implementation();
    }
    static T findValue(const char* name){
        const CStringPair<T> * p = getPairList();
        for (; p->_name[0]!=0; p++)
            if (strcmp(name,p->_name)==0)
                break;
        return p->_value;
    }

    static const char* findKey(T value){
        const CStringPair<T> * p = getPairList();
        for (; p->_name[0]!=0; p++)
            if (strcmp(value,p->_value)==0)
                break;
        return p->_name;
    };
};

#define DEFINE_PAIRLIST(name, type) struct name:public CStringPairHandle<type, name>{ \
    static CPair _pairList[];       \
    static CPair* implementation(){     \
        return _pairList;           \
    }};
#define INIT_PAIRLIST(name) name::CPair name::_pairList[]
Carrick answered 28/3, 2011 at 21:40 Comment(0)
B
0

Parse the string yourself, match the string with a value (which is also an index to a map<string, enum>.

Brassbound answered 7/4, 2009 at 17:5 Comment(0)
F
0

You can calculate the hash of the string and then use this:

template <typename H, typename E>
E map_hash(H const key, std::initializer_list<std::pair<H, E>> const il)
{
  auto const i(
    std::find_if(il.begin(),
      il.end(),
      [key](auto& p)
      {
        return p.first == key;
      }
    )
  );

  assert(i != il.end());

  return i->second;
}
Ferroelectric answered 9/7, 2017 at 2:7 Comment(0)
S
0

Using C++ reflection library from here: https://github.com/tapika/cppreflect

You can - include library like this:

#include "cppreflect/cppreflect.h"

Basic usage:

Declare enumeration:

DECLARE_ENUM( enumName,
    // Prefix for all enums, "" if no prefix used.
    "myenum_",

    myenum_enumValue1,
    myenum_enumValue2,
    myenum_enumValue3 = 5,

    // comment
    myenum_enumValue4
);

Conversion logic:

From enumeration to string:

printf( EnumToString(myenum_enumValue3).c_str() );

=> "enumValue3"

From string to enumeration:

enumName value;

if( !StringToEnum("enumValue4", value) )
    printf("Conversion failed...");

=> 

value == myenum_enumValue4

Main / core functionality resides in here:

https://github.com/tapika/cppreflect/blob/master/cppreflect/enumreflect.h

Saintmihiel answered 17/2, 2020 at 16:22 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.