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.

bluish
  • 26,356
  • 27
  • 122
  • 180
Shree
  • 4,627
  • 6
  • 37
  • 49

10 Answers10

31

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");
}
bluish
  • 26,356
  • 27
  • 122
  • 180
Eclipse
  • 44,851
  • 20
  • 112
  • 171
  • 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? – yves Baumes Apr 07 '09 at 20:59
  • 1
    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 slow – Eclipse Apr 07 '09 at 21:33
  • 1
    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. – CashCow Sep 13 '12 at 12:18
  • 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? – Oleg Vazhnev Sep 21 '13 at 18:11
  • What if the enum has no name (it is from a lib, so I cannot add the name)? – sop Sep 17 '15 at 13:21
  • Does this not leave you with a tightly coupled EnumParser, that know all about different enums all over your system? – pingu Jul 12 '20 at 13:25
  • @pingu You do have a tightly coupled EnumParser 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. – Eclipse Jul 22 '20 at 23:21
9
std::map< string, enumType> enumResolver;
bluish
  • 26,356
  • 27
  • 122
  • 180
tpdi
  • 34,554
  • 11
  • 80
  • 120
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)
Oleg Vazhnev
  • 23,239
  • 54
  • 171
  • 305
  • 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 ;) – bluish Sep 23 '13 at 08:13
  • I had to add '`typename`' in front of the `map ::const_iterator` declaration to get rid of an invalid declaration compiler error. Otherwise works great. – tbc Jan 13 '14 at 19:21
  • 2
    in gcc I also have to add `template<>` in front of `EnumParser::EnumParser()` – Oleg Vazhnev Sep 22 '14 at 09:41
  • Where do you put EnumParser::EnumParser() ? outside of the header? inside the cpp for whatever is using EnumParser ? – pingu Jul 12 '20 at 13:24
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.

bluish
  • 26,356
  • 27
  • 122
  • 180
Mark Ransom
  • 299,747
  • 42
  • 398
  • 622
  • 1
    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. – CashCow Sep 13 '12 at 12:21
  • 1
    @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. – Mark Ransom Sep 13 '12 at 13:29
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";
    }
}
bluish
  • 26,356
  • 27
  • 122
  • 180
  • 1
    how is initializing the map a problem? –  Apr 07 '09 at 17:27
  • You have to guarantee it gets done before you use it to map the strings. This is possible, but only by writing a function. –  Apr 07 '09 at 17:29
  • 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? – veefu Apr 07 '09 at 17:49
  • 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. –  Apr 07 '09 at 19:58
  • "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. – aberaud Apr 10 '13 at 08:30
2

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

Vadim Ferderer
  • 911
  • 7
  • 11
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[]
Ben Lai
  • 11
  • 2
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;
}
user1095108
  • 14,119
  • 9
  • 58
  • 116
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

TarmoPikaro
  • 4,723
  • 2
  • 50
  • 62
0

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

dirkgently
  • 108,024
  • 16
  • 131
  • 187