[Из песочницы] 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 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 stringsEnumMap = _make_strings_enum_map (); std: map:: const_iterator it = stringsEnumMap.find (value); if (it==stringsEnumMap.end ()) return (type)0; return it→second; } static const std: vector& values () { static const std: vector valueVector = _make_values (); return valueVector; } private: static const std: vector _make_values () { std: vector valueVector; valueVector.reserve (3); valueVector.push_back (one); valueVector.push_back (two); valueVector.push_back (three); return valueVector;\ } static std: map _make_enum_strings_map () { std: map 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 _make_strings_enum_map () { std: map 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 — достаточное количество аргументов для большинства задач.

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

#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 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 stringsEnumMap = _make_strings_enum_map (); \ std: map:: const_iterator it = stringsEnumMap.find (value);\ if (it==stringsEnumMap.end ())\ return (Type)0;\ return it→second;\ }\ static const std: vector& values ()\ {\ static const std: vector valueVector = _make_values ();\ return valueVector;\ }\ private:\ static const std: vector _make_values ()\ {\ std: vector valueVector;\ valueVector.reserve (VA_SIZE (__VA_ARGS__));\ VA_FOR (ENUM_TO_VECTOR, valueVector, __VA_ARGS__)\ return valueVector;\ }\ static std: map _make_enum_strings_map ()\ {\ std: map enumStringsMap;\ VA_FOR (ENUM_ENUM_TO_STRING, enumStringsMap, __VA_ARGS__)\ return enumStringsMap;\ }\ static std: map _make_strings_enum_map ()\ {\ std: map 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 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 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& values () { static const std: vector 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

Type: Type type; type = Type: from_string («one»); std: string stringType = Type: to_string (type); Также в процессе адаптации было принято решение разрешить назначать конкретное значение определенному значению. Идея была подсмотрена тут.

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

#ifndef _StringEnums_h #define _StringEnums_h

#include #include #include

#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 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 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& values ()\ {\ static const std: vector 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

#endif

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

STRING_ENUM (MyStringEnum, one, (two,4), three); MyStringEnum: Type type; type = MyStringEnum: from_string («one»); std: string stringType = MyStringEnum: to_string (type); Проверял на gcc 4.6–4.9 clang 3.2–3.3 MSVC 2013.

Конструктивная критика и рациональные предложения приветствуются.

© Habrahabr.ru