I've been working on some kind of mapper that would allow me to map between types.
My objectives:
- it should allow mapping between two enums
- it should allow mapping enum to string_view
- it should allow reverse mapping using a single configuration (enum1 <-> enum2, enum <-> string_view)
- the fewer resources it uses the better
- the more efficient it is the better (not essential)
- easy use, clear interface
- conformed to C++20 standard
- compilable with gcc 10.2 up
I've already created some code. At the first glance it does what I want, also has quite easy to use interface, but I'm looking for some potential problems, as well as some places where I could do some refactoring. Also, maybe it just seemingly looks ok but as the usage of this mapper will spread around my code base, some problems may emerge.
Code is below, also I'm attaching the Compiler Explorer link: click
I'm looking forward to hearing your opinions.
// #include <iostream> #include <tuple> #include <optional> #include <string_view> #include <cassert> template<size_t Size> struct FixedString { char data_[Size + 1] {}; constexpr FixedString(const char* src) { std::copy(src, src + Size, std::begin(data_)); } constexpr FixedString(const std::string_view src) { std::copy(src, src.size(), std::begin(data_)); } constexpr operator const char*() const { return data_; } constexpr operator std::string_view() const { return std::string_view { data_}; } }; template<size_t Size> FixedString(const char (&)[Size]) -> FixedString<Size - 1>; template<auto T1, auto T2> struct Tie { constexpr static decltype(T1) t1 = T1; constexpr static decltype(T2) t2 = T2; }; template<auto T1, FixedString T2> struct TieStr { constexpr static decltype(T1) t1 = T1; constexpr static decltype(T2) t2 = T2; }; template<class = void> class Mapper; template<class T> struct IsFixedString : std::false_type {}; template<size_t N> struct IsFixedString<FixedString<N>> : std::true_type {}; template<class Config> class Mapper { public: template<class...T> using MakeConfig = std::tuple<T...>; template<class...T> using MakeMapper = Mapper<MakeConfig<T...>>; template<class Destination, class Source> static constexpr auto map(const Source& source) { return do_map<Destination, Source>(source); } private: template<class Destination, class Source> static constexpr auto do_map(const Source& source) { static_assert(!std::is_same_v<Destination, Source>, "Destination and Source types cannot be the same."); return mape_types<Destination>(source, std::make_integer_sequence<uint32_t, std::tuple_size_v<Config>>{}); } template<bool Flag = false> static constexpr void type_mismatch() { static_assert(Flag, "Mapper cannot perform mapping."); } template <class Destination, class Source, uint32_t... Is> static auto mape_types(const Source& source, std::integer_sequence<uint32_t, Is...>) { auto destination = std::optional<Destination>{}; ( tryToMap<Is, Destination, Source>(destination, source) || ... ); return destination; } template<auto Idx, class Destination, class Source> static bool tryToMap(std::optional<Destination>& ret_val, const Source& src) { using SelectedTie = std::tuple_element_t<Idx, Config>; using RawDst_t = std::decay_t<Destination>; using RawSrc_t = std::decay_t<Source>; using RawT1_t = std::decay_t<decltype(SelectedTie::t1)>; using RawT2_t = std::decay_t<decltype(SelectedTie::t2)>; static_assert( not std::is_convertible_v<RawT1_t, RawDst_t> || not std::is_convertible_v<RawT2_t, RawDst_t> || std::is_same_v<RawDst_t, RawT1_t> || std::is_same_v<RawDst_t, RawT2_t>, "Destination type does not belong to the configuration." ); if constexpr (std::is_same_v<RawSrc_t, RawT1_t> || std::is_convertible_v<RawSrc_t, RawT1_t>) { if (SelectedTie::t1 == src) { ret_val.emplace(SelectedTie::t2); return true; } } else if constexpr (std::is_same_v<RawSrc_t, RawT2_t> || std::is_convertible_v<RawSrc_t, RawT2_t>) { if (SelectedTie::t2 == src) { ret_val.emplace(SelectedTie::t1); return true; } } else { type_mismatch(); } return false; } }; enum class Enum1_src { A, B, C }; enum class Enum1_dst { A, B, C }; //Configure mapper using Mp1 = Mapper<>::MakeMapper< Tie<Enum1_src::A, Enum1_dst::A>, Tie<Enum1_src::B, Enum1_dst::B>, Tie<Enum1_src::C, Enum1_dst::C> >; using Mp2StrMapper = Mapper<>::MakeMapper< TieStr<Enum1_src::A, "Enum1_src::A_string">, TieStr<Enum1_src::B, "Enum1_src::B_string">, TieStr<Enum1_src::C, "Enum1_src::C_string"> >; int main() { using namespace std::literals; //enum to string_view assert(Mp2StrMapper::map<std::string_view>(Enum1_src::A).value() == "Enum1_src::A_string"); assert(Mp2StrMapper::map<std::string_view>(Enum1_src::B).value() == "Enum1_src::B_string"); assert(Mp2StrMapper::map<std::string_view>(Enum1_src::C).value() == "Enum1_src::C_string"); //string_view to enum assert(Mp2StrMapper::map<Enum1_src>("Enum1_src::A_string"sv) == Enum1_src::A); assert(Mp2StrMapper::map<Enum1_src>("Enum1_src::B_string"sv) == Enum1_src::B); assert(Mp2StrMapper::map<Enum1_src>("Enum1_src::C_string"sv) == Enum1_src::C); //enum to enum assert(Mp1::map<Enum1_dst>(Enum1_src::A) == Enum1_dst::A); assert(Mp1::map<Enum1_dst>(Enum1_src::B) == Enum1_dst::B); assert(Mp1::map<Enum1_dst>(Enum1_src::C) == Enum1_dst::C); }