我的目标是实现一个容器(这里是一组堆栈,每种类型一个),它同时接受许多不同类型的对象。在运行时,使用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_;
};
答案 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_cast
将unknown
改为依赖于保留您的筹码的T
的类型。
确保unknown
是stack_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(); }
};
以下是如何使用它并隐藏push
和push_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)静态类型语言中的动态,递归,异构类型:paper和presentation
2)动态C ++ presentation
这两位人员还合作撰写了一篇关于ACCU在线文章的动态C ++文章:Dynamic C++
有很多关于如何为静态语言(如C ++)构建动态结构的信息。