Часто приходится сталкиваться в такими моментами, как отображение перечисления в строку и обратно, либо в фиксированное значения для дальнейшей сериализации. Здесь приводиться способ однозначного статического отображения значения типа A в значение типа B и обратно. Я попытался наделить метод небольшой гибкостью, позволяющей легко расширять варианты отображений.
В кратце суть метода состоит в том, что имеется статический контейнер — карта отображений — а так же ряд вспомогательных функций, расширяемых под нужды проекта и избавляющих от прямого взаимодействия с контейнером.
В итоге преобразование из исходного типа в строку и обратно будет выглядеть так:
// Исходный тип
enum class Fruit{
Unknown,
Apple,
Banana,
Orange,
};
// Преобразование в строку
string fruitStr = toString(Fruit::Orange);
// Обратное преобразование из строки в исходный тип
Fruit fruit = stringTo<Fruit>(fruitStr);
Чтобы функционал отображения стал уникальным — необходимо обобщить интерфейс взаимодействия с контейнерами.
Первое, что нам понадобиться — перегруженная функция доступа к карте отображений по исходному типу.
// Вспомогательный хранитель типа для разрешения перегрузки
template<typename> struct TypeHolder {};
// Сигнатура функции получения доступа к контейнеру отображения
ContainerType const& viewMapOf(TypeHolder<Fruits>);
Второе, что нам понадобиться — интерфейс и организация самого контейнера отображений.
template<typename SourceT, typename VariantsT>
struct ViewMap
{
// Исходный тип
using SourceType = SourceT ;
// Структура отображения
struct View: VariantsT
{
View(SourceT id=SourceT(), VariantsT vnt=VariantsT()): VariantsT(vnt), origin(id) { ;; }
bool operator<(View const& b) const { return origin < b.origin ; }
SourceT origin ; //< Исходное значение
};
using Views = std::set<View> ;
ViewMap() { ;; }
ViewMap(std::initializer_list<View> const& initViews, View const& initInvalidView): views(initViews), invalidView(initInvalidView) { ;; }
// Получение исходного типа
static SourceT extractOrigin(View const& view) { return view.origin ; }
Views views; // Карта отображений
View invalidView; // Отображение ошибки
} ;
Для отображения в строку необходимо расширить поля структуры VievMap::View. Дополнительные поля я называю вариантами. Вот как выглядит готовый шаблон контейнера:
struct StringVariant
{
StringVariant(std::string const& s = ""): str(s) { ;; }
std::string str ;
};
// Карта отображений в строку
template< typename SourceT >
struct StringViewMap: ViewMap<SourceT,StringVariant>
{
using Base = ViewMap<SourceT, StringVariant>;
using View = typename Base::View;
StringViewMap() { ;; }
StringViewMap(std::initializer_list<View> const& mapInit, View const& invalidInit): Base(mapInit,invalidInit) { ;; }
// Извлечение строки из отображения
static std::string const& extractString(typename Base::View const& view) { return view.str ; }
} ;
Как видно, StringViewMap наследует весь базовый функционал ViewMap, расширяя его вспомогательной функцией extractString.
Теперь реализовать функционал toString, stringTo очень просто:
template<typename SourceType>
string toString(SourceType id)
{
using MapRef = typename ViewMapTraits<SourceType>::MapRef;
using PureMap = typename ViewMapTraits<SourceType>::PureMap;
MapRef map = viewMapOf(TypeHolder<SourceType>()) ;
auto const& views = map.views ;
auto pos = views.find(typename PureMap::View(id)) ;
return PureMap::extractString( (pos != views.end()) ? *pos : map.invalidView ) ;
}
template<typename SourceType>
SourceType stringTo( string const& str )
{
using MapRef = typename ViewMapTraits<SourceType>::MapRef;
using PureMap = typename ViewMapTraits<SourceType>::PureMap;
MapRef map = viewMapOf(TypeHolder<SourceType>()) ;
auto const& views = map.views ;
auto pos = std::find_if(
views.begin(),
views.end(),
[&](typename PureMap::View const& val) { return PureMap::extractString(val) == str ; }
) ;
return PureMap::extractOrigin( (pos != views.end()) ? *pos : map.invalidView ) ;
}
Весь секрет toString и stringTo в использовании интерфейса контейнера — а именно его фукнций extractOrigin и extractString. Таким образом stringTo, toString будет работать только с теми отображениями, что предоставляют интерфейс extractString.
ViewMapTraits необходим ввиду того, что сигнатура перегруженной функции viewMapOf может отличаться для разных перегрузок, а именно возвращаемое значение, может быть как ссылкой так и объектом. Вот каков он внутри:
template<typename SourceType>
struct ViewMapTraits
{
using MapRef = decltype( mapViewOf(TypeHolder<SourceType>()) ) ;
using PureMap = typename std::remove_cv<typename std::remove_reference<MapRef>::type>::type ;
};
И, наконец, реализация viewMapOf для перечисления Fruit:
StringViewMap<Fruit> const& viewMapOf(TypeHolder<Fruit>)
{
static StringViewMap<Fruit> viewMap = {
{
{Fruit::Apple, {"apple"}},
{Fruit::Banana, {"banana"}},
{Fruit::Orange, {"orange"}},
},
{Fruit::Unknown, {"unknown"}}
};
return viewMap ;
}
Весь базовый функционал готов. Я покажу как добавить дополнительные варианты отображения на примере нового перечисления:
enum class Mix
{
Unknown,
RedApple,
GreenApple,
GreenBanana,
BigOrange,
SmallOrange,
};
// Варианты отображения для Mix
struct MixVariant
{
MixVariant(Fruit f = Fruit::Unknown, std::string const& s = ""):
fruit(f), str(s) { ;; }
Fruit fruit ; // Классификация фрукта
std::string str ; // Строковое представление
};
// Карта отображений
struct MixViewMap: ViewMap<Mix,MixVariant>
{
using Base = ViewMap<Mix,MixVariant>;
using View = typename Base::View;
MixViewMap() { ;; }
MixViewMap(std::initializer_list<View> const& mapInit, View const& invalidInit): Base(mapInit,invalidInit) { ;; }
// Интерфейс для toString, stringTo
static std::string const& extractString(typename Base::View const& view) { return view.str ; }
// Интерфейс для toFruit
static std::string const& extractFruit(typename Base::View const& view) { return view.fruit ; }
} ;
// Заполняем карту
MixViewMap const& viewMapOf(TypeHolder<Mix>)
{
static MixViewMap map = {
{
{Mix::RedApple, {Fruit::Apple, "red_apple"}},
{Mix::GreenApple, {Fruit::Apple, "green_apple"}},
{Mix::GreenBanana, {Fruit::Banana, "green_banana"}},
{Mix::BigOrange, {Fruit::Orange, "big_orange"}},
{Mix::SmallOrange, {Fruit::Orange, "small_orange"}},
},
{Mix::Unknown, {Fruit::Unknown, "unknown"}},
};
return map ;
}
// Вспомогательная функция классификации
template<typename SourceType>
Fruit toFruit(SourceType id)
{
using MapRef = typename ViewMapTraits<SourceType>::MapRef;
using PureMap = typename ViewMapTraits<SourceType>::PureMap;
MapRef map = viewMapOf(TypeHolder<SourceType>()) ;
auto const& views = map.views ;
auto pos = views.find(typename PureMap::View(id)) ;
return PureMap::extractFruit( (pos != views.end()) ? *pos : map.invalidView ) ;
}
Здесь добавили дополнительную функцию — классификатор toFruit. Смысл ее тот же, что и у toString, изменилось немного содержание. Теперь продемонстрирую работу преобразований:
string redAppleStr = "red_apple";
Mix mix = stringTo<Mix>(redAppleStr);
// mix == Mix::RedApple
Fruit mixFruit = toFruit(mix);
// mixFruit == Fruit::Apple
string mixFruitStr = toString(mixFruit);
// mixFruitStr == "apple"
Применяю данную технику в своих проектах — очень удобно. Наверняка есть идеи по улучшению — здесь я изложил основной подход.
Автор: saydex