📜 ⬆️ ⬇️

Expandable mappings of any type of value without using macros

Often you have to deal with such moments as mapping an enumeration to a string and back, or to a fixed value for further serialization. Here is a way to uniquely map a mapped value of type A to a value of type B and back. I tried to endow the method with a bit of flexibility, making it easy to expand the mapping options.

In short, the essence of the method is that there is a static container - a map of mappings - as well as a number of auxiliary functions that expand to meet the needs of the project and eliminate the direct interaction with the container.

As a result, the conversion from the source type to the string and back will look like this:

//   enum class Fruit{ Unknown, Apple, Banana, Orange, }; //    string fruitStr = toString(Fruit::Orange); //        Fruit fruit = stringTo<Fruit>(fruitStr); 

')
In order for the display functionality to become unique, it is necessary to generalize the interface for interacting with containers.
The first thing we need is the overloaded function of accessing the map of mappings according to the original type.

 //       template<typename> struct TypeHolder {}; //        ContainerType const& viewMapOf(TypeHolder<Fruits>); 


The second thing we need is the interface and organization of the display container itself.

 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; //   } ; 


To display a string, you must expand the fields of the VievMap :: View structure. Additional fields I call options. Here is what the finished container template looks like:

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


As you can see, StringViewMap inherits all the basic ViewMap functionality, extending it with the auxiliary function extractString.
Now it is very simple to implement the toString functionality, 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 ) ; } 


The whole secret toString and stringTo is to use the container interface - namely, its extractOrigin and extractString functions. Thus, stringTo, toString will only work with the mappings that the extractString interface provides.

ViewMapTraits is necessary because the signature of the overloaded viewMapOf function may be different for different overloads, namely, the return value may be both a reference and an object. This is what it is inside:

 template<typename SourceType> struct ViewMapTraits { using MapRef = decltype( mapViewOf(TypeHolder<SourceType>()) ) ; using PureMap = typename std::remove_cv<typename std::remove_reference<MapRef>::type>::type ; }; 


And finally, the viewMapOf implementation for the Fruit enumeration:

 StringViewMap<Fruit> const& viewMapOf(TypeHolder<Fruit>) { static StringViewMap<Fruit> viewMap = { { {Fruit::Apple, {"apple"}}, {Fruit::Banana, {"banana"}}, {Fruit::Orange, {"orange"}}, }, {Fruit::Unknown, {"unknown"}} }; return viewMap ; } 


All basic functionality is ready. I will show how to add additional display options using the example of a new listing:

 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 ) ; } 


Here added an additional feature - toFruit classifier. Its meaning is the same as that of toString, slightly changed content. Now I will demonstrate the work of transformations:

 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" 


I apply this technique in my projects - very convenient. Surely there are ideas for improvement - here I outlined the main approach.

The advantages of this approach:

Disadvantages:


UPD: Changed the title to a more suitable one and added advantages and disadvantages of the approach

Source: https://habr.com/ru/post/238077/


All Articles