假设我有一堆向量:
vectorv1; vector v2; vector v3;
所有相同的长度.现在,对于每个索引i,我希望能够将(v1 [i],v2 [i],v3 [i])视为元组,并且可以传递它.事实上,我希望有一个元组向量而不是向量元组,我可以使用它来完成上述操作.(用C语言,我可能会说结构数组而不是数组结构).我不想影响任何数据重新排序(想想:真正长的向量),即新的向量由我传入的各个向量支持.让我们.
现在,我希望我编写的类(ToVBackedVoT
因缺少一个更好的名称而调用它)来支持任意选择向量来支持它(不仅仅是3,不是int,double和int,不是每个只是标量).我希望元组的向量是可变的,并且不需要在构造/赋值上进行复制.
如果我理解正确,可变参数模板和std::tuple
C++ 11中的新类型是这样做的手段(假设我不想要无类型void*
数组等).但是,我几乎不了解它们,也从未与它们合作过.你能帮我勾勒出这样一堂课的样子吗?或者如何,给定
template
我可以表达一些类似"模板参数列表是用这种类型的元素向量替换原始模板参数中的每个类型名称"吗?
注意:我想我可能也想稍后能够将其他向量连接到支持向量,从而创建一个实例ToVBackedVoT
,例如,一个实例ToVBackedVoT
.所以,在回答时请记住这一点.但这并不是至关重要的.
所有可变参数模板杂耍的替代方法是使用它boost::zip_iterator
来实现此目的.例如(未经测试):
std::vector<int> ia; std::vector<double> d; std::vector<int> ib; std::for_each( boost::make_zip_iterator( boost::make_tuple(ia.begin(), d.begin(), ib.begin()) ), boost::make_zip_iterator( boost::make_tuple(ia.end(), d.end(), ib.end()) ), handle_each() );
您的处理程序,如下所示:
struct handle_each : public std::unary_function<const boost::tuple<const int&, const double&, const int&>&, void> { void operator()(const boost::tuple<const int&, const double&, const int&>& t) const { // Now you have a tuple of the three values across the vector... } };
正如您所看到的,扩展它以支持任意一组向量非常简单.
一种想法是如果只有一部分字段用于特定任务,则以向量的形式将存储保持在"数组结构"样式中以获得良好性能.然后,对于需要不同字段集的每种任务,您可以围绕其中一些向量编写一个轻量级包装器,为您提供一个类似于std::vector
支持的随机访问迭代器接口.
关于可变参数模板的语法,这就是包装类(没有任何迭代器)的样子:
template<class ...Ts> // Element types class WrapMultiVector { // references to vectors in a TUPLE std::tuple<std::vector<Ts>&...> m_vectors; public: // references to vectors in multiple arguments WrapMultiVector(std::vector<Ts> & ...vectors) : m_vectors(vectors...) // construct tuple from multiple args. {} };
要构造这样的模板化类,通常优先使用模板类型推导辅助函数(类似于那些make_{pair|tuple|...}
函数std
):
template<class ...Ts> // Element types WrapMultiVector<Ts...> makeWrapper(std::vector<Ts> & ...vectors) { return WrapMultiVector<Ts...>(vectors...); }
您已经看到了不同类型的"解包"类型列表.
添加适合您的应用程序的迭代器(您特别要求随机访问迭代器)并不容易.一个开始可以只转发迭代器,你可以扩展到随机访问迭代器.
下面的迭代器类能够使用元素迭代器元组构造,递增和解除引用以获得元素引用元组(对于读写访问很重要).
class iterator { std::tuple<typename std::vector<Ts>::iterator...> m_elemIterators; public: iterator(std::tuple<typename std::vector<Ts>::iterator...> elemIterators) : m_elemIterators(elemIterators) {} bool operator==(const iterator &o) const { return std::get<0>(m_elemIterators) == std::get<0>(o.m_elemIterators); } bool operator!=(const iterator &o) const { return std::get<0>(m_elemIterators) != std::get<0>(o.m_elemIterators); } iterator& operator ++() { tupleIncrement(m_elemIterators); return *this; } iterator operator ++(int) { iterator old = *this; tupleIncrement(m_elemIterators); return old; } std::tuple<Ts&...> operator*() { return getElements(IndexList()); } private: template<size_t ...Is> std::tuple<Ts&...> getElements(index_list<Is...>) { return std::tie(*std::get<Is>(m_elemIterators)...); } };
出于演示目的,在此代码中有两种不同的模式,它们在元组上"迭代"以便应用某些操作或构造一个新的元组,其中每个元素都会调用一些epxression .我用它们来证明替代方案; 你也可以只使用第二种方法.
tupleIncrement
:您可以使用辅助函数,它使用元编程索引单个条目并将索引前进一个,然后调用递归函数,直到索引位于元组的末尾(然后有一个特殊的案例实现被触发使用SFINAE).该函数在类之外定义,而不是在上面; 这是它的代码:
template<std::size_t I = 0, typename ...Ts> inline typename std::enable_if<I == sizeof...(Ts), void>::type tupleIncrement(std::tuple<Ts...> &tup) { } template<std::size_t I = 0, typename ...Ts> inline typename std::enable_if<I < sizeof...(Ts), void>::type tupleIncrement(std::tuple<Ts...> &tup) { ++std::get<I>(tup); tupleIncrement<I + 1, Ts...>(tup); }
此方法不能用于分配引用元组,operator*
因为这样的元组必须立即用引用初始化,这对于这种方法是不可能的.所以我们需要其他东西operator*
:
getElements
:此版本使用索引列表(/sf/ask/17360801/)也会扩展,然后您可以使用std::get
索引列表来扩展完整表达式.在IndexList
打电话时的函数实例,其仅需要模板类型推演,以获得这些适当的索引列表Is...
.类型可以在包装类中定义:
// list of indices typedef decltype(index_range<0, sizeof...(Ts)>()) IndexList;
可以在此处找到更完整的代码和一些示例:http://ideone.com/O3CPTq
开放性问题是:
如果向量具有不同的大小,则代码将失败.更好的方法是检查所有"结束"迭代器是否相等; 如果一个迭代器"结束",我们也"结束"; 但这需要一些逻辑operator==
,operator!=
除非可以"伪造"它; 意思是operator!=
能尽快返回false 任何运营商是不相等的.
解决方案不是常量,例如没有const_iterator
.
无法追加,插入等.包装类可以添加一些insert
或和/或push_back
函数,以使其工作类似于std::vector
.如果你的目标是它在语法上与元组向量兼容,那么重新实现所有相关函数std::vector
.
没有足够的测试;)