C++ 仅使用静态多态性的异构容器
我的目标是实现一个同时接受多种不同类型对象的容器(这里是一组堆栈,每种类型一个)。在运行时,使用void指针(或所有存储类型的公共基类)和运行时类型标识(RTTI),这样做很简单。由于容器将要保存的所有类型在编译时都是已知的,所以使用模板生成此类类可能(也可能不)是可能的。我知道C++ 仅使用静态多态性的异构容器,c++,templates,c++11,metaprogramming,containers,C++,Templates,C++11,Metaprogramming,Containers,我的目标是实现一个同时接受多种不同类型对象的容器(这里是一组堆栈,每种类型一个)。在运行时,使用void指针(或所有存储类型的公共基类)和运行时类型标识(RTTI),这样做很简单。由于容器将要保存的所有类型在编译时都是已知的,所以使用模板生成此类类可能(也可能不)是可能的。我知道boost::variant已经提供了类似的功能,但它要求存储的类型作为模板参数列出,如boost::variantv 我真正想要的是一个类,它在每次创建一个新的模板专门化(相当于push())时,都会透明地向自身添加一
boost::variant
已经提供了类似的功能,但它要求存储的类型作为模板参数列出,如boost::variantv代码>
我真正想要的是一个类,它在每次创建一个新的模板专门化(相当于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;
}
intmain()
{
多类型栈foo;
//向容器中添加一个double(在本例中为堆栈)
//..创建一个匹配的std::stack,并将该值推到顶部。
foo.push(0.1);
//将int添加到容器中。在这种情况下,将推导参数类型。
//…该类将创建一个std::stack,并将该值推到顶部。
foo.push(123);
//将第二个double推送到内部std::stack。
foo.push(3.14159);
std::cout我有一个实现,它与您所要求的略有不同,但可能适合您。我制作了一个类似列表的结构,当您尝试向其中添加一个新类型的元素时,它可以复制或将自身移动到一个可以包含该新元素类型的封装容器(不同类型)中。(就像复制案例中的持久数据结构)
这是代码。它非常难看,我不打算发布它,但在写这篇文章的时候没有人回答,所以我只能希望有人能帮助它变得更好
//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(); }
};
主要的缺点是,如果保存中间状态(即保存到变量中),则效率会很低但是,如果您将操作链接在一起,如示例中的b
,则可以避免该操作。创建一个std::unordered\u map
。您键入的访问代码采用该类型并找到适当的条目。然后将unknown
转换为依赖于保存堆栈的T
的类型
确保unknown
是stack\u holder
的基础,并且unknown
有一个virtual
析构函数
这可能不是你想要的,但是C++类型的系统是纯的:以后的表达式不能改变“早期”类型。
如果将类型链接起来,则可以构造更复杂的类型,但这只是列出类型,同时隐藏它们
如果对象是单例对象,一些使用静态
局部变量的黑客可能会奏效。尝试使用静态多态性来实现您描述的类型的异构容器的问题是,虽然“容器将要保存的所有类型在编译时都是已知的,”这些信息直到编译过程的后期才可用。事实上,多亏了C++的编译转换单元模型,您只能真正依赖于链接时可用的类型信息,而这只需要发出虚拟调度
实际上,我要说的是,在不直接调用的情况下实现大部分功能的最佳方法是使用Sean Parent在
。它确实在内部依赖于全面的基于继承的动态类型,但它将其全部隐藏起来,并允许通过少量工作根据类型对元素进行分层。扩展@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;
}
所以不幸的是,我们使用了动态类型,没有我们想要的模板参数推断(您可以使用推断推送,但我怀疑它会容易出现细微的程序员错误;最好是显式的),但我们得到了期望的行为:一个不枚举类型的多类型堆栈,让编译器替我们确定它们
编辑:我应该指出,与静态类型的实现相比,这种方法有一个潜在的巨大好处(如果这样做是可能的话):使用纯静态实现,每个类型为MultiStack
的对象对于使用的每种类型都会有一个堆栈;例如,如果在一个函数中的MultiStack
中使用std::string
,则生活在另一个函数中的MultiStack
也会有一个std::string
堆栈,反之亦然任何给定的多堆栈对象只分配其使用的类型的堆栈。 < P>在C++中有两个演示,描述了如何在静态语言中实现动态容器,如:C++:
1) 静态类型语言中的动态、递归、异构类型:以及
<P>2)动态C+++<
这两个用户也在动态C++文章上为AcCu:
联机文章合作。
<> P>有很多关于如何构建静态C++语言的动态构造的信息。你不能根据任何一个随机客户端稍后如何使用类型来定义一个类型。没有某种动态代码,你就不能建立一个无界的数据结构。我相信你的用例可能是类似的。至少我需要这样来定义某些闪存数据布局(并且成功地使用了这个习惯用法).boost::any
是新的void*
。我想您也可以直接从MultiStackImpl
对象中提取对std::stack
的引用,而不是将其隐藏在重复的接口后面。事实上,您可能会替换大部分实现
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
}
#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