简体   繁体   English

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

[英]Type erasure: Retrieving value - type check at compile time

I have a limited set of very different types, from which I want to store instances in a single collection, specifically a map. 我有一组非常不同的类型,我希望将实例存储在一个集合中,特别是一个映射。 To this end, I use the type erasure idiom, ie. 为此,我使用了类型擦除习语,即。 I have a non-templated base class from which the templated, type specific class inherits: 我有一个非模板化的基类,模板化的,类型特定的类继承自:

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 { ... }
}

I then store unique_ptrs to concept in my map. 然后我将unique_ptrs存储到我的地图中的概念中。 To retrieve the value, I have a templated function which does a dynamic cast to the specified type. 为了检索该值,我有一个模板化的函数,它对指定的类型进行动态转换。

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;
}

What I don't like about this solution is, that specifying a wrong T is only detected at runtime. 我不喜欢这个解决方案的是,只在运行时检测到指定错误的T. I'd really really like this to be done at compile time. 我真的很想在编译时完成这项工作。

My options are as I see the following, but I don't think they can help here: 我的选择是如下所示,但我不认为他们可以在这里提供帮助:

  • Using ad hoc polymorphism by specifying free functions with each type as an overload, or a template function, but I do not know where to store the result. 通过将每个类型的自由函数指定为重载或模板函数来使用ad hoc多态,但我不知道在哪里存储结果。

    • Using CRTP won't work, because then the base class would need to be templated. 使用CRTP将无法正常工作,因为基类需要进行模板化。

    • Conceptually I would need a virtual function which takes an instance of a class where the result will be stored. 从概念上讲,我需要一个虚函数,它接受一个存储结果的类的实例。 However since my types are fundamentally different, this class would need to be templated, which does not work with virtual. 但是,由于我的类型根本不同,因此需要对此类进行模板化,这对于虚拟不起作用。

Anyways, I'm not even sure if this is logically possible, but I would be very glad if there was a way to do this. 无论如何,我甚至不确定这是否在逻辑上可行,但如果有办法做到这一点我会很高兴。

For a limited set of types, your best option is variant . 对于有限的一组类型,您最好的选择是variant You can operate on a variant most easily by specifying what action you would take for every single variant, and then it can operate on a variant correctly. 您可以通过指定对每个变体采取的操作来最轻松地操作变体,然后它可以正确地对变体进行操作。 Something along these lines: 这些方面的东西:

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 is c++17 but is often available in the experimental namespace beforehand, you can also use the version from boost. std::variant是c ++ 17,但通常可以在实验命名空间中使用,你也可以使用boost中的版本。 See here for the definition of overloaded: http://en.cppreference.com/w/cpp/utility/variant/visit (just a small utility the standard library unfortunately doesn't provide). 请参阅此处了解重载的定义: http//en.cppreference.com/w/cpp/utility/variant/visit (遗憾的是,标准库只提供了一个小实用程序)。

Of course, if you are expecting that a certain key maps to a particular type, and want to throw an error if it doesn't, well, there is no way to handle that at compile time still. 当然,如果你期望某个键映射到某个特定类型,并且如果它没有,则想要抛出错误,那么,仍然没有办法在编译时处理它。 But this does let you write visitors that do the thing you want for each type in the variant, similar to a virtual in a sense but without needing to actually have a common interface or base class. 但是这确实可以让你为变体中的每种类型编写访问者,类似于某种意义上的虚拟,但不需要实际拥有公共接口或基类。

You cannot do compile-time type checking for an erased type. 您不能对已擦除类型执行编译时类型检查。 That goes against the whole point of type erasure in the first place. 这首先违背了类型擦除的全部要点。

However, you can get an equivalent level of safety by providing an invariant guarantee that the erased type will match the expected type. 但是,通过提供擦除类型与预期类型匹配的不变保证,您可以获得相同的安全级别。

Obviously, wether that's feasible or not depends on your design at a higher level. 显然,这是否可行取决于您在更高层次上的设计。

Here's an example: 这是一个例子:

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;
};

The strong encapsulation here provides a level of safety almost equivalent to a compile-time check, since map insertions are aggressively protected to maintain the invariant. 这里强大的封装提供了几乎等同于编译时检查的安全级别,因为地图插入受到积极保护以维持不变量。

Again, this might not work with your design, but it's a way of handling that situation. 同样,这可能不适用于您的设计,但它是处理这种情况的一种方式。

Your runtime check occurs at the point where you exit type erasure. 您的运行时检查发生在退出类型擦除的位置。

If you want to compile time check the operation, move it within the type erased boundaries, or export enough information to type erase later. 如果要编译时间检查操作,请在类型擦除边界内移动它,或导出足够的信息以便稍后键入erase。

So enumerate the types, like std variant. 所以枚举类型,比如std variant。 Or enumerate the algorithms, like you did copy. 或者枚举算法,就像你复制一样。 You can even mix it, like a variant of various type erased sub-algorithms for the various kinds of type stored. 您甚至可以将它混合使用,就像存储各种类型的各种类型的擦除子算法的变体一样。

This does not support any algorithm on any type polymorphism; 这不支持任何类型多态的任何算法; one of the two must be enumerated for things to resolve at compile time and not have a runtime check. 必须枚举两者中的一个,以便在编译时解决问题而不进行运行时检查。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM