有效的双向作用域枚举映射

时间:2019-03-26 15:44:17

标签: c++ enums c++14

具有:

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的基础类型可能不同
  • 在编译时已知映射

3 个答案:

答案 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)。

由于尚未初始化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的枚举数,对应于i的第B个枚举数,反之亦然。 在此设置中,我们可以从A aB进行arrB[std::size(a)]的查找,反之亦然,复杂度为O(1)。


以下类biENumMap是上述双向方法在C ++ 14和更高版本上的实现示例。 请注意,由于extended 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);