我有一组非常不同的类型,我希望将实例存储在一个集合中,特别是一个地图。为此,我使用了类型擦除习语,即。我有一个非模板化的基类,模板化的特定于类的类继承该基类:
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不会起作用,因为基类需要模板化。
从概念上讲,我需要一个虚函数,它接受一个存储结果的类的实例。但是,由于我的类型根本不同,所以这个类需要模板化,这对于虚拟不起作用。
无论如何,我甚至不确定这在逻辑上是否可行,但如果有办法,我会很高兴。
答案 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。或者枚举算法,就像你复制一样。您甚至可以将它混合使用,就像存储各种类型的各种类型擦除子算法的变体一样。
这不支持任何类型多态的任何算法;必须枚举其中一个,以便在编译时解决问题而不进行运行时检查。