[Из песочницы] Статические отображения

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

// Исходный тип enum class Fruit{ Unknown, Apple, Banana, Orange, };

// Преобразование в строку string fruitStr = toString (Fruit: Orange);

// Обратное преобразование из строки в исходный тип Fruit fruit = stringTo(fruitStr); Чтобы функционал отображения стал уникальным — необходимо обобщить интерфейс взаимодействия с контейнерами.Первое, что нам понадобиться — перегруженная функция доступа к карте отображений по исходному типу.

// Вспомогательный хранитель типа для разрешения перегрузки template struct TypeHolder {};

// Сигнатура функции получения доступа к контейнеру отображения ContainerType const& viewMapOf (TypeHolder); Второе, что нам понадобиться — интерфейс и организация самого контейнера отображений.

template 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;

ViewMap () { ;; } ViewMap (std: initializer_list 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 { using Base = ViewMap; using View = typename Base: View;

StringViewMap () { ;; } StringViewMap (std: initializer_list 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 string toString (SourceType id) { using MapRef = typename ViewMapTraits:: MapRef; using PureMap = typename ViewMapTraits:: PureMap;

MapRef map = viewMapOf (TypeHolder()) ;

auto const& views = map.views; auto pos = views.find (typename PureMap: View (id)) ;

return PureMap: extractString ((pos!= views.end ()) ? *pos: map.invalidView) ; }

template SourceType stringTo (string const& str) { using MapRef = typename ViewMapTraits:: MapRef; using PureMap = typename ViewMapTraits:: PureMap;

MapRef map = viewMapOf (TypeHolder()) ; 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 struct ViewMapTraits { using MapRef = decltype (mapViewOf (TypeHolder())) ; using PureMap = typename std: remove_cv:: type>:: type; }; И, наконец, реализация viewMapOf для перечисления Fruit:

StringViewMap const& viewMapOf (TypeHolder) { static StringViewMap 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 { using Base = ViewMap; using View = typename Base: View;

MixViewMap () { ;; } MixViewMap (std: initializer_list 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) { 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 Fruit toFruit (SourceType id) { using MapRef = typename ViewMapTraits:: MapRef; using PureMap = typename ViewMapTraits:: PureMap;

MapRef map = viewMapOf (TypeHolder()) ;

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(redAppleStr); // mix == Mix: RedApple

Fruit mixFruit = toFruit (mix); // mixFruit == Fruit: Apple

string mixFruitStr = toString (mixFruit); // mixFruitStr == «apple»

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

© Habrahabr.ru