具有:
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
的基础类型可能不同?
答案 0 :(得分:2)
您可以使用功能模板和完全专业的功能轻松完成此操作。您使主模板返回无效的大小写,然后专业化将返回您想要的映射。
如果有
#define PARAMS_AND_CTOR(structname) \
private: \
params _someParams; \
public: \
structname(const params& p) noexcept : _someParams(p) { }
然后您可以添加所有映射值,例如
struct foo {
PARAMS_AND_CTOR(foo)
void banana() { }
void apple() { }
};
struct bar {
PARAMS_AND_CTOR(bar)
void watermelon() { }
void orange() { }
void strawberry() { }
};
然后您将其命名为
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
答案 1 :(得分:1)
就实现优雅而言,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();
};
(这使用折叠表达式+逗号运算符为数组元素分配值,例如参见here。)
用户代码提供了要使用的映射对的列表,并且已完成:
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{});
Demo,包括完整的编译时测试用例和最高效率的运行时代码(实际上只是mov
)。
以下是在编译时仅 可用的先前版本(另请参阅修订历史记录):https://godbolt.org/z/GCkAhn
答案 2 :(得分:1)
如果需要执行运行时查找,以下方法将在两个方向上都具有复杂度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
的枚举数,对应于i
的第B
个枚举数,反之亦然。
在此设置中,我们可以从A a
到B
进行arrB[std::size(a)]
的查找,反之亦然,复杂度为O(1)。
以下类biENumMap
是上述双向方法在C ++ 14和更高版本上的实现示例。
请注意,由于extended 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);