我想在C++ 17中高效地双向映射一些不同类型的值(只有很少的值进行1:1映射)。例如考虑映射枚举值和整数,尽管该问题也适用于其他类型。目前,我正在这样做:

#include <optional>

enum class ExampleEnum { A, B, C, D, E };

class MyMapping {
public:
    std::optional<int> enumToInt(ExampleEnum v) {
        switch(v) {
        case ExampleEnum::A:
            return 1;
        case ExampleEnum::B:
            return 5;
        case ExampleEnum::D:
            return 42;
        }
        return std::nullopt;
    }

    std::optional<ExampleEnum> intToEnum(int v) {
        switch(v) {
        case 1:
            return ExampleEnum::A;
        case 5:
            return ExampleEnum::B;
        case 42:
            return ExampleEnum::D;
        }
        return std::nullopt;
    }
};

这具有明显的缺点,即必须两次编写所有内容,而忘记更新其中一个功能将导致不一致。有没有更好的方法?

我需要:
  • 一致性。在映射和反向映射中应该不可能有不同的语义。
  • 编译时定义。映射的值是事先已知的,并且在运行时不会更改。
  • 运行时查找。在编译时不知道将查找哪些值,甚至可能根本不包含映射(而是返回一个空的可选值)。

  • 我想拥有:
  • 没有额外的内存分配
  • 与双开关方法
  • 基本相同的性能
  • 一种实现,使映射定义易于扩展(即将来添加更多值或将其应用于其他类型)
  • 最佳答案

    我已经尝试过非常幼稚和简单的实现。 https://godbolt.org/z/MtcHw8

    #include <optional>
    enum class ExampleEnum { A, B, C, D, E };
    
    
    template<typename Enum, int N>
    struct Mapping
    {
        Enum keys[N];
        int values[N];
    
        constexpr std::optional<Enum> at(int x) const noexcept
        {
            for(int i = 0; i < N; i++)
                if(values[i] == x) return keys[i];
            return std::nullopt;
        }
    
        constexpr std::optional<int> at(Enum x) const noexcept
        {
            for(int i = 0; i < N; i++)
                if(keys[i] == x) return values[i];
            return std::nullopt;
        }
    };
    
    constexpr Mapping<ExampleEnum, 3> mapping{{ExampleEnum::A, ExampleEnum::B, ExampleEnum::D},
                                             {111, 222, 333}};
    
    int main()
    {
        int x = rand(); // Force runtime implementation
        auto optEnum = mapping.at(x);
        if(optEnum.has_value())
            return *mapping.at(ExampleEnum::B); // Returns 222, (asm line 3) constexpr works
    
        auto y = (ExampleEnum)rand(); // Force runtime implementation
        auto optInt = mapping.at(y);
        if(optInt.has_value())
            return (int)*mapping.at(333); // Returns 3, constexpr works
    
        return 0;
    }
    

    它利用循环展开来实现int -> ExampleEnum映射中的切换方法性能。
    ExampleEnum -> int映射的程序集相当晦涩,因为优化程序利用了枚举值被排序的事实,并且比if-else实现更喜欢跳转表。

    无论如何,该接口(interface)不需要重复,只需创建带有两个数组的constexpr对象即可。同一类型可以有多个映射。另外,enum类型是模板化的。

    而且,可以轻松扩展以支持两个enum class而不是仅enum-int

    我还创建了原始开关实现片段以进行程序集比较:
    https://godbolt.org/z/CbEcnZ

    PS。我相信可以使用适当的模板推导指南来简化constexpr Mapping<ExampleEnum, 3> mapping语法,但我还没有找到如何做的方法。

    PPS。我将N设置为15,循环展开仍在进行:https://godbolt.org/z/-Cpmgm

    关于c++ - C++ 17中的双向静态值映射,我们在Stack Overflow上找到一个类似的问题:https://stackoverflow.com/questions/59147517/

    10-11 23:04
    查看更多