4

这是一个非常简单的类,表示三行双精度数,每行附加一些字符串信息:

struct ThreeRows {
  std::vector<double> row1;
  std::vector<double> row2;
  std::vector<double> row3;

  std::string row1info;
  std::string row2info;
  std::string row3info;
};

我想要做的是通过以下方式概括这种类型:

  1. 行不应再固定为三,但应支持任意数量的行作为类型的一部分。

  2. 我应该能够指定每一行应该是什么类型。也许我想要double第一行和int第二行。(使这个例子成为一个两行类。)

  3. 最后,我应该能够将除strings 之外的其他信息附加到行中。例如(继续第 2 点中的示例),我可能想将 a 附加string到第一行,但将自定义附加rss_feed到第二行。

如果模板允许(他们不允许),我想输入这样的内容来获取我的类型:

Rows<{double,int}, {string,rss_feed}>

从中确定的行数,或者如果我真的必须:

Rows<{double,int}, {string,rss_feed}, 2>

但这是不可能的。

可以做些什么,如果可以,我将如何使用这个类模板?我将如何真正获得我的向量和信息对象并使用它们?

4

3 回答 3

11

使用一些模板魔法,您的问题可以通过可变参数模板和模板递归来解决。

template< size_t N, typename T, typename U, typename... Ts >
struct Rows : public Rows<N-1, Ts...> {
  vector<T> row;
  U rowinfo;
};

template<typename T, typename U>
struct Rows<1, T, U> {
  vector<T> row;
  U rowinfo;
};

Rows< 3, int, string, float, string, double, rss_feed > rows; 

=>  struct Rows {
       vector<int> v1;
       string s1;
       vector<foat> v2;
       string s2;
       vector<double> v3;
       rss_feed feed;
    }

要访问这些字段的值,您可以实现模板化get()成员函数

template< size_t N, typename T, typename U, typename... Ts >
struct Rows : public Rows<N-1, Ts...> {
  vector<T> row;
  U rowinfo;

  template< size_t M >
  typename enable_if< M == N, tuple<vector<T>&, U&> >::type
  get() {
    return make_tuple( ref(row), ref(rowinfo) );
  }   
};

template<typename T, typename U>
struct Rows<1, T, U> {
    vector<T> row;
    U rowinfo;

    template< size_t M >
    typename enable_if< M == 1, tuple<vector<T>&, U&> >::type
    get() {
        return make_tuple( ref(row), ref(rowinfo) );
    }

};

花点时间消化这些。这些get()方法返回一个包含对请求字段的引用的元组,并且只有在传入的数字与类的模板匹配时才启用它们,那么其他数字呢?好吧,我们可以像这样启用它们

template< size_t M >
typename enable_if< M != N, tuple<vector<T>&, U&> >::type
get() {
    return Rows<N-1, Ts...>::template get<M>();  // call parent's get,  
                                                 // ::template is required to avoid ambiguity

} 

但这是错误的!!!返回类型不是tuple<vector<T>&, U&>,应该是可变参数模板中对应类型的tuple<vector<A>&, B&>whereA和isBTs...

但是我们到底是怎么得到我们想要的类型呢Ts...,我们可以用下面的技术找出可变参数模板的类型。

template< size_t N, typename... Ts >
struct variadic_type;

template< typename T, typename U, typename... Ts >
struct variadic_type< 0, T, U, Ts... > {
    typedef T T_type;
    typedef U U_type;
};

template< size_t k, typename T, typename U, typename... Ts >
struct variadic_type< k, T, U, Ts... > {
    typedef typename variadic_type< k-1, Ts... >::T_type T_type;
    typedef typename variadic_type< k-1, Ts... >::U_type U_type;
};

所以variadic_type< 1, int, int, short, short, float, float >::T_typeU_typeshortshort

现在我们有一种方法可以找出可变参数模板上特定位置的类型,我们可以将其应用于我们错误的get()方法以获得正确的返回类型......

template< size_t M >
typename
    enable_if< M != N,
                tuple<
                    vector<typename variadic_type< N-M, T, U, Ts...>::T_type>&,
                    typename variadic_type< N-M, T, U, Ts...>::U_type&
                > >::type
get() {
    return Rows<N-1, Ts...>::template get<M>();
}

我们完成了,我们可以get()调用

Rows< 3, int, string, double, string, float, string > rows;

auto r3 = rows.get<3>();  //  { vector<int>& v, string& s };
auto r2 = rows.get<2>();  //  { vector<double>& v, string& s };
auto r1 = rows.get<1>();  //  { vector<float>& v, string& s };

auto r4 = rows.get<4>();  //  error:: other numbers will not compile!!

现在索引规则处理起来不是很愉快,但我认为这可以理解。

于 2013-05-03T00:45:19.140 回答
3

这可以很容易地使用 astd::tuple来指定您的类型列表。我们需要做的就是声明主模板接受两个参数,然后创建一个部分特化,其中这些类型参数是元组。在偏特化中,我们可以使用参数推导来捕获元组的模板参数并将它们重用于我们的目的。我们可以创建一个新模板来指定类型列表(即Types<int,double>),但是在这种情况下元组特别好,因为无论如何您都需要有一种访问各个行的方法,并且 astd::tuple提供了一个内置的通过std::get<i>. 将元组用于模板参数可能会使您用于std::get访问行更加明显。

这是一个完整的例子:

#include <string>
#include <tuple>
#include <vector>

// primary template
template <typename RowTuple,typename RowInfoTuple> struct Rows;

// variadic partial specialization 
template <typename... RowTypes,typename... RowInfoTypes>
struct Rows<std::tuple<RowTypes...>,std::tuple<RowInfoTypes...>>
{
  // use variadic expansion to make a tuple of vectors
  std::tuple<std::vector<RowTypes>...> rows;
  std::tuple<RowInfoTypes...> rowinfos;
};

struct rss_feed { };

int main(int,char**)
{
  Rows<
    std::tuple<double,int>,
    std::tuple<std::string,rss_feed>
  > data;

  std::get<0>(data.rows).push_back(1.5);
  std::get<1>(data.rows).push_back(2);
  std::get<0>(data.rowinfos) = "info";
  std::get<1>(data.rowinfos) = rss_feed();
  return 0;
}
于 2013-05-04T04:00:39.913 回答
2

如果您在编译时知道(有限的)类型,我会用它boost::variant来执行此操作:

typedef boost::variant<double, int> DataType;
typedef boost::variant<string, rss_feed> MetaDataType;

struct Row
{
    DataType data_;
    MetaDataType meta_data_;
};

template <int NUM_ROWS>
struct Rows
{
    Row row_data_[NUM_ROWS];
};

这并没有为您提供您似乎暗示的每行的固定类型,但它应该可以解决您的一般问题

于 2013-05-03T01:18:57.457 回答