2014-03-04 77 views
6

我的目標是實現一個容器(這裏是一組堆棧,每個類型一個堆棧),它們可以同時接受許多不同類型的對象。在運行時使用void指針(或所有存儲類型的公共基類)和運行時類型標識(RTTI),這是微不足道的。由於所有類型的容器在編譯時都是已知的,它可能(或不可能)使用模板來創建這樣的類。我知道boost::variant已經提供了類似的功能,但它要求存儲的類型被列爲模板參數,如boost::variant< int, std::string > v;使用靜態多態性的異構容器

我真正想要的是每次創建等效於push()的新模板特化時,透明地爲其自身添加匹配(內部)數據結構的類。類的用法是這樣的:

int main() 
{ 
    MultiTypeStack foo; 
    //add a double to the container (in this case, a stack). The class would 
    //..create a matching std::stack<double>, and push the value to the top. 
    foo.push<double>(0.1); 
    //add an int to the container. In this case, the argument type is deduced. 
    //..The class would create a std::stack<int>, and push the value to the top. 
    foo.push(123); 
    //push a second double to the internal std::stack<double>. 
    foo.push<double>(3.14159); 
    std::cout << "int: " << foo.top<int>() << "\n";  //"int: 123" 
    std::cout << "double: " << foo.top<double>() << "\n";//"double: 3.14159" 
    return 0; 
} 

一個天真的實施爲例:

template<typename T> struct TypeIndex; 
template<> struct TypeIndex<int>{enum{i = 0};}; 
template<> struct TypeIndex<double>{enum{i = 1};}; 

class MultiTypeStack 
{ 
public: 
    template<typename T> 
    void push(const T &val){std::get<TypeIndex<T>::i>(stacks_).push(val);} 

    template<typename T> 
    void pop(){std::get<TypeIndex<T>::i>(stacks_).pop();} 

    template<typename T> 
    T top(){return std::get<TypeIndex<T>::i>(stacks_).top();} 
private: 
    std::tuple<std::stack<int>, std::stack<double>> stacks_; 
}; 
+1

您不能根據任何隨機客戶端可能*以後使用類型來定義類型。如果沒有某種動態代碼,你不能建立一個無界的數據結構。 –

+0

正如你展示你的_'nive實現',可能是[這個問答'從元組或變量模板參數創建數組初始化'](http://stackoverflow.com/questions/18251815/creating- an-array-initializer-from-a-tuple-or-variadic-template-parameters)對你很有用。我相信你的用例可能是相似的。至少我需要這樣來定義某些閃存數據佈局(併成功使用了這個習慣用法)。 –

+3

'boost :: any'是新的'void *'。 –

回答

3

創建std::unordered_map<std::type_index, std::unique_ptr<unknown>>。您輸入的訪問代碼將採用該類型並找到合適的條目。然後static_castunknown轉換爲依賴於T的類型,該類型包含您的堆棧。

確保unknownstack_holder<T>基地,並unknownvirtual析構函數。

這可能不是你想要什麼,但C++類型系統是純:後來表達式不能改變「早」的類型。

如果你鏈接了這個類型,你可以構造一個更復雜的類型,但是這只是在隱藏它們時列出了類型。

如果對象是一個單獨的一些兩輪牛車使用static當地人可以工作。

1

我是從你的要求是什麼稍有不同的實現,但也許它會爲工作您。我做了一個類似列表的結構,當你試圖向其中添加一個新類型的元素時,它可以複製或移動到一個可以包含該新元素類型的封裝容器(不同類型)中。 (就像副本中的持久數據結構一樣)。

這是代碼。這是非常醜陋的,我不會發布它,但在撰寫本文時沒有回答,所以我只能希望有人能幫助它變得更好。

//Checks if list (or element) S has element of type T 
template<class L, class T> struct HasElem : std::is_same<L,T>{}; 

template<template<class,class> class Node, class T, class NodeT, class Next> 
struct HasElem<Node<NodeT,Next>,T>{ 
    static constexpr bool value = std::is_same<NodeT,T>::value || HasElem<Next,T>::value; 
}; 
template<template<class> class Leaf, class S, class T> struct HasElem<Leaf<S>,T> : std::is_same<S,T>{}; 

//Push type transform 
template<class N, class T> struct Push{}; 
template<template<class,class> class Node, class T, class Next, class U> struct Push<Node<T,Next>,U>{ 
    typedef Node<U,Node<T,Next>> type; 
}; 

//Node type 
template<class T, class Next> 
struct Node{ 
    Node(Next&& nxt) : next(nxt){} 
    Node(const Next& nxt) : next(nxt){} 

    std::stack<T> st; 
    Next next; 

    //Pushing a new type onto the stack 
    template<class U> typename std::enable_if<!HasElem<Node,U>::value,typename Push<Node,U>::type>::type 
     push(const U& u) &&{ //disallow pushing new types on lvalues 
      return typename Push<Node,U>::type(std::move(*this)).push(u); 
     } 
    //Pushing a new type onto the stack as an lvalue and return a copy 
    template<class U> typename std::enable_if<!HasElem<Node,U>::value,typename Push<Node,U>::type>::type 
     push_new(const U& u) const{ //cannot overload on && qualifier. Make the name uglier to warn of the cost 
      return typename Push<Node,U>::type(*this).push(u); 
     } 

    //Regular old push 
    Node& push(const T& t){ st.push(t); return *this; } 
    //Push onto another node in the list 
    template<class U> typename std::enable_if<HasElem<Node,U>::value,Node>::type 
     push(const U& u){ next.push(u); return *this; } 

    template<class U> typename std::enable_if<std::is_same<T,U>::value,U>::type& 
     top(){ return st.top(); } 
    template<class U> typename std::enable_if<!std::is_same<T,U>::value && HasElem<Node,U>::value,U>::type& 
     top(){ return next.top<U>(); } 

}; 

//The last node. I made it hold data but it doesn't need to 
template<class T> struct Leaf{ 
    std::stack<T> st; 

    Leaf& push(const T& t){ st.push(t); return *this; } 
    template<class U> Node<U,Leaf> push(const U& u){ 
     return Node<U,Leaf>(std::move(*this)).push(u); 
    } 

    template<class U> void top(){} 
    T& top(){ return st.top(); } 
    void pop(){ st.pop(); } 
}; 

下面是如何使用它,躲遠pushpush_new之間的差別的例子。

template<class T, class Next, class U> auto push(Node<T,Next>&& n, const U& u) 
-> decltype(n.push(u)){ 
    return n.push(u); 
} 
template<class T, class Next, class U> auto push(const Node<T,Next>& n, const U& u) 
-> decltype(n.push_new(u)){ 
    return n.push_new(u); 
} 

int main(){ 
    auto b = Leaf<int>().push<int>(42).push<double>(3.14).push<char>('a'); 
    auto a = push(b,(char*)"Hello"); //Make a copy of b but with "Hello" 

    cout << a.top<int>() << " " << a.top<double>() << " " << 
     a.top<char>() << " " << a.top<char*>() << endl; 

    cout << b.top<char>() << endl; //The earlier version b still exists 
} 

的主要缺點是,如果在保存的中間狀態將是低效率的(即入變量),但如果在例如鏈運作在一起像b可以避開它。

4

與試圖用靜態的多態來實現你所描述的那種類型的異構容器的問題是,雖然「所有類型的容器要保持在編譯時就知道,」這個信息不可用,直到很晚編譯過程。事實上,得益於C++的編譯模型,你只能真正依賴那些在鏈接可用的類型信息,它只是尖叫虛擬調度。

實際上,我想說完成你想要什麼,而不必調用Greenspun's Tenth Rule of Programming徹底的最好方法是使用臨時動態多態性(上一類動態調度的方法,而不需要從一個繼承特定基類)由肖恩家長在 his GoingNative 2013 talk概述。它在內部完全依賴基於繼承的全面動態類型,但是它隱藏了一切,並允許根據類型對一些元素進行分層,只需要一點工作。擴大對@ Yakk的 建議:

#include <stack> 
#include <unordered_map> 
#include <typeindex> 

class MultiStack 
{ 
    class MultiStackBase 
    { 
    public: 
     virtual ~MultiStackBase() = default; 
    }; 

    template <typename T> 
    class MultiStackImpl 
     : public MultiStackBase 
    { 
     std::stack <T> _stack; 

    public: 
     virtual ~MultiStackImpl() = default; 

     template <typename U> 
     void push (U&& new_element) 
     { _stack.push (std::forward <U> (new_element)); } 

     void pop() 
     { _stack.pop(); } 

     T& top() 
     { return _stack.top(); } 

     const T& top() const 
     { return _stack.top(); } 
    }; 

    mutable std::unordered_map <std::type_index, std::unique_ptr <MultiStackBase>> stacks; 

protected: 
    template <typename T> 
    static std::type_index index() 
    { return std::type_index {typeid (T)}; } 

    template <typename T> 
    MultiStackImpl <T>& stack_cast() 
    { 
     if (stacks.count (index <T>()) == 0) 
      stacks [index <T>()] = std::make_unique <MultiStackImpl <T>>(); 
     return dynamic_cast <MultiStackImpl <T>&> (*stacks [index <T>()]); 
    } 

    template <typename T> 
    const MultiStackImpl <T>& stack_cast() const 
    { 
     if (stacks.count (index <T>()) == 0) 
      stacks [index <T>()] = std::make_unique <MultiStackImpl <T>>(); 
     return dynamic_cast <const MultiStackImpl <T>&> (*stacks [index <T>()]); 
    } 

public: 
    template <typename T, typename U> 
    void push (U&& new_element) 
    { stack_cast <T>().push (std::forward <U> (new_element)); } 

    template <typename T> 
    void pop() 
    { stack_cast <T>().pop(); } 

    template <typename T> 
    T& top() 
    { return stack_cast <T>().top(); } 

    template <typename T> 
    const T& top() const 
    { return stack_cast <T>().top(); } 
}; 


#include <iostream> 

int main() 
{ 
    MultiStack m; 

    m.push <int> (42); 
    m.push <float> (3.14); 

    std::cout << m.top <int>() << std::endl 
       << m.top <float>() << std::endl; 
} 

我們得到以下的輸出:

42 
3.14 

所以很遺憾,我們沒有采取動態類型,並且沒有模板參數推導,因爲我們想(你可能有一個推斷推動,但我懷疑它會容易出現細微的程序錯誤;更好的是使其明顯),但我們得到了期望的行爲:多類型堆棧沒有列舉類型,讓編譯器確定它們是爲了我們。

編輯:我要指出的是,這種方法有一個潛在的巨大的利益在靜態類型的實現(如果這樣的事情,甚至有可能):用純靜態的實現,MultiStack類型的每個對象有一個堆棧對於每使用類型;例如,如果在一個函數中使用std::stringMultiStack,則生活在另一個函數中的MultiStack也具有std::string堆棧,反之亦然。這樣做,任何給定的MultiStack對象只會爲其使用的類型分配堆棧。

+0

我想你也可以直接從'MultiStackImpl'對象中引用'std :: stack',而不是將它隱藏在重複接口之後。實際上,你大概可以用'boost :: any'來替換大部分的實現,但是我會像這樣對它進行說明。 –

+1

對於'stack_cast const','stacks'需要'mutable'。 – Yakk

+0

@Yakk好的。這就是我所寫的模板函數,我不使用! –

0

在C++ Now 2013中有幾個演示文稿描述瞭如何使用靜態語言(如C++)實現動態容器。他們是:

1)動態,遞歸,異構類型的靜態類型語言中:paperpresentation

2)動態C++ presentation

那兩個人還合作了一個動態C++文章在線ACCU的文章:Dynamic C++

關於如何爲靜態語言(如C++)構建動態構造有大量的信息。