2017-09-04 130 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