【问题标题】:C++ runtime type switching with multiple enums具有多个枚举的 C++ 运行时类型切换
【发布时间】:2020-02-18 08:34:25
【问题描述】:

这是一个魔法开关问题,解决方案描述为here。但是,我想知道是否有可能使它成为 3 维的,即让它基于三个给定的枚举进行切换。在理想情况下,n 维当然是首选。我首先尝试将其设为 2 维,如下所示,但我遇到了双可变参数解包,我不知道它是如何工作的。

template<class Enum, class Enum2, template<Enum, Enum2>class Z>
struct magic_switch {
  // return value of a call to magic_switch(Args...)
  template<class...Args>
  using R = std::result_of_t<Z<Enum(0), Enum2(0)>(Args...)>;
  // A function pointer for a jump table:
  template<class...Args>
  using F = R<Args...>(*)(Args&&...);
  // Produces a single function pointer for index I and args Args...
  template<size_t I, size_t I2, class...Args>
  F<Args...> f() const {
    using ret = R<Args...>;
    return +[](Args&&...args)->ret{
      using Invoke=Z<Enum(I), Enum(I2)>;
      return Invoke{}(std::forward<Args>(args)...);
    };
  }
  // builds a jump table:
  template<class...Args, size_t...Is, size_t ...Is2>
  std::array<F<Args...>,size_t(Enum::COUNT)>
  table( std::index_sequence<Is...>, std::index_sequence<Is2...> ) const {
    return {{
      f<Is, Is2, Args...>()... ... // << -- 2d expansion not working
    }};
  }
  template<class...Args>
  R<Args...> operator()(Enum n, Enum2 n2, Args&&...args) {
    // a static jump table for this case of Args...:
    static auto jump=table<Args...>(std::make_index_sequence<size_t(Enum::COUNT)>{}, std::make_index_sequence<size_t(Enum2::COUNT)>{});
    // Look up the nth entry in the jump table, and invoke it:
    return jump[size_t(n) + size_t(Enum::COUNT) * size_t(n2)](std::forward<Args>(args)...);
  }
};

调用这段代码如下

enum class abc_enum { a, b, c, COUNT };
enum class defg_enum { d, e, f, g, COUNT };

template<abc_enum e, defg_enum f>
struct stuff {
  void operator()() const {
    std::cout << (int)e << '\n';
    std::cout << (int)f << '\n';
  }
};
magic_switch<abc_enum, defg_enum, stuff>{}(abc_enum::b, defg_enum::f);

但是,由于双可变参数包扩展,上面无法编译。对于更多维度,甚至更多的可变参数包将不得不被解包。 n维魔法开关问题有解决办法吗?

【问题讨论】:

    标签: c++ templates variadic-templates


    【解决方案1】:

    你可以一步一步来,

    首先将每个运行时枚举转换为可能的std::integral_constant 中的std::variant

    template <typename Enum, typename Seq> struct EnumVariantHelper;
    
    template <typename Enum, std::size_t ... Is>
    struct EnumVariantHelper<Enum, std::index_sequence<Is...>>
    {
        using type = std::variant<std::integral_constant<Enum, Enum(Is)>...>;
    };
    
    template <typename Enum, std::size_t Count>
    using EnumVariant = typename EnumVariantHelper<Enum, std::make_index_sequence<Count>>::type;
    
    template <typename Enum, std::size_t Count>
    struct AsVariant
    {
        template <Enum E>
        struct F
        {
            EnumVariant<Enum, Count> operator()() const
            {
                return std::integral_constant<Enum, E>{};
            }
        };
    };
    

    然后,让组合的东西到std::visit,你可能会这样做:

    auto v1 = magic_switch<abc_enum, abc_enum::COUNT,
                           AsVariant<abc_enum, std::size_t(abc_enum::COUNT)>::F>{}(abc_enum::b);
    auto v2 = magic_switch<defg_enum, defg_enum::COUNT,
                           AsVariant<defg_enum, std::size_t(defg_enum::COUNT)>::F>{}(defg_enum::f);
    
    std::visit([](auto e1, auto e2){ return stuff<e1(), e2()>{}(); }, v1, v2);
    

    Demo

    【讨论】:

      【解决方案2】:

      您可以将问题拆分为更小的部分,然后创建一个 N-D std::array,而不是使用所有可能的函数指针创建单个 std::array

      这是您询问的 2D 问题的一个快速而肮脏的示例:

      template<class Enum, class Enum2, template <Enum, Enum2> class Z>
      struct magic_switch {
        // return value of a call to magic_switch(Args...)
        template <class...Args>
        using R = std::result_of_t<Z<Enum(0), Enum2(0)>(Args...)>;
      
        // A function pointer for a jump table:
        template <class...Args>
        using F = R<Args...>(*)(Args&&...);
      
        // Produces a single function pointer for index I and args Args...
        template<size_t I, size_t I2, class...Args>
        F<Args...> f() const {
          using ret = R<Args...>;
          return +[](Args&&...args)->ret{
            using Invoke=Z<Enum(I), Enum2(I2)>;
            return Invoke{}(std::forward<Args>(args)...);
          };
        }
      
        // builds a jump table:
        // Table for fixed Enum value
        template <class... Args>
        using InnerArray = std::array<F<Args...>, size_t(Enum2::COUNT)>;
      
        template <size_t I, class... Args, size_t... Is2>
        InnerArray<Args...> innerTable (std::index_sequence<Is2...>) const {
          return {{ f<I, Is2, Args...>()... }}; // Expand Is2.
        }
      
        // Nested (2D) table
        template<class... Args, size_t... Is>
        std::array<InnerArray<Args...>, size_t(Enum::COUNT)> table (std::index_sequence<Is...>) const {
          return {{ innerTable<Is, Args...>(std::make_index_sequence<size_t(Enum2::COUNT)>{})... }}; // Expand Is.
        }
      
        template<class...Args>
        R<Args...> operator()(Enum n, Enum2 n2, Args&&...args) {
          // a static jump table for this case of Args...:
          static auto jump=table<Args...>(std::make_index_sequence<size_t(Enum::COUNT)>{});
          // Look up the nth entry in the jump table, and invoke it:
          return jump[size_t(n)][size_t(n2)](std::forward<Args>(args)...);
        }
      };
      

      并在您的示例中实际使用它:

      enum class abc_enum { a, b, c, COUNT };
      enum class defg_enum { d, e, f, g, COUNT };
      
      template<abc_enum e, defg_enum f>
      struct stuff {
        void operator()() const {
          std::cout << (int)e << '\n';
          std::cout << (int)f << '\n';
        }
      };
      
      int main () {
        magic_switch<abc_enum, defg_enum, stuff>{}(abc_enum::b, defg_enum::f);
      }
      

      我没有尝试让它自动处理 N 个不同的枚举。假设这是留给读者的练习:)。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2021-12-22
        • 2015-08-18
        • 1970-01-01
        相关资源
        最近更新 更多