Pull to refresh

Comments 23

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

//твое перечисление
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;
}

идея конечно в целом хорошая но чтобы сделать запись компактной все равно придется оборачивать ее в макросы
и к тому же
    const char* value = "one";
    MyEnum me = mp.toEnum<value>();
    char* str = mp.toParam<me>();

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

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

А так, действительно, вся карта отображения двух множеств будет только в рантайме.
единственная возможность оптимизации это ф-я tolower но на макросах этого сделать я не смог а constexpr не поддерживает даже 2013 MSVC(только November 2013 CTP of the Visual C++ Compiler)
к тому же всетаки поиск по map сравним по скорости даже с преобразованием строки в число, поэтому скорость парсинга конфигов почти такая же как с цифрами.
Я решил подобную ситуацию инным способом. С помощью 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).
В Qt enum определённый в QObject классе можно преобразовывать к строке и обратно.
В Qt для этого целый отдельный этап препроцессинга введён :)
С одной стороны, хочется читаемых конфигов, с другой — быстрого парсинга и быстрого обращения по этому типу.

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

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

Спасибо за мотивацию! :)
Only those users with full accounts are able to leave comments. Log in, please.