如何编写模板以基于整数/枚举转换为模板化类型

时间:2017-05-25 20:31:56

标签: c++ c++11 templates enums casting

我想简化我在我的应用程序中编写的代码,该代码处理多种数据结构类型但具有公共头。鉴于这样的事情:

enum class MyType {
  Foo = 100,
  Bar = 200,
};

struct Hdr {
  MyType type;
};

struct Foo {
  Hdr hdr;
  int x;
  int y;
  int z;
};

struct Bar {
  Hdr hdr;
  double value;
  double ratio;
};

void process(const Foo *ptr)
{
  // process Foo here
}

void process(const Bar *ptr)
{
  // process Bar here
}

extern void *getData();

int main()
{
  const void *pv = getData();
  auto pHdr = static_cast<const Hdr *>(pv);
  switch (pHdr->type) {
    case MyType::Foo: process(static_cast<const Foo *>(pv)); break;
    case MyType::Bar: process(static_cast<const Bar *>(pv)); break;
    default: throw "Unknown";
  }
  return 0;
}

理想情况下,我想用以下内容替换上面的switch语句:

process(multi_cast<pHdr->type>(pv);

我完全可以写这样的语句来让它起作用:

template<MyType::Foo>
const Foo *multi_cast(void *p)
{
  return static_cast<const Foo *>(p);
}

template<MyType::Bar>
const Bar *multi_cast(void *p)
{
  return static_cast<const Bar *>(p);
}

但是我不能写模板参数是枚举的模板(或者就此而言是int) 我只是看了这么久才能看到答案吗? 或者没有其他方法可以做到这一点?

3 个答案:

答案 0 :(得分:3)

没有其他方法可以做到。

正如评论所指出的那样,由于类型在运行时存储在头文件中,因此您必须进行某种类型的运行时查找;没有任何模板或重载解析可以帮助您,因为所有这些都是在编译时。

您可以根据需要抽象查找,但是您只能将switch语句替换为其他类型的查找,并且只有在远离简单的切换/查找表时才能降低性能。

例如,你可以从这样的事情开始并坚持下去:

#include <iostream>
#include <cassert>

enum class Type {
    FOO,
    BAR,
    NUM_
};

struct Header {
    Header(Type t)
        : type(t)
    {}

    Type type;
};

struct Foo {
    Foo(int x, int y, int z)
        : header(Type::FOO), x(x), y(y), z(z)
    {}

    Header header;
    int x;
    int y;
    int z;
};

struct Bar {
    Bar(double value, double ratio)
        : header(Type::BAR), value(value), ratio(ratio)
    {}

    Header header;
    double value;
    double ratio;
};

static inline void process(Foo*) {
    printf("processing foo...\n");
}

static inline void process(Bar*) {
    printf("processing bar...\n");
}

using ProcessFunc = void(*)(void*);
static ProcessFunc typeProcessors[(size_t)Type::NUM_] = {
    [](void* p) { process((Foo*)p); },
    [](void* p) { process((Bar*)p); },
};

static void process(void* p) {
    Type t = ((Header*)p)->type;
    assert((size_t)t < (size_t)Type::NUM_ && "Invalid Type.");

    typeProcessors[(size_t)t](p);
}

static void* get_foo()
{
    static Foo foo(0, 0, 0);
    return &foo;
}

static void* get_bar()
{
    static Bar bar(0.0, 0.0);
    return &bar;
}

int main() {
    Foo foo(0, 0, 0);
    Bar bar(0.0, 0.0);

    process(&foo);
    process(&bar);

    process(get_foo());
    process(get_bar());

    return 0;
}

然而你只是变得可爱而且很可能变慢。您也可以将开关放在process(void*)

如果您没有对数据进行序列化(可疑),主要是一次处理一种类型,并且想要一个OO解决方案(我不会),您可以返回类型继承的基类型来自并添加一个纯虚拟process函数,如下所示:

struct Type { 
    virtual void process() = 0;
    virtual ~Type() {} 
};

struct Foo : Type {
    int x = 0;
    int y = 0;
    int z = 0;

    virtual void process() override {
        printf("processing foo...\n");
    }
};

struct Bar : Type {
    double value = 0.0;
    double ratio = 0.0;

    virtual void process() override {
        printf("processing bar...\n");
    }
};

static Type* get_foo() {
    static Foo foo;
    return &foo;
}

static Type* get_bar() {
    static Bar bar;
    return &bar;
}

int main() {
    Foo foo;
    Bar bar;

    foo.process();
    bar.process();

    get_foo()->process();
    get_bar()->process();

    return 0;
}

我会坚持使用开关,但是我会将Type :: FOO和Type :: BAR的值保持为默认的0和1.如果你把这些值搞得太多,编译器可能决定将开关实现为一堆分支而不是查找表。

答案 1 :(得分:-1)

您正在使用可称为静态(=编译时)多态的内容。这需要生成此类switch语句,以便将运行时值pHrd->dtype转换为case子句中的一个编译时值句柄。像你的

process(multi_cast<pHdr->type>(pv);

是不可能的,因为在编译时不知道pHdr->type

如果你想避开switch,你可以使用普通的动态多态忘记关于enum Hdr,但是使用抽象基础类

struct Base {
  virtual void process()=0;
  virtual ~Base() {}
};

struct Foo : Base { /* ... */ };
struct Bar : Base { /* ... */ };

Base*ptr = getData();
ptr->process();

答案 2 :(得分:-1)

您有两个问题:

  1. 将运行时值(您的“类型”)转换为编译时确定的类型(具有关联行为)。
  2. 将可能的不同类型“统一”为单个(已知编译时静态)类型。
  3. 第2点是继承与virtual成员函数一起用于:

    struct Thing {
      virtual void doStuff() const = 0;
      virtual ~Thing() {}
    };
    struct AThing : Thing {
      void doStuff() const override { std::cout << "A"; }
    };
    struct BThing : Thing {
      void doStuff() const override { std::cout << "B"; }
    };
    

    通常通过创建某种“工厂”机制来解决第1点,然后根据运行时值将调度分配给其中一个工厂。首先,工厂:

    Thing * factoryA() { return new AThing(); }
    Thing * factoryB() { return new BThing(); }
    Thing * factory_failure() { throw 42; }
    

    “调度”(或“选择正确的工厂”)可以用不同的方式完成,其中一种是你的switch声明(快速,但笨拙),通过某个容器/阵列进行线性搜索(简单,慢)或通过在地图中查找(对数 - 或基于散列的地图的常量)。

    我选择了(有序)地图,但我没有使用std::map(或std::unordered_map),而是使用(排序!)std::array来避免动态内存分配:

    // Our "map" is nothing more but an array of key value pairs.
    template <
      typename Key,
      typename Value,
      std::size_t Size>
    using cmap = std::array<std::pair<Key,Value>, Size>;
    
    
    // Long type names make code hard to read.
    template <
      typename First,
      typename... Rest>
    using cmap_from =
      cmap<typename First::first_type,
           typename First::second_type,
           sizeof...(Rest) + 1u>;
    
    
    // Helper function to avoid us having to specify the size
    template <
      typename First,
      typename... Rest>
    cmap_from<First, Rest...> make_cmap(First && first,
                                        Rest && ... rest) {
      return {std::forward<First>(first), std::forward<Rest>(rest)...};
    }
    

    使用std::lower_bound我在这个有序数组(ehm“map”)上执行二进制搜索:

    // Binary search for lower bound, check for equality
    template <
      typename Key,
      typename Value,
      std::size_t Size>
    Value get_from(cmap<Key,Value,Size> const & map,
                   Key const & key,
                   Value alternative) {
      assert(std::is_sorted(std::begin(map), std::end(map),
                            [](auto const & lhs, auto const & rhs) {
                              return lhs.first < rhs.first; }));
      auto const lower = std::lower_bound(std::begin(map), std::end(map),
                                          key,
                                          [](auto const & pair, auto k) {
                                            return pair.first < k; });
      if (lower->first == key) {
        return lower->second;
      } else {
        // could also throw or whatever other failure mode
        return alternative;
      }
    }
    

    所以,最后,我可以使用static const映射来获得一个工厂给定一些运行时值“type”(或选择,因为我命名):

    int main() {
      int const choices[] = {1, 10, 100};
      static auto const map =
        make_cmap(std::make_pair(1, factoryA),
                  std::make_pair(10, factoryB));
      try {
        for (int choice : choices) {
          std::cout << "Processing choice " << choice << ": ";
          auto const factory = get_from(map, choice, factory_failure);
          Thing * thing = factory();
          thing->doStuff();
          std::cout << std::endl;
          delete thing;
        }
      } catch (int const & value) {
        std::cout << "Caught a " << value
                  << " ... wow this is evil!" << std::endl;
      }
    }
    

    (Live on ideone)

    该“地图”的初始化可能是constexpr

    当然,您应该使用托管指针(如Thing *)而不是原始指针(std::unique_ptr)。此外,如果您不希望将处理(doStuff)作为成员函数,那么只需调用一个给定函数对象的单个“调度”(virtual)成员函数,自己的实例(this)。使用CRTP基类,您不需要为每个类型实现该成员函数。

相关问题