簡體   English   中英

有效的雙向作用域枚舉映射

[英]Effective bidirectional scoped enum mapping

有:

enum class A : int {
    FirstA,
    SecondA,
    InvalidB
};

enum class B : int {
    FirstB,
    SecondB,
    InvalidB
};

如何啟用這樣的功能?

B b = mapper[A::FirstA];
A a = mapper[B::SecondB];

一種可能的解決方案是創建一個Mapper模板類,該類允許通過構造函數中的初始化列表指定映射,如下所示:

Mapper<A, B> mapper(
     {
     {A::FirstA,   B::SecondB},
     {A::SecondA,  B::FirstB}
     },
     {A::InvalidA, B::InvalidB} // this is for conversions, where no mapping is specified
);

但是在內部這將需要折衷-兩個映射(從AB以及從BA )或一個映射,例如從AB以及線性搜索BA轉換。

是否可以在標准C++14實現這一點,以便:

  • 沒有使用雙容器
  • 雙向查找性能同樣好
  • 定義和使用映射相對簡單(不需要內部實現)

根據要求,即:

  • 它不是身份映射(即,來自A的值未映射到B的相同基礎值
  • AB基礎類型可能不同
  • 映射在編譯時是已知的

您可以使用功能模板和完全專業的功能輕松完成此操作。 您使主模板返回無效的大小寫,然后專業化將返回所需的映射。

如果你有

template<A>
B mapper() { return B::InvalidB; }
template<B>
A mapper() { return A::InvalidA; }

然后您可以添加所有映射的值,例如

template<>
B mapper<A::FirstA>() { return B::SecondB; }
template<>
B mapper<A::SecondA>() { return B::FirstB; }
template<>
A mapper<B::FirstB>() { return A::SecondA; }
template<>
A mapper<B::SecondB>() { return A::FirstA; }

然后你會這樣稱呼它

B b = mapper<A::FirstA>();
A a = mapper<B::SecondB>();

這使您完全沒有容器。 您甚至可以制作一些宏來簡化操作,例如

#define MAKE_ENUM_MAP(from, to) \
template<from> \
auto mapper() { return to::Invalid; } \
template<to> \
auto mapper() { return from::Invalid; }


#define ADD_MAPPING(from_value, to_value) \
template<> \
auto mapper<from_value>() { return to_value; } \
template<> \
auto mapper<to_value>() { return from_value; }

然后你會用它們像

MAKE_ENUM_MAP(A, B)
ADD_MAPPING(A::FirstA, B::SecondB)
ADD_MAPPING(A::SecondA, B::FirstB)

為您生成所有代碼。 上述版本使用的一個單一的枚舉值Invalid的映射的情況下無效。 如果你不想,你可以什么添加到宏fromto值使用無效的映射,比如

#define MAKE_ENUM_MAP(from, from_value, to, to_value) \
template<from> \
auto mapper() { return to_value; } \
template<to> \
auto mapper() { return from_value; }

你會這樣稱呼它

MAKE_ENUM_MAP(A, A::InvalidA, B, B::InvalidB)

內森(Nathan)的解決方案在實現優雅方面很難被擊敗。 但是,如果您迫切需要一種不依賴宏或也可以在運行時使用的解決方案,可以在這里在一個簡單的配對列表中指定映射。

從本質上講,我們利用了兩個枚舉都應具有連續的基礎整數值(從零開始)的事實,這意味着我們可以將兩個方向的映射表示為簡單數組。 這都是constexpr因此在編譯時的開銷為零。 為了在運行時使用,它確實存儲了兩次信息以允許即時查找,但只需要N (sizeof(A) + sizeof(B))存儲。 我不知道有什么數據結構可以做得更好(即,除了兩個數組之一之外,不存儲任何其他數據,而且比兩個方向上的線性搜索都好)。 請注意,這與存儲對本身一樣占用相同的存儲空間(但不會從映射的雙射性中獲得任何收益)。

template<class TA, class TB, class ... Pairs>
struct Mapper
{
    constexpr static std::array<TA, sizeof...(Pairs)> generateAIndices()
    {
        std::array<TA, sizeof...(Pairs)> ret{};
        ((void)((ret[static_cast<std::size_t>(Pairs::tb)] = Pairs::ta), 0), ...);
        return ret;
    }
    constexpr static std::array<TB, sizeof...(Pairs)> generateBIndices()
    {
        std::array<TB, sizeof...(Pairs)> ret{};
        ((void)((ret[static_cast<std::size_t>(Pairs::ta)] = Pairs::tb), 0), ...);
        return ret;
    }

    constexpr TB operator[](TA ta)
    {
        return toB[static_cast<std::size_t>(ta)];
    }
    constexpr TA operator[](TB tb)
    {
        return toA[static_cast<std::size_t>(tb)];
    }

    static constexpr std::array<TA, sizeof...(Pairs)> toA = generateAIndices();
    static constexpr std::array<TB, sizeof...(Pairs)> toB = generateBIndices();
};

(這使用折疊表達式+逗號運算符為數組元素分配值,請參見例如此處 )。

用戶代碼提供了要使用的映射對的列表,並已完成:

using MyMappingList = PairList<
    MyMappingPair<A::A1, B::B2>,
    MyMappingPair<A::A2, B::B3>,
    MyMappingPair<A::A3, B::B4>,
    MyMappingPair<A::A4, B::B1>
    >;

auto mapper = makeMapper<A, B>(MyMappingList{});

該演示包括完整的編譯時測試用例和最高效率的運行時代碼(實際上只是mov )。


這是在編譯時有效的先前版本(另請參閱修訂歷史記錄): https : //godbolt.org/z/GCkAhn

如果需要執行運行時查找,則以下方法將在兩個方向上都具有復雜度O(1)。

由於您的AB所有枚舉器均未初始化,因此第一個枚舉器的值為零,第二個枚舉器的值為1 ,依此類推。 關於這些從零開始的整數作為數組的索引,我們可以使用兩個數組構造一個雙向映射。 例如,假設當前映射為

A::FirstA  (=0) <--> B::SecondB (=1),
A::SecondA (=1) <--> B::FirstB  (=0),

,然后讓我們定義以下兩個數組

A arrA[2] = {A::SecondA, A::FirstA},
B arrB[2] = {B::SecondB, B::FirstB},

其中arrA[i]A的枚舉數,對應於Bi個枚舉數,反之亦然。 在此設置中,我們可以從執行查找A aB作為arrB[std::size(a)] ,並且反之亦然,具有復雜度O(1)。


以下類biENumMap是上述雙向方法在C ++ 14和更高biENumMap上的實現示例。 請注意,由於擴展的constexpr可從C ++ 14獲得,因此ctor也可以是常量表達式。 兩個重載operator()分別是AB查找函數。 這些也可以是常量表達式,該類使我們能夠在編譯時和運行時執行雙向查找:

template<std::size_t N>
class biENumMap
{
    A arrA[N];
    B arrB[N];

public:
    constexpr biENumMap(const std::array<std::pair<A,B>, N>& init) 
        : arrA(), arrB()
    {        
        for(std::size_t i = 0; i < N; ++i)
        {
            const auto& p = init[i];
            arrA[static_cast<std::size_t>(p.second)] = p.first;
            arrB[static_cast<std::size_t>(p.first) ] = p.second;
        }
    }

    constexpr A operator()(B b) const{
        return arrA[static_cast<std::size_t>(b)];
    }

    constexpr B operator()(A a) const{
        return arrB[static_cast<std::size_t>(a)];
    }
};

我們可以如下使用此類:

DEMO

// compile-time construction.
constexpr biEnumMap<3> mapper({{
    {A::FirstA  , B::SecondB },
    {A::SecondA , B::FirstB  },
    {A::InvalidA, B::InvalidB} }});

// compile-time tests, A to B.
static_assert(mapper(A::FirstA  ) == B::SecondB );
static_assert(mapper(A::SecondA ) == B::FirstB  );
static_assert(mapper(A::InvalidA) == B::InvalidB);

// compile-time tests, B to A.
static_assert(mapper(B::FirstB  ) == A::SecondA );
static_assert(mapper(B::SecondB ) == A::FirstA  );
static_assert(mapper(B::InvalidB) == A::InvalidA);

// run-time tests, A to B.
std::vector<A> vA = {A::FirstA, A::SecondA, A::InvalidA};
assert(mapper(vA[0]) == B::SecondB );
assert(mapper(vA[1]) == B::FirstB  );
assert(mapper(vA[2]) == B::InvalidB);    

// run-time tests, B to A.
std::vector<B> vB = {B::FirstB, B::SecondB, B::InvalidB};
assert(mapper(vB[0]) == A::SecondA );
assert(mapper(vB[1]) == A::FirstA  );
assert(mapper(vB[2]) == A::InvalidA);   

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM