String enum — строковые enum

Я работаю в игровой сфере. В связи с этим постоянно приходится сталкиваться со всевозможными конфигами.
Каждый раз, когда в конфигах должно быть некое перечисление, возникает дилема. С одной стороны, хочется читаемых конфигов, с другой — быстрого парсинга и быстрого обращения по этому типу.
Что хочется:

"type":
[
	{
		"id": 1,
		"type": "one",
	},
	{
		"id": 2,
		"type": "two",
	},
	{
		"id": 6,
		"type": "three",
	}
]


Но в тоже время в кодe хочется использовать структуры типа:

enum Type
{
	one,
	two,
	three
};



Далее в теле статьи будут приведены несколько вариантов решения.



Требования к библиотеке были следующие:

  • Кроссплатформенность;
  • Минимум зависимостей;
  • Скорость чтения;
  • Простой синтаксис;


Первый вариант решения был до с++11:

class Type
{
public:
    enum type { one, two, three };
    static const std::string &to_string( type enumVal )
    {
        static const std::map<type,std::string> enumStringsMap = _make_enum_strings_map();
        auto it = enumStringsMap.find(enumVal);
        static std::string emptyString;
        if(it==enumStringsMap.end())
            return emptyString;
        return it->second;
    }
    static type from_string(const std::string &value)
    {
        static const std::map<std::string,type> stringsEnumMap = _make_strings_enum_map();
        std::map<std::string,type>::const_iterator it = stringsEnumMap.find(value);
        if(it==stringsEnumMap.end())
            return (type)0;
        return it->second;
    }
    static const std::vector<type>& values()
    {
        static const std::vector<type> valueVector = _make_values();
        return valueVector;
    }
private:
    static const std::vector<type> _make_values()
    {
        std::vector<type> valueVector;
        valueVector.reserve(3);
        valueVector.push_back(one);
        valueVector.push_back(two);
        valueVector.push_back(three);
        return valueVector;\
    }
    static std::map<type,std::string> _make_enum_strings_map()
    {
        std::map<type,std::string> enumStringsMap;
        enumStringsMap.insert(std::make_pair(one, "one"));
        enumStringsMap.insert(std::make_pair(two, "two"));
        enumStringsMap.insert(std::make_pair(three, "three"));
        return enumStringsMap;
    }
    static std::map<std::string,type> _make_strings_enum_map()
    {
        std::map<std::string,type> stringsEnumMap;
        stringsEnumMap.insert(std::make_pair("one", one));
        stringsEnumMap.insert(std::make_pair("two", two));
        stringsEnumMap.insert(std::make_pair("three", three));
        return stringsEnumMap;
    }
};

Неплохо, но писать это для каждого перечисления долговато.

Пример использования:

    Type::type type;
    type =  Type::from_string("one");
    std::string stringType = Type::to_string(type);


В принципе, работает, если to_string, from_string и values не вызываются, то никаких накладных расходов не будет.

Решено было остановится на синтаксисе STRING_ENUM( Type, one, two, three);

Как сделать подобный код на c++ я не представляю, но там, где бессилен c++, помогут Они — макросы (да, неизбежное зло, но писать 60 строк кода для каждого класса очень накладно).

Нашел кусочек решения тут. После небольших доработок получается следующий код, думаю, что 32 — достаточное количество аргументов для большинства задач.

В итоге, после некоторых экспериментов остановился на таком коде:

string_enum.h
#define VA_SIZE(...) INVOKE( VA_GET_SIZE VA_OB INVOKE(VA_SPEC##__VA_ARGS__()), 0, 33, 32, 31, 30, 29, 28, 27, 26, 25, 24, 23, 22, 21, 20, 19, 18, 17, 16, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1 VA_CB )

#define VA_OB (
#define VA_CB )
#define VA_SPEC() 1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31,32,33,34
#define VA_GET_SIZE(_0,_1,_2,_3,_4,_5,_6,_7,_8,_9,_10,_11,_12,_13,_14,_15,_16,_17,_18,_19,_20,_21,_22,_23,_24,_25,_26,_27,_28,_29,_30,_31,_32,_,n,...) n

#define INVOKE( ... ) INVOKE_A( __VA_ARGS__ )
#define INVOKE_A( ... ) __VA_ARGS__

#define VA_FOR(macro,data,...) INVOKE( CAT(VA_FOR, VA_SIZE(__VA_ARGS__)) ( macro, data, VA_APPLY(VA_FIRST (__VA_ARGS__)),  (VA_APPLY(VA_WO_FIRST (__VA_ARGS__))) ) )

#define VA_APPLY(x) x
#define VA_FIRST(a, ...) a
#define VA_WO_FIRST(a, ...) __VA_ARGS__

#define VA_FOR0(m,d,e,x)  
#define VA_FOR1(m,d,e,x)  m( d, e ) 
#define VA_FOR2(m,d,e,x)  m( d, e )  VA_FOR1( m, d, VA_APPLY(VA_FIRST x), (VA_APPLY(VA_WO_FIRST x)))
#define VA_FOR3(m,d,e,x)  m( d, e )  VA_FOR2( m, d, VA_APPLY(VA_FIRST x), (VA_APPLY(VA_WO_FIRST x)))
#define VA_FOR4(m,d,e,x)  m( d, e )  VA_FOR3( m, d, VA_APPLY(VA_FIRST x), (VA_APPLY(VA_WO_FIRST x)))
#define VA_FOR5(m,d,e,x)  m( d, e )  VA_FOR4( m, d, VA_APPLY(VA_FIRST x), (VA_APPLY(VA_WO_FIRST x)))
#define VA_FOR6(m,d,e,x)  m( d, e )  VA_FOR5( m, d, VA_APPLY(VA_FIRST x), (VA_APPLY(VA_WO_FIRST x)))
#define VA_FOR7(m,d,e,x)  m( d, e )  VA_FOR6( m, d, VA_APPLY(VA_FIRST x), (VA_APPLY(VA_WO_FIRST x)))
#define VA_FOR8(m,d,e,x)  m( d, e )  VA_FOR7( m, d, VA_APPLY(VA_FIRST x), (VA_APPLY(VA_WO_FIRST x)))
#define VA_FOR9(m,d,e,x)  m( d, e )  VA_FOR8( m, d, VA_APPLY(VA_FIRST x), (VA_APPLY(VA_WO_FIRST x)))
#define VA_FOR10(m,d,e,x) m( d, e )  VA_FOR9( m, d, VA_APPLY(VA_FIRST x), (VA_APPLY(VA_WO_FIRST x)))
#define VA_FOR11(m,d,e,x) m( d, e )  VA_FOR10( m, d, VA_APPLY(VA_FIRST x), (VA_APPLY(VA_WO_FIRST x)))
#define VA_FOR12(m,d,e,x) m( d, e )  VA_FOR11( m, d, VA_APPLY(VA_FIRST x), (VA_APPLY(VA_WO_FIRST x)))
#define VA_FOR13(m,d,e,x) m( d, e )  VA_FOR12( m, d, VA_APPLY(VA_FIRST x), (VA_APPLY(VA_WO_FIRST x)))
#define VA_FOR14(m,d,e,x) m( d, e )  VA_FOR13( m, d, VA_APPLY(VA_FIRST x), (VA_APPLY(VA_WO_FIRST x)))
#define VA_FOR15(m,d,e,x) m( d, e )  VA_FOR14( m, d, VA_APPLY(VA_FIRST x), (VA_APPLY(VA_WO_FIRST x)))
#define VA_FOR16(m,d,e,x) m( d, e )  VA_FOR15( m, d, VA_APPLY(VA_FIRST x), (VA_APPLY(VA_WO_FIRST x)))
#define VA_FOR17(m,d,e,x) m( d, e )  VA_FOR16( m, d, VA_APPLY(VA_FIRST x), (VA_APPLY(VA_WO_FIRST x)))
#define VA_FOR18(m,d,e,x) m( d, e )  VA_FOR17( m, d, VA_APPLY(VA_FIRST x), (VA_APPLY(VA_WO_FIRST x)))
#define VA_FOR19(m,d,e,x) m( d, e )  VA_FOR18( m, d, VA_APPLY(VA_FIRST x), (VA_APPLY(VA_WO_FIRST x)))
#define VA_FOR20(m,d,e,x) m( d, e )  VA_FOR19( m, d, VA_APPLY(VA_FIRST x), (VA_APPLY(VA_WO_FIRST x)))
#define VA_FOR21(m,d,e,x) m( d, e )  VA_FOR20( m, d, VA_APPLY(VA_FIRST x), (VA_APPLY(VA_WO_FIRST x)))
#define VA_FOR22(m,d,e,x) m( d, e )  VA_FOR21( m, d, VA_APPLY(VA_FIRST x), (VA_APPLY(VA_WO_FIRST x)))
#define VA_FOR23(m,d,e,x) m( d, e )  VA_FOR22( m, d, VA_APPLY(VA_FIRST x), (VA_APPLY(VA_WO_FIRST x)))
#define VA_FOR24(m,d,e,x) m( d, e )  VA_FOR23( m, d, VA_APPLY(VA_FIRST x), (VA_APPLY(VA_WO_FIRST x)))
#define VA_FOR25(m,d,e,x) m( d, e )  VA_FOR24( m, d, VA_APPLY(VA_FIRST x), (VA_APPLY(VA_WO_FIRST x)))
#define VA_FOR26(m,d,e,x) m( d, e )  VA_FOR25( m, d, VA_APPLY(VA_FIRST x), (VA_APPLY(VA_WO_FIRST x)))
#define VA_FOR27(m,d,e,x) m( d, e )  VA_FOR26( m, d, VA_APPLY(VA_FIRST x), (VA_APPLY(VA_WO_FIRST x)))
#define VA_FOR28(m,d,e,x) m( d, e )  VA_FOR27( m, d, VA_APPLY(VA_FIRST x), (VA_APPLY(VA_WO_FIRST x)))
#define VA_FOR29(m,d,e,x) m( d, e )  VA_FOR28( m, d, VA_APPLY(VA_FIRST x), (VA_APPLY(VA_WO_FIRST x)))
#define VA_FOR30(m,d,e,x) m( d, e )  VA_FOR29( m, d, VA_APPLY(VA_FIRST x), (VA_APPLY(VA_WO_FIRST x)))
#define VA_FOR31(m,d,e,x) m( d, e )  VA_FOR30( m, d, VA_APPLY(VA_FIRST x), (VA_APPLY(VA_WO_FIRST x)))
#define VA_FOR32(m,d,e,x) m( d, e )  VA_FOR31( m, d, VA_APPLY(VA_FIRST x), (VA_APPLY(VA_WO_FIRST x)))

#define CAT(x,y) CAT_A(x, y)
#define CAT_A(x,y) x##y

#define ENUM_IDENTITY(X,A) A,
#define ENUM_STRING_TO_ENUM(X,A) X.insert(std::make_pair(#A,A));
#define ENUM_ENUM_TO_STRING(X,A) X.insert(std::make_pair(A,#A));
#define ENUM_TO_VECTOR(X,A) X.push_back(A);

#define STRING_ENUM(name, ...) \
class name \
{ \
public:\
    enum Type { VA_FOR(ENUM_IDENTITY, fake, __VA_ARGS__) }; \
    static const std::string &to_string( Type enumVal ) \
    {\
        static const std::map<Type,std::string> enumStringsMap = _make_enum_strings_map();\
        auto it = enumStringsMap.find(enumVal);\
        static std::string emptyString;\
        if(it==enumStringsMap.end())\
            return emptyString;\
        return it->second;\
    }\
    static Type from_string(const std::string &value)\
    {\
        static const std::map<std::string,Type> stringsEnumMap = _make_strings_enum_map(); \
        std::map<std::string,Type>::const_iterator it = stringsEnumMap.find(value);\
        if(it==stringsEnumMap.end())\
            return (Type)0;\
        return it->second;\
    }\
    static const std::vector<Type>& values()\
    {\
        static const std::vector<Type> valueVector = _make_values();\
        return valueVector;\
    }\
private:\
    static const std::vector<Type> _make_values()\
    {\
        std::vector<Type> valueVector;\
        valueVector.reserve(VA_SIZE(__VA_ARGS__));\
        VA_FOR(ENUM_TO_VECTOR, valueVector, __VA_ARGS__)\
        return valueVector;\
    }\
    static std::map<Type,std::string> _make_enum_strings_map()\
    {\
        std::map<Type,std::string> enumStringsMap;\
        VA_FOR(ENUM_ENUM_TO_STRING, enumStringsMap, __VA_ARGS__)\
        return enumStringsMap;\
    }\
    static std::map<std::string,Type> _make_strings_enum_map()\
    {\
        std::map<std::string,Type> stringsEnumMap;\
        VA_FOR(ENUM_STRING_TO_ENUM, stringsEnumMap, __VA_ARGS__)\
        return stringsEnumMap;\
    }\
};



Пример использования:

    STRING_ENUM( MyStringEnum, one, two, three);
    MyStringEnum::Type type;
    type =  MyStringEnum::from_string("one");
    std::string stringType = MyStringEnum::to_string(type);


Плюсы решения компилируется почти во всех компиляторах, лично пробовал на gcc 4.3-4.9 clang 2.8-3.4 MSVC 2012-2013.

Прошло некоторое время и c++11 стал поддерживаться почти всеми компиляторами. Захотелось сделать типобезопасный enum, также решено было вынести вспомогательные ф-ии в отдельный класс, так как enum class глобальную область видимости не засоряет:

enum class MyEnum { ONE, TWO, THREE };
class MyEnumHelper{
public: 
    typedef MyEnum Type;
    static const std::string &to_string( Type enumVal ) 
    {
        static const std::map<Type, std::string> enumStringsMap = { { Type::ONE, tolower("ONE") }, { Type::TWO, tolower("TWO") }, { Type::THREE, tolower("THREE") } };
        auto it = enumStringsMap.find(enumVal);
        static std::string emptyString;
        if(it==enumStringsMap.end())
            return emptyString;
        return it->second;
    }
    static Type from_string(const std::string &value)
    {
        static const std::map<std::string, Type> enumStringsMap = { { tolower("ONE"), Type::ONE }, { tolower("TWO"), Type::TWO }, { tolower("THREE"), Type::THREE } };
        auto it = enumStringsMap.find(value);
        if(it==enumStringsMap.end())
            return (Type)0;
        return it->second;
    }
    static const std::vector<Type>& values()
    {
        static const std::vector<Type> valueVector = { Type::ONE, Type::TWO, Type::THREE };
        return valueVector;
    }
private: 
    inline static char easytolower(char in){ 
        if(in<='Z' && in>='A') 
            return in-('Z'-'z'); 
        return in; 
    }
    static std::string tolower(std::string &&tolower) 
    { 
        std::string temp = tolower; 
        for (std::string::size_type i=0; i<temp.length(); ++i) 
            temp[i] = easytolower(temp[i]); 
        return temp; 
    } 
};


Пример использования:

    Type::Type type;
    type =  Type::from_string("one");
    std::string stringType = Type::to_string(type);


Также в процессе адаптации было принято решение разрешить назначать конкретное значение определенному значению. Идея была подсмотрена тут.

И, наконец, после многих часов отладки макросов препроцессора (помогла эта тема), вот он, финальльный вариант:

string_enum.h
#ifndef _StringEnums_h
#define _StringEnums_h

#include <string>
#include <vector>
#include <map>

#define VA_SIZE(...) VA_SIZE_((VA_SIZE_PREFIX_ ## __VA_ARGS__ ## _VA_SIZE_POSTFIX,32,31,30,29,28,27,26,25,24,23,22,21,20,19,18,17,16,15,14,13,12,11,10,9,8,7,6,5,4,3,2,1,0))
#define VA_SIZE_INVOKE(...) INVOKE(VA_SIZE(__VA_ARGS__))
#define VA_SIZE_(__args) VA_GET_SIZE __args

#define VA_SIZE_PREFIX__VA_SIZE_POSTFIX ,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,0

#define VA_GET_SIZE(__p0,__p1,__p2,__p3,__p4,__p5,__p6,__p7,__p8,__p9,__p10,__p11,__p12,__p13,__p14,__p15,__p16,__p17,__p18,__p19,__p20,__p21,__p22,__p23,__p24,__p25,__p26,__p27,__p28,__p29,__p30,__p31,__n,...) __n

#define INVOKE( ... ) INVOKE_A( __VA_ARGS__ )
#define INVOKE_A( ... ) __VA_ARGS__

#define VA_FOR(macro,...) INVOKE( CAT(VA_FOR, VA_SIZE_INVOKE(__VA_ARGS__)) ( macro, (__VA_ARGS__) ) )

#define VA_APPLY(x) x
#define VA_FIRST(a, ...) a
#define VA_WO_FIRST(a, ...) __VA_ARGS__

#define VA_FOR0(m,x)
#define VA_FOR1(m,x)  m( VA_APPLY(VA_FIRST x) )
#define VA_FOR2(m,x)  m( VA_APPLY(VA_FIRST x) )  VA_FOR1( m, (VA_APPLY(VA_WO_FIRST x)))
#define VA_FOR3(m,x)  m( VA_APPLY(VA_FIRST x) )  VA_FOR2( m, (VA_APPLY(VA_WO_FIRST x)))
#define VA_FOR4(m,x)  m( VA_APPLY(VA_FIRST x) )  VA_FOR3( m, (VA_APPLY(VA_WO_FIRST x)))
#define VA_FOR5(m,x)  m( VA_APPLY(VA_FIRST x) )  VA_FOR4( m, (VA_APPLY(VA_WO_FIRST x)))
#define VA_FOR6(m,x)  m( VA_APPLY(VA_FIRST x) )  VA_FOR5( m, (VA_APPLY(VA_WO_FIRST x)))
#define VA_FOR7(m,x)  m( VA_APPLY(VA_FIRST x) )  VA_FOR6( m, (VA_APPLY(VA_WO_FIRST x)))
#define VA_FOR8(m,x)  m( VA_APPLY(VA_FIRST x) )  VA_FOR7( m, (VA_APPLY(VA_WO_FIRST x)))
#define VA_FOR9(m,x)  m( VA_APPLY(VA_FIRST x) )  VA_FOR8( m, (VA_APPLY(VA_WO_FIRST x)))
#define VA_FOR10(m,x) m( VA_APPLY(VA_FIRST x) )  VA_FOR9( m, (VA_APPLY(VA_WO_FIRST x)))
#define VA_FOR11(m,x) m( VA_APPLY(VA_FIRST x) )  VA_FOR10( m, (VA_APPLY(VA_WO_FIRST x)))
#define VA_FOR12(m,x) m( VA_APPLY(VA_FIRST x) )  VA_FOR11( m, (VA_APPLY(VA_WO_FIRST x)))
#define VA_FOR13(m,x) m( VA_APPLY(VA_FIRST x) )  VA_FOR12( m, (VA_APPLY(VA_WO_FIRST x)))
#define VA_FOR14(m,x) m( VA_APPLY(VA_FIRST x) )  VA_FOR13( m, (VA_APPLY(VA_WO_FIRST x)))
#define VA_FOR15(m,x) m( VA_APPLY(VA_FIRST x) )  VA_FOR14( m, (VA_APPLY(VA_WO_FIRST x)))
#define VA_FOR16(m,x) m( VA_APPLY(VA_FIRST x) )  VA_FOR15( m, (VA_APPLY(VA_WO_FIRST x)))
#define VA_FOR17(m,x) m( VA_APPLY(VA_FIRST x) )  VA_FOR16( m, (VA_APPLY(VA_WO_FIRST x)))
#define VA_FOR18(m,x) m( VA_APPLY(VA_FIRST x) )  VA_FOR17( m, (VA_APPLY(VA_WO_FIRST x)))
#define VA_FOR19(m,x) m( VA_APPLY(VA_FIRST x) )  VA_FOR18( m, (VA_APPLY(VA_WO_FIRST x)))
#define VA_FOR20(m,x) m( VA_APPLY(VA_FIRST x) )  VA_FOR19( m, (VA_APPLY(VA_WO_FIRST x)))
#define VA_FOR21(m,x) m( VA_APPLY(VA_FIRST x) )  VA_FOR20( m, (VA_APPLY(VA_WO_FIRST x)))
#define VA_FOR22(m,x) m( VA_APPLY(VA_FIRST x) )  VA_FOR21( m, (VA_APPLY(VA_WO_FIRST x)))
#define VA_FOR23(m,x) m( VA_APPLY(VA_FIRST x) )  VA_FOR22( m, (VA_APPLY(VA_WO_FIRST x)))
#define VA_FOR24(m,x) m( VA_APPLY(VA_FIRST x) )  VA_FOR23( m, (VA_APPLY(VA_WO_FIRST x)))
#define VA_FOR25(m,x) m( VA_APPLY(VA_FIRST x) )  VA_FOR24( m, (VA_APPLY(VA_WO_FIRST x)))
#define VA_FOR26(m,x) m( VA_APPLY(VA_FIRST x) )  VA_FOR25( m, (VA_APPLY(VA_WO_FIRST x)))
#define VA_FOR27(m,x) m( VA_APPLY(VA_FIRST x) )  VA_FOR26( m, (VA_APPLY(VA_WO_FIRST x)))
#define VA_FOR28(m,x) m( VA_APPLY(VA_FIRST x) )  VA_FOR27( m, (VA_APPLY(VA_WO_FIRST x)))
#define VA_FOR29(m,x) m( VA_APPLY(VA_FIRST x) )  VA_FOR28( m, (VA_APPLY(VA_WO_FIRST x)))
#define VA_FOR30(m,x) m( VA_APPLY(VA_FIRST x) )  VA_FOR29( m, (VA_APPLY(VA_WO_FIRST x)))
#define VA_FOR31(m,x) m( VA_APPLY(VA_FIRST x) )  VA_FOR30( m, (VA_APPLY(VA_WO_FIRST x)))
#define VA_FOR32(m,x) m( VA_APPLY(VA_FIRST x) )  VA_FOR31( m, (VA_APPLY(VA_WO_FIRST x)))

#define CAT(x,y) CAT_A(x, y)
#define CAT_A(x,y) x##y

#define M_STR(A) M_STR_(A)
#define M_STR_(A) #A

#define M_LOWER_STR(A) M_LOWER_STR_(A)
#define M_LOWER_STR_(A) tolower(#A)

#define M_IF(P, T, E) CAT(M_IF_, P)(T, E)
#define M_IF_1(T, E) E
#define M_IF_2(T, E) T
#define M_FIRST(A, ...) A
#define M_SECOND(A, B, ...) B
#define M_ID(...) __VA_ARGS__

#define ENUM_ENAME(A) M_IF(VA_SIZE(M_ID A), M_FIRST A = M_SECOND A, A),
#define ENUM_ELEM(A) M_IF(VA_SIZE(M_ID A), M_FIRST A, A)
#define ENUM_ELEM_TYPE(A) Type::ENUM_ELEM(A)
#define ENUM_ELEM_NAME(A) M_LOWER_STR(ENUM_ELEM(A))
#define ENUM_STRING_TO_TYPE(A) {ENUM_ELEM_NAME(A), ENUM_ELEM_TYPE(A)},
#define ENUM_TYPE_TO_STRING(A) {ENUM_ELEM_TYPE(A), ENUM_ELEM_NAME(A)},
#define ENUM_TYPE(A) ENUM_ELEM_TYPE(A),

#define STRING_ENUM(name, ...) \
enum class name {  VA_FOR(ENUM_ENAME, __VA_ARGS__) };  \
class name##Helper { \
public: \
    typedef name Type; \
    static const std::string &to_string( Type enumVal ) \
    {\
        static const std::map<Type,std::string> enumStringsMap = { VA_FOR(ENUM_TYPE_TO_STRING, __VA_ARGS__) }; \
        auto it = enumStringsMap.find(enumVal);\
        static std::string emptyString;\
        if(it==enumStringsMap.end())\
            return emptyString;\
        return it->second;\
    }\
    static Type from_string(const std::string &value)\
    {\
        static const std::map<std::string,Type> enumStringsMap = { VA_FOR(ENUM_STRING_TO_TYPE, __VA_ARGS__) }; \
        auto it = enumStringsMap.find(value);\
        if(it==enumStringsMap.end())\
            return (Type)0;\
        return it->second;\
    }\
    static const std::vector<Type>& values()\
    {\
        static const std::vector<Type> valueVector = { VA_FOR(ENUM_TYPE, __VA_ARGS__) }; \
        return valueVector;\
    }\
private: \
    inline static char easytolower(char in) \
    { \
        if(in<='Z' && in>='A') \
            return in-('Z'-'z'); \
        return in; \
    }\
    static std::string tolower(std::string &&tolower) \
    { \
        std::string temp = tolower; \
        for (std::string::size_type i=0; i<temp.length(); ++i) \
                temp[i] = easytolower(temp[i]); \
        return temp; \
    } \
};

#endif




Вариант использования:

    STRING_ENUM( MyStringEnum, ONE, (TWO,4), THREE);
    MyStringEnum type;
    type =  MyStringEnumHelper::from_string("one");
    std::string stringType = MyStringEnumHelper::to_string(type);


Проверял на gcc 4.6-4.9 clang 3.2-3.3 MSVC 2013.

UPD пример относительно скорости http://ideone.com/1HNGIe

Конструктивная критика и рациональные предложения приветствуются.
Ads
AdBlock has stolen the banner, but banners are not teeth — they will be back

More

Comments 23

    +11
    Variadic templates не пробовали?
      0
      Пробовал не получилось к сожалению, если есть идея как это сделать без макросов буду очень признателен за подсказку.
        0
        Если я понял верно Вашу задачу, то может быть как-то так:

        //твое перечисление
        enum class MyEnum
        {
        	One,
        	Two,
        	Three
                //etc
        };
        //соответствующие перечислению строки
        char OneStr[] = "One";
        char TwoStr[] = "Two";
        char ThreeStr[] = "Three";
        //etc
        
        template <typename T, T t>
        struct ValueToType {}; //смотри Александреску
        
        template <MyEnum ParamEnum, typename Param, Param Val>
        class MyPair
        {
        public:
                //опять же, по поводу параметров этих функций, смотри Александреску
        	MyEnum toEnum(ValueToType<Param, Val> const&) { return ParamEnum; }
        	Param toParam(ValueToType<MyEnum, ParamEnum> const&) { return Val; }
        };
        
        template <class ... T>
        class MyEnumDecoder {}; //базовый шаблон, который мы "раскрутим" специализацией
        
        template <MyEnum ParamEnum, typename Param, Param Val, class ... T>
        class MyEnumDecoder<MyPair<ParamEnum, Param, Val>, T ...> : 
        	protected MyPair<ParamEnum, Param, Val>, protected MyEnumDecoder<T ...>
        {
        private:
        	using MyPair<ParamEnum, Param, Val>::toEnum;
        	using MyPair<ParamEnum, Param, Val>::toParam;
        
        	using MyEnumDecoder<T ...>::toEnum;
        	using MyEnumDecoder<T ...>::toParam;
        public:
        	MyEnumDecoder(MyPair<ParamEnum, Param, Val> mp, T ... others) : 
        		MyPair<ParamEnum, Param, Val>(mp), MyEnumDecoder<T ...>(others ...) {}
        
        	template <Param param>
        	MyEnum toEnum()
        	{
        		return this->toEnum(ValueToType<Param, param>());
        	}
        	template <MyEnum paramEnum>
        	Param toParam()
        	{
        		return this->toParam(ValueToType<MyEnum, paramEnum>());
        	}
        };
        
        //это замыкание рекурсии. чтобы понять, что происходит выше, смотри что происходит тут
        template <MyEnum ParamEnum, typename Param, Param Val>
        class MyEnumDecoder<MyPair<ParamEnum, Param, Val>> : 
        	protected MyPair<ParamEnum, Param, Val>
        {
        private:
        	using MyPair<ParamEnum, Param, Val>::toEnum;
        	using MyPair<ParamEnum, Param, Val>::toParam;
        public:
        	MyEnumDecoder(MyPair<ParamEnum, Param, Val> mp) : 
        		MyPair<ParamEnum, Param, Val>(mp) {}
        
        	template <Param param>
        	MyEnum toEnum()
        	{
        		return this->toEnum(ValueToType<Param, param>());
        	}
        	template <MyEnum paramEnum>
        	Param toParam()
        	{
        		return this->toParam(ValueToType<MyEnum, paramEnum>());
        	}
        };
        
        /*************************************************************************************/
        
        int _tmain(int argc, _TCHAR* argv[])
        {
        	MyPair<MyEnum::One, char*, OneStr> mpOne;
        	MyPair<MyEnum::Two, char*, TwoStr> mpTwo;
        	MyPair<MyEnum::Three, char*, ThreeStr> mpThree;
        
        	MyEnumDecoder<
        		MyPair<MyEnum::One, char*, OneStr>,
        		MyPair<MyEnum::Two, char*, TwoStr>,
        		MyPair<MyEnum::Three, char*, ThreeStr>
        	> mp(mpOne, mpTwo, mpThree);
        	
        	MyEnum me = mp.toEnum<OneStr>();
        	char* str = mp.toParam<MyEnum::Three>();
        	
        
        	getchar();
        
        	return 0;
        }
        
        
          0
          идея конечно в целом хорошая но чтобы сделать запись компактной все равно придется оборачивать ее в макросы
          и к тому же
              const char* value = "one";
              MyEnum me = mp.toEnum<value>();
              char* str = mp.toParam<me>();
          

          не компилируется а мне это не подходит, потому как именно эту строку я считываю из конфига, на этапе компиляции она как раз не известна.
            0
            Теперь ясно. Значит можно упростить и сделать проверку
            char* str = mp.toParam<me>();
            
            еще на этапе компиляции, а
            MyEnum me = mp.toEnum<value>();
            
            будет всегда рантайм проверка.
              0
              char* str = mp.toParam<me>();
              

              тоже не компилируется
              все параметры шаблонов должны быть известны еще на этапе компиляции
              а у меня преобразования могут быть и в ту, и в другую строну в рантайме
                0
                Да, Вы правы. Я подразумевал, что у Вас в коде стоит преобразование в духе:
                char* str = mp.toParam<MyEnum::One>();
                

                А так, действительно, вся карта отображения двух множеств будет только в рантайме.
                  0
                  единственная возможность оптимизации это ф-я tolower но на макросах этого сделать я не смог а constexpr не поддерживает даже 2013 MSVC(только November 2013 CTP of the Visual C++ Compiler)
                  к тому же всетаки поиск по map сравним по скорости даже с преобразованием строки в число, поэтому скорость парсинга конфигов почти такая же как с цифрами.
      +3
      Я решил подобную ситуацию инным способом. С помощью std::tuple.

      template<class Tag, class Type>
      struct TagValue{
        using tag = Tag;
        using type = Type;
        Type value;
        TagValue(Type &value) :   value(value){}
        TagValue(Type &&value) : value(forward<Type>(value) ){}
      };
      
      template<class Tag, class Type>
      using tv = TagValue<Tag, Type>;   // сокращенная запись
      
      // type autodetect
      /// call this to auto deduce &/&&
      template<typename _tag, typename T>
      TagValue<_tag, T> mtv(T &&value){
          return TagValue<_tag, T>( std::forward<T>(value) );
      }
      


      Затем
      namespace tags{
         class three;
      }
      using  namespace tags;
      
      auto list = make_tuple(
         tv<class one, int>(1) , 
         mtv<class two>(2),        // либо
         mtv<three>(3)             // либо  (если class three уже используется, можно не объявлять в namespace tags)
       )
      
      int num2 =  tuple_get_tag<class two>(list);
      int num3 =  tuple_get_tag<three>(list);
      


      tuple_get_tag — ф-ия которая фозвращает TagValue::value для заданого tag (compile-time, аналог std::get<> ).
      Получается что то вроде compile-time hash-map.

      P.S. если значения константные и типа int, можно либо использовать std::integral_constant, либо сделать структуру TagInt, аналогичную TagValue. (в обоих случаях будет полный compile-time).
        +1
        В Qt enum определённый в QObject классе можно преобразовывать к строке и обратно.
          +1
          В Qt для этого целый отдельный этап препроцессинга введён :)
          +8
          С одной стороны, хочется читаемых конфигов, с другой — быстрого парсинга и быстрого обращения по этому типу.

          Глядя на всё то, что вы понаписали после этой фразы, очень хочется спросить: для чего вам этого хочется? Нужны очень весомые основания, чтобы встраивать в свой проект такую жуть.

          Было бы здорово, если бы вы привели пример, когда действительно жизненно необходимо использовать перечисления, вместо хешей.
            0
            Плюсы решения
            1 — простота синтаксиса (синтаксис похож на обычным enum)
            2 — минимум кода (столько же сколько объявить обычный enum)
            3 — возможность сделать итерироваться по значениям enum
            4 — скорость работы (при мапе из 20 элементов скорость поиска в 6 раз выше чем std::map<string,int> и в 3.5 раза std::unordered_map<std::string,int>)
            5 — отсутствие зависимостей(кроме std::string std::map)
            6 — возможность использовать switch (что тоже положительно сказывается на скорости)
            7 — возможность приведения имен строк к нижнему регистру STRING_ENUM( MyStringEnum, ONE, TWO, THREE); в строку будет преобразовано как «one», «two», «three»
            8 — типобезопасность

            Минусы
            1 — Шаблонная магия
            2 — 32 элемента максимум

            пример относительно скорости ideone.com/1HNGIe
          • UFO just landed and posted this here
                –2
                А можно для идиотов: зачем вообще нужны читаемые конфиги, если они не предназначены для чтения человеческими глазами, и соответственно, вдумчивой правки руками?
                Если, конечно, под «читаемые» мы понимаем одно и то же, всё остальное смысла не имеет.
                  0
                  гейм девелопмент же. Там 80% всего — это конфиги
                    +1
                    Открою страшный секрет: конфиги в геймдеве пишут люди и в основном для людей, и как раз читаемость первична. Если конфигов много и их загрузка занимает порядочное время, то их можно как то обрабатывать. Был у меня проект где весь мир был в конфигах ~ 15mb XML далее скриптами делалось 2 выгонки для сервер и для клиента. Далее еще по ним проходился другой скрипт и удалял все что можно(пробелы табы неиспользуемые поля и тд). Но если проект не столь масштабен то читаемость на первом месте.
                    +2
                    Забавный велосипед, но если нужна кросс-язычность, лучше protobuf enum — особенно когда протобуф уже есть в проекте.
                      0
                      Кросс-язычность не нужна нужна кросс-платформенность а этот код собирается под всеми системами Windows — MinGW & MSVC 2013, Mac Os X & iOS — XCode(clang), Android GCC4.8+ Clang 3.2+
                      0
                      У предложенного подхода (на такого рода макросах) есть как минимум пара недостатков:
                      1. При большом размере перечисления компилятор начинает тупить на раскрытии всей этой макросни. А если компилятор и прожевывает, то начинает тупить IDE, которая тоже хочет всё это раскрыть в памяти.
                      2. Очевидно, поддерживаются перечисления, начинающиеся с 0 и нет возможности задания конкретного значения для элемента перечисления.
                      В своих собственных экспериментах я остановился в итоге на варианте, используемом также в llvm/clang'е, когда все такого рода enum'ы выносятся в отдельный .h-файл, поэлементно оборачиваются в макросы, а #define'ы перед включением этого .h-ника определяют то, что будет на выходе — определение enum'а, или сериализатор в строки.
                        0
                        Сорри, второй недостаток снимается. :) Увидел в тексте, что задавать значения для элементов таки можно.
                        0
                        Добрый день, newnon!
                        Меня тоже очень интересует данная тема. Прочитал Вашу статью в сентябре и решил вернуться к своему давнему желанию — сделать генератор Enum'ов. Если Вам интересно, вот сегодня опубликовал.

                        Спасибо за мотивацию! :)

                        Only users with full accounts can post comments. Log in, please.