如何使用C ++ 11可变参数模板来定义由元组元组支持的元组向量?

时间:2013-12-20 10:23:02

标签: c++ templates c++11 vector variadic-templates

假设我有一堆载体:

vector<int> v1;
vector<double> v2;
vector<int> v3;

所有相同的长度。现在,对于每个索引i,我希望能够将(v1 [i],v2 [i],v3 [i])视为元组,并且可以传递它。事实上,我希望有一个元组向量而不是向量元组,使用它我可以做上面的事情。 (用C语言,我可能会说结构数组而不是数组结构)。我不想影响任何数据重新排序(想想:真的很长的向量),即新的向量由我传入的各个向量支持。让我们。

现在,我希望我编写的类(称为ToVBackedVoT缺少一个更好的名称)来支持任意选择向量来支持它(不仅仅是3,不是int,double和int,不是每个只是标量)。我希望元组的向量是可变的,并且不需要在构造/赋值上进行复制。

如果我理解正确,可变参数模板和C ++ 11中的新std::tuple类型就是这样做的假设(假设我不想要无类型的void*数组等)。但是,我几乎不了解它们,也从未与它们合作过。你能帮我勾勒出这样一堂课的样子吗?或者如何,给定

template <typename ... Ts>

我可以表达类似“模板参数列表是用原始模板参数中的每个类型名称替换此类型元素的向量”吗?

注意:我想我可能也想稍后能够将其他向量连接到支持向量,将ToVBackedVoT<int, double, int>的实例变成{{1}的实例}}。所以,在回答时请记住这一点。但这并不重要。

6 个答案:

答案 0 :(得分:12)

一个想法是如果只有一部分字段用于特定任务,则以矢量形式将存储保持在“数组结构”样式中以获得良好性能。然后,对于需要不同字段集的每种任务,您可以围绕这些向量的部分编写一个轻量级包装器,为您提供一个类似于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被称为每个元素。我用它们来证明替代方案;你也可以只使用第二种方法。

  1. 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*的其他内容:

  2. getElements:此版本使用的索引列表(https://stackoverflow.com/a/15036110/592323)也会被扩展,然后您可以将std::get与索引列表一起使用以扩展完整表达式。调用函数时IndexList实例化一个适当的索引列表,只有模板类型推导才能获得Is...。类型可以在包装类中定义:

    // list of indices
    typedef decltype(index_range<0, sizeof...(Ts)>()) IndexList;
    
  3. 可以在此处找到包含一些示例的更完整代码:http://ideone.com/O3CPTq

    未解决的问题

    • 如果向量具有不同的大小,则代码将失败。更好的方法是检查所有“结束”迭代器是否相等;如果一个迭代器“结束”,我们也“结束”;但这需要一些超过operator==operator!=的逻辑,除非可以“伪造”它;这意味着只要任何运算符不相等,operator!=就会返回false。

    • 解决方案不是正确的,例如没有const_iterator

    • 无法添加,插入等。包装器类可以添加一些insert或/或push_back函数,以使其与std::vector类似。如果您的目标是在语法上与元组向量兼容,请从std::vector重新实现所有相关函数。

    • 测试不够;)

答案 1 :(得分:6)

所有可变参数模板杂耍的替代方法是使用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...
  }
};

正如你所看到的,扩展它以支持任意一组向量是非常简单的。

答案 2 :(得分:2)

您可以使用以下内容:

#if 1 // Not available in C++11, so write our own

// class used to be able to use std::get<Is>(tuple)...
template<int... Is>
struct index_sequence { };

// generator of index_sequence<Is>
template<int N, int... Is>
struct make_index_sequence : make_index_sequence<N - 1, N - 1, Is...> { };

template<int... Is>
struct make_index_sequence<0, Is...> : index_sequence<Is...> { };

#endif

// The 'converting' class
// Note that it doesn't check that vector size are equal...
template<typename ...Ts>
class ToVBackedVoT
{
public:
    explicit ToVBackedVoT(std::vector<Ts>&... vectors) : data(vectors...) {}

    std::tuple<const Ts&...> operator [] (unsigned int index) const
    {
        return at(index, make_index_sequence<sizeof...(Ts)>());
    }
    std::tuple<Ts&...> operator [] (unsigned int index)
    {
        return at(index, make_index_sequence<sizeof...(Ts)>());
    }

private:
    template <int... Is>
    std::tuple<const Ts&...> at(unsigned int index, index_sequence<Is...>) const
    {
        return std::tie(std::get<Is>(data)[index]...);
    }

    template <int... Is>
    std::tuple<Ts&...> at(unsigned int index, index_sequence<Is...>)
    {
        return std::tie(std::get<Is>(data)[index]...);
    }

private:
    std::tuple<std::vector<Ts>&...> data;
};

要迭代,请创建一个'{3}}

中的'IndexIterator'

要连接其他向量,您必须为ToVBackedVoT

创建另一个std::tuple_cat作为std::tuple

答案 3 :(得分:2)

从提问者关于如何使用它的说明(采用元组的代码),我将提出这个建议。

//give the i'th element of each vector
template<typename... Ts>
inline tuple<Ts&...> ith(size_t i, vector<Ts>&... vs){
    return std::tie(vs[i]...);
}

有一项建议允许将参数包保存为类的成员(N3728)。使用它,这里有一些未经测试和不可测试的代码。

template<typename... Types>
class View{
private:
    vector<Types>&... inner;

public:

    typedef tuple<Types&...> reference;

    View(vector<Types>&... t): inner(t...) {}

    //return smallest size
    size_t size() const{
        //not sure if ... works with initializer lists
        return min({inner.size()...});
    }

    reference operator[](size_t i){
        return std::tie(inner[i]...);
    }
};

迭代:

public:
    iterator begin(){
        return iterator(inner.begin()...);
    }
    iterator end(){
        return iterator(inner.end()...);
    }

    //for .begin() and .end(), so that ranged-based for can be used
    class iterator{
        vector<Types>::iterator... ps;
        iterator(vector<Types>::iterator... its):ps(its){}
        friend View;

    public:

        //pre:
        iterator operator++(){
            //not sure if this is allowed.
            ++ps...;
            //use this if not:
            //  template<typename...Types> void dummy(Types... args){} //global
            //  dummy(++ps...);
            return *this;
        }
        iterator& operator--();
        //post:
        iterator operator++(int);
        iterator operator--(int);
        //dereference:
        reference operator*()const{
            return std::tie(*ps...);
        }
        //random access:
        iterator operator+(size_t i) const;
        iterator operator-(size_t i) const;
        //need to be able to check end
        bool operator==(iterator other) const{
            return std::make_tuple(ps...) == std::make_tuple(other.ps...);
        }
        bool operator!=(iterator other) const{
            return std::make_tuple(ps...) != std::make_tuple(other.ps...);
        }

    };

答案 4 :(得分:1)

转换为std :: tuple of vectors(vector :: iterators):

#include <iostream>
#include <vector>

// identity
// ========

struct identity
{
    template <typename T>
    struct apply {
        typedef T type;
    };
};

// concat_operation
// ================

template <typename Operator, typename ...> struct concat_operation;

template <
    typename Operator,
    typename ...Types,
    typename T>
struct concat_operation<Operator, std::tuple<Types...>, T>
{
    private:
    typedef typename Operator::template apply<T>::type concat_type;
    public:
    typedef std::tuple<Types..., concat_type> type;
};

template <
    typename Operator,
    typename ...Types,
    typename T,
    typename ...U>
struct concat_operation<Operator, std::tuple<Types...>, T, U...>
{
    private:
    typedef typename Operator::template apply<T>::type concat_type;
    public:
    typedef typename concat_operation<
        Operator,
        std::tuple<Types..., concat_type>,
        U...>
    ::type type;
};

template <
    typename Operator,
    typename T,
    typename ...U>
struct concat_operation<Operator, T, U...>
{
    private:
    typedef typename Operator::template apply<T>::type concat_type;
    public:
    typedef typename concat_operation<
        Operator,
        std::tuple<concat_type>,
        U...>
    ::type type;
};


// ToVectors (ToVBackedVoT)
// =========

template <typename ...T>
struct ToVectors
{
    private:
    struct to_vector {
        template <typename V>
        struct apply {
            typedef typename std::vector<V> type;
        };
    };

    public:
    typedef typename concat_operation<to_vector, T...>::type type;
};

// ToIterators
// ===========

template <typename ...T>
struct ToIterators;

template <typename ...T>
struct ToIterators<std::tuple<T...>>
{
    private:
    struct to_iterator {
        template <typename V>
        struct apply {
            typedef typename V::iterator type;
        };
    };

    public:
    typedef typename concat_operation<to_iterator, T...>::type type;
};


int main() {
    typedef ToVectors<int, double, float>::type Vectors;
    typedef ToVectors<Vectors, int, char, bool>::type MoreVectors;
    typedef ToIterators<Vectors>::type Iterators;

    // LOG_TYPE(Vectors);
    // std::tuple<
    //     std::vector<int, std::allocator<int> >,
    //     std::vector<double, std::allocator<double> >,
    //     std::vector<float, std::allocator<float> > >

    // LOG_TYPE(Iterators);
    // std::tuple<
    //     __gnu_cxx::__normal_iterator<int*, std::vector<int, std::allocator<int> > >,
    //     __gnu_cxx::__normal_iterator<double*, std::vector<double, std::allocator<double> > >,
    //     __gnu_cxx::__normal_iterator<float*, std::vector<float, std::allocator<float> > > >
}

答案 5 :(得分:1)

作为类似于boost::zip_iterator的替代方案,我用一个非常简单的界面编写了一个zip函数:

vector<int> v1;
vector<double> v2;
vector<int> v3;

auto vec_of_tuples = zip(v1, v2, v3);

例如,迭代这些元组:

for (auto tuple : zip(v1, v2, v3)) {
    int x1; double x2; int x3;
    std::tie(x1, x2, x3) = tuple;
    //...
}

这里,zip()可以使用任何类型的任意数量的范围。它返回一个适配器,它可以被视为一个延迟评估的范围,而不是来自包装范围的元素元组。

适配器是我的Haskell-style functional library "fn"的一部分,并使用可变参数模板实现。

目前它不支持通过适配器对原始范围值的修改,因为库的设计(它旨在与函数式编程中的非可变范围一起使用)。

关于如何完成此操作的简要说明zip(...)返回一个实现begin()end()的适配器对象,返回一个迭代器对象。迭代器为包装范围保存一个迭代器元组。增加迭代器会增加所有包装的迭代器(使用索引列表实现并将递增表达式解压缩为一系列表达式:++std::get<I>(iterators)...)。取消引用迭代器将减少所有包装的迭代器并将其传递给std::make_tuple(这也实现为解压缩表达式*std::get<I>(iterators)...)。

P.S。它的实现基于来自这个问题的答案的许多想法。