簡體   English   中英

類型擦除:檢索值 - 在編譯時檢查類型

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

我有一組非常不同的類型,我希望將實例存儲在一個集合中,特別是一個映射。 為此,我使用了類型擦除習語,即。 我有一個非模板化的基類,模板化的,類型特定的類繼承自:

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將無法正常工作,因為基類需要進行模板化。

    • 從概念上講,我需要一個虛函數,它接受一個存儲結果的類的實例。 但是,由於我的類型根本不同,因此需要對此類進行模板化,這對於虛擬不起作用。

無論如何,我甚至不確定這是否在邏輯上可行,但如果有辦法做到這一點我會很高興。

對於有限的一組類型,您最好的選擇是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 (遺憾的是,標准庫只提供了一個小實用程序)。

當然,如果你期望某個鍵映射到某個特定類型,並且如果它沒有,則想要拋出錯誤,那么,仍然沒有辦法在編譯時處理它。 但是這確實可以讓你為變體中的每種類型編寫訪問者,類似於某種意義上的虛擬,但不需要實際擁有公共接口或基類。

您不能對已擦除類型執行編譯時類型檢查。 這首先違背了類型擦除的全部要點。

但是,通過提供擦除類型與預期類型匹配的不變保證,您可以獲得相同的安全級別。

顯然,這是否可行取決於您在更高層次上的設計。

這是一個例子:

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

這里強大的封裝提供了幾乎等同於編譯時檢查的安全級別,因為地圖插入受到積極保護以維持不變量。

同樣,這可能不適用於您的設計,但它是處理這種情況的一種方式。

您的運行時檢查發生在退出類型擦除的位置。

如果要編譯時間檢查操作,請在類型擦除邊界內移動它,或導出足夠的信息以便稍后鍵入erase。

所以枚舉類型,比如std variant。 或者枚舉算法,就像你復制一樣。 您甚至可以將它混合使用,就像存儲各種類型的各種類型的擦除子算法的變體一樣。

這不支持任何類型多態的任何算法; 必須枚舉兩者中的一個,以便在編譯時解決問題而不進行運行時檢查。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM