C ++运行时类型切换(避免切换)

时间:2015-06-04 10:02:53

标签: c++ templates types switch-statement runtime

我已经使用C ++几年了,但我还没有找到解决我经常遇到的问题的方法。知道如何解决它会很棒。

目前我所拥有的是:

// Client code:
switch(currentEnumValue)
    {
    case MyEnum::kValue01:
      processData<MyEnum::kValue01>(data);
      break;
    case MyEnum::kValue02:
      processData<MyEnum::kValue02>(data);
      break;
    default:
      LOG("Invalid command");
      break;
    }

// Declarations

enum class MyEnum {kValue01, kValue02};
class MyClass
{
// code
template <MyEnum> void processData(char*); /* Implemented somewhere else */
}
  template <> void MyClass::processData<MyEnum::kValue01>(char* data); /* Implemented somewhere else */
  MyClass <> void MyClass::processData<MyEnum::kValue02>(char* data); /* Implemented somewhere else */

我想删除交换机的原因很多。而不是它,我需要像processData<runtime-decltype(currentEnumValue)>(data);

这样的东西

我知道关于typeid以及没有将编译时间和运行时混合在一起......但尽管如此,我还是希望找到一些解决方案,最好不包括宏。

3 个答案:

答案 0 :(得分:5)

这个类基于构造一些模板并使用提供的args调用它,为给定的Enum生成一个跳转表,直到某个count大小。它假设枚举值从0开始,然后转到Count-1。

template<class Enum, Enum Count, template<Enum>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)>(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, class...Args>
  F<Args...> f() const {
    using ret = R<Args...>;
    return +[](Args&&...args)->ret{
      using Invoke=Z<Enum(I)>;
      return Invoke{}(std::forward<Args>(args)...);
    };
  }
  // builds a jump table:
  template<class...Args, size_t...Is>
  std::array<F<Args...>,size_t(Count)>
  table( std::index_sequence<Is...> ) const {
    return {{
      f<Is, Args...>()...
    }};
  }
  template<class...Args>
  R<Args...> operator()(Enum n, Args&&...args) {
    // a static jump table for this case of Args...:
    static auto jump=table<Args...>(std::make_index_sequence<size_t(Count)>{});
    // Look up the nth entry in the jump table, and invoke it:
    return jump[size_t(n)](std::forward<Args>(args)...);
  }
};

然后如果你有一个枚举:

enum class abc_enum { a, b, c, count };

和一个函数对象模板:

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

你可以派遣:

magic_switch<abc_enum, abc_enum::count, stuff>{}(abc_enum::b);

在任何情况下,在模板stuff中,您将枚举值作为编译时常量。你用运行时常量来调用它。

开销应类似于switch语句或vtable调用,具体取决于编译器的优化方式。

live example

请注意,将Enum设置为std::size_t是有效的。

在C ++ 11中,您需要make_index_sequenceindex_sequence

template<size_t...>
struct index_sequence {};
namespace details {
  template<size_t Count, size_t...szs>
  struct sequence_maker : sequence_maker<Count-1, Count-1, szs...> {};
  template<size_t...szs>
  struct sequence_maker<0,szs...> {
    using type = index_sequence<szs...>;
  };
}
template<size_t Count>
using make_index_sequence=typename details::sequence_maker<Count>::type;
template<class...Ts>
using index_sequence_for=make_index_sequence<sizeof...(Ts)>;

和别名:

template<class Sig>
using result_of_t=typename std::result_of<Sig>::type;

然后在上面的代码中删除std::使用它们。

live example

答案 1 :(得分:2)

Boost变体做了类似你正在做的事情。它允许您使用基于模板的构造替换switch语句,该构造可以检查所有案例是否在编译时定义,但在运行时选择一个。

如,

using namespace boost;
using Data = variant<int, double>;

struct ProcessDataFn: static_visitor<void>
{
    char* data;
    void operator()(int& i)
    {
        // do something with data
    }

    void operator()(double& d)
    {
        // do something else
    }
};

void processData(char* data, Data& dataOut)
{
    apply_visitor(ProcessDataFn{data}, dataOut);
}

void example(char * data)
{
    Data d = 0;
    processData(data, d); // calls first overload of operator()
    Data d = 0.0;
    processData(data, d); // calls second overload
}

答案 2 :(得分:1)

为了扩展我的评论,理想情况下我们有编译时反射并且能够编写通用调度函数。在它缺席的情况下,遗憾的是,一个选项是使用宏来为您使用X宏模式执行此操作:

#define LIST_OF_CASES   \
    X_ENUM(kValue0)     \
    X_ENUM(kValue1)     \
    X_ENUM(kValue2)


enum MyEnum
{
#   define X_ENUM(a) a,
    LIST_OF_CASES
#   undef X_ENUM
};

void dispatch(MyEnum val)
{
    switch (val)
    {
#       define X_ENUM(a) case a: processData<a>(); break;
        LIST_OF_CASES
#       undef X_ENUM
    default:
        // something's really wrong here - can't miss cases using this pattern
    }
}

这种方法的一个好处是它可以扩展到大量的枚举,很难省略一个案例,并且你可以通过使用多参数X_ENUM宏来附加额外的信息。

我知道你说你想要避免使用宏,但是没有虚函数的替代方法就是拥有某种由枚举索引的函数指针的静态表,这只是伪装的虚函数(带有诚然,开销较低,但仍然承受间接函数调用的成本。)