2017-09-04 135 views
3

我有一组非常不同的类型,我希望将实例存储在单个集合中,特别是地图。为此,我使用类型擦除成语,即。我从该模板,类型特定类继承的非模板基类:类型擦除:在编译时检索值类型检查

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只在运行时检测到。我真的很喜欢这个在编译时完成。

我的选择是因为我看到下面,但我不认为他们能在这里帮助:

  • 通过对每个类型的过载,或模板函数指定免费使用的功能特设多态性,但我不知道在哪里存储结果。

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

    • 从概念上讲,我需要一个虚拟函数,它需要一个类的实例来存储结果。然而,由于我的类型是根本不同的,这个类需要模板化,这不适用于虚拟。

不管怎么说,我甚至不知道这在逻辑上是可行的,但如果有办法做到这一点,我会很高兴。

+3

整个擦除类型是将类型分辨率移动到运行时。编译时检查没有多大意义。 – Frank

+0

另外:你应该避免抛出原始字符串,而是使用'std :: exception'。 – Frank

+0

你可能想要'variant'而不是你的基类。在这两种情况下,您都可以使用访问者。 – Jarod42

回答

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,但通常可在实验命名空间事先,你也可以使用的版本从升压。看到这里的重载定义:http://en.cppreference.com/w/cpp/utility/variant/visit(只是一个小实用程序,标准库不幸没有提供)。

当然,如果您希望某个键映射到某个特定类型,并且想要抛出一个错误(如果没有),那么在编译时仍然没有办法处理它。但是,这可以让你编写访问者为变体中的每种类型执行所需的操作,类似于某种意义上的虚拟,但不需要实际具有通用的接口或基类。

+0

这可能是我想要的。我不希望某个键映射到特定的类型,只是“创建/检索”的类型是相同的。 –

+0

体面质量的“重载”需要函数指针专门化,以便可以重载非lambda表达式。考虑到简单的重载是多么短暂,你可以将它包含在你的文章中吗? – Yakk

+0

@Yakk它被标记为11,而不是17,在11重载是更多的几行,因为你不能'使用...'。如果您认为这会更有帮助,我可以提供一个11实施的链接? –

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

强劲的封装这里提供了安全性几乎等同于一个编译时检查的水平,因为地图插入都在积极保护,以保持不变。

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

1

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

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

所以列举类型,如std变体。或者列举算法,就像你拷贝一样。你甚至可以混合它,就像存储各种类型的各种擦除子算法的变体一样。

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

+0

但我不得不枚举算法和类型,或不?例如,我可以创建一个虚拟的get成员(=枚举算法),其中每个重载都有一个不同类型(=枚举类型)作为输出参数?我认为这会奏效,但我不能让他们中的任何一个离开,或者我可以吗? –

+1

@MariusHerzog不,如果你枚举类型,你会得到一个变体。如果列举算法,则会得到类似'std :: function :: operator()'的'copy'函数的东西。 'get'是从类型擦除到具体类型的*退出*;自然是涉及退出式擦除。但是,您可以*使用值*做某些事情。如果你用类型擦除的数据做了7件不同的事情,你可以把这些事情用在类型擦除中的'get' *来完成,而不是在那里你必须进行运行时检查。 – Yakk