[英]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
);
但是在內部這將需要折衷-兩個映射(從A
到B
以及從B
到A
)或一個映射,例如從A
到B
以及線性搜索B
到A
轉換。
是否可以在標准C++14
實現這一點,以便:
根據要求,即:
A
的值未映射到B
的相同基礎值 A
和B
基礎類型可能不同 ?
您可以使用功能模板和完全專業的功能輕松完成此操作。 您使主模板返回無效的大小寫,然后專業化將返回所需的映射。
如果你有
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
的映射的情況下無效。 如果你不想,你可以什么添加到宏from
和to
值使用無效的映射,比如
#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)。
由於您的A
和B
所有枚舉器均未初始化,因此第一個枚舉器的值為零,第二個枚舉器的值為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
的枚舉數,對應於B
第i
個枚舉數,反之亦然。 在此設置中,我們可以從執行查找A a
以B
作為arrB[std::size(a)]
,並且反之亦然,具有復雜度O(1)。
以下類biENumMap
是上述雙向方法在C ++ 14和更高biENumMap
上的實現示例。 請注意,由於擴展的constexpr可從C ++ 14獲得,因此ctor也可以是常量表達式。 兩個重載operator()
分別是A
和B
查找函數。 這些也可以是常量表達式,該類使我們能夠在編譯時和運行時執行雙向查找:
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)];
}
};
我們可以如下使用此類:
// 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.