异构容器仅使用静态多态性

时间:2014-03-04 21:31:21

标签: c++ templates c++11 metaprogramming containers

我的目标是实现一个容器(这里是一组堆栈,每种类型一个),它同时接受许多不同类型的对象。在运行时,使用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_;
};

4 个答案:

答案 0 :(得分:4)

尝试使用静态多态来实现所描述类型的异构容器的问题是,虽然“所有类型的容器将在编译时都知道”,但这个信息直到很晚才能获得。编译过程。事实上,由于C ++的翻译单元编译模型,你只能真正依赖 link 时可用的类型信息,这只是尖叫虚拟调度。

实际上,我会说在不调用Greenspun's Tenth Rule of Programming的情况下完成大多数你想要的最好的方法是使用ad-hoc动态多态的方法(动态调度一个方法)类型,不要求它继承自特定的基类)Sean Parent在其中概述 his GoingNative 2013 talk。它确实依赖于基于完全继承的动态类型,但它隐藏了它,并允许根据类型对元素进行分层,只需要一点点工作。扩展@Yakk's 建议:

#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

所以不幸的是我们确实采用了动态类型,并且没有我们想要的模板参数推导(你可以进行推理推送,但我怀疑它会容易出现细微的程序员错误;更好地使其明确) ,但是我们得到了所需的行为:一个没有枚举类型的多类型堆栈,让编译器为我们确定它们。

编辑:我应该指出,这种方法比静态类型的实现有一个潜在的巨大好处(如果这样的话甚至可能):使用纯静态实现,类型{的每个对象{1}}将使用使用的每个类型的堆栈;例如,如果您在一个函数中使用MultiStack中的std::string,则生活在另一个函数中的MultiStack 也会有MultiStack堆栈,反之亦然。这样做,任何给定的std::string对象只为它使用的类型分配堆栈。

答案 1 :(得分:3)

创建std::unordered_map<std::type_index, std::unique_ptr<unknown>>。您键入的访问代码采用类型并找到适当的条目。然后static_castunknown改为依赖于保留您的筹码的T的类型。

确保unknownstack_holder<T>的基础,并且unknown有一个virtual析构函数。

这可能不是您想要的,但C ++类型系统是纯粹的:后面的表达式不能改变“早期”类型。

如果你链接了类型,你可以构造一个更复杂的类型,但这只是在隐藏它们时列出类型。

如果对象是单身,那么使用static本地人的一些hackery就可以工作。

答案 2 :(得分: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,你就可以避免它。

答案 3 :(得分:0)

C ++ Now 2013上有几个演示文稿描述了如何使用C ++等静态语言实现动态容器。他们是:

1)静态类型语言中的动态,递归,异构类型:paperpresentation

2)动态C ++ presentation

这两位人员还合作撰写了一篇关于ACCU在线文章的动态C ++文章:Dynamic C++

有很多关于如何为静态语言(如C ++)构建动态结构的信息。