类型擦除:检索值 - 在编译时检查类型

时间:2017-09-04 17:17:23

标签: c++ c++11 templates type-erasure crtp

我有一组非常不同的类型,我希望将实例存储在一个集合中,特别是一个地图。为此,我使用了类型擦除习语,即。我有一个非模板化的基类,模板化的特定于类的类继承该基类:

struct concept
{
   virtual std::unique_ptr<concept> copy() = 0; // example member function
};

template <typename T>
struct model : concept
{
   T value;
   std::unique_ptr<concept> copy() override { ... }
}

然后我将unique_ptrs存储到地图中的概念中。为了检索该值,我有一个模板化的函数,它对指定的类型进行动态转换。

template <typename T>
void get(concept& c, T& out) {
   auto model = dynamic_cast<model<T>>(&c);
   if (model == nullptr) throw "error, wrong type";
   out = model->value;
}

我不喜欢这个解决方案,指定错误的T仅在运行时检测到。我真的很喜欢这在编译时完成。

我的选择就像我看到的那样,但我认为他们不能在这里提供帮助:

  • 使用ad hoc多态通过指定自由函数,每个类型作为重载或模板函数,但我不知道在哪里存储结果。

    • 使用CRTP不会起作用,因为基类需要模板化。

    • 从概念上讲,我需要一个虚函数,它接受一个存储结果的类的实例。但是,由于我的类型根本不同,所以这个类需要模板化,这对于虚拟不起作用。

无论如何,我甚至不确定这在逻辑上是否可行,但如果有办法,我会很高兴。

3 个答案:

答案 0 :(得分:2)

对于有限的一组类型,您最好的选择是variant。您可以通过指定对每个变体采取的操作来最轻松地操作变体,然后它可以正确地对变体进行操作。这些方面的东西:

std::unordered_map<std::string, std::variant<Foo, Bar>> m;

m["a_foo"] = Foo{};
m["a_bar"] = Bar{};

for (auto& e : m) {
    std::visit(overloaded([] (Foo&) { std::cerr << "a foo\n"; }
                          [] (Bar&) { std::cerr << "a bar\n"; },
               e.second);
}

std::variant是c ++ 17,但通常可以在实验命名空间中使用,您也可以使用boost中的版本。请参阅此处了解重载的定义:http://en.cppreference.com/w/cpp/utility/variant/visit(遗传标准库仅提供一个小实用程序)。

当然,如果您希望某个键映射到某个特定类型,并且想要抛出错误,如果它没有,那么,仍然无法在编译时处理它。但这确实可以让你编写访问者,为变体中的每种类型做你想要的事情,类似于某种意义上的虚拟,但不需要实际拥有公共接口或基类。

答案 1 :(得分:1)

您无法对已擦除类型执行编译时类型检查。这首先违背了类型擦除的全部要点。

但是,通过提供擦除类型与预期类型匹配的不变保证,您可以获得相同的安全级别。

显然,这是否可行取决于您在更高层次上的设计。

以下是一个例子:

class concept {
public:
  virtual ~concept() {}
};

template<typename T>
struct model : public concept { 
  T value;
};

class Holder {
public:
  template<typename T>
  void addModel() {
    map.emplace(std::type_index(typeid(T)), std::make_unique<model<T><());
  }

  template<typename T>
  T getValue() {
    auto found = types.find(std::type_index(typeid(T)));
    if(found == types.end()) {
      throw std::runtime_error("type not found");
    }

    // no need to dynamic cast here. The invariant is covering us.
    return static_cast<model<T>*>(found->second.get())->value;
  }

private:
  // invariant: map[type] is always a model<type>
  std::map<std::type_index, std::unique_ptr<concept>> types;
};

这里强大的封装提供了几乎相当于编译时检查的安全级别,因为地图插入受到积极保护以维持不变量。

同样,这可能不适用于您的设计,但它是处理这种情况的一种方式。

答案 2 :(得分:1)

您的运行时检查发生在退出类型擦除的位置。

如果要编译时间检查操作,请在类型擦除边界内移动它,或者导出足够的信息以便稍后输入。

所以枚举类型,比如std variant。或者枚举算法,就像你复制一样。您甚至可以将它混合使用,就像存储各种类型的各种类型擦除子算法的变体一样。

这不支持任何类型多态的任何算法;必须枚举其中一个,以便在编译时解决问题而不进行运行时检查。