这是一个非常简单的类,表示三行双精度数,每行附加一些字符串信息:
struct ThreeRows {
std::vector<double> row1;
std::vector<double> row2;
std::vector<double> row3;
std::string row1info;
std::string row2info;
std::string row3info;
};
我想要做的是通过以下方式概括这种类型:
行不应再固定为3,但应支持任意数量的行作为该类型的一部分。
我应该能够指定每行应该包含哪些类型。也许我希望第一行double
,第二行int
。 (将此示例设为两行类。)
最后,我应该能够将除string
之外的其他信息附加到行中。例如(继续第2点中的示例),我可能希望将string
附加到第一行,但将自定义rss_feed
附加到第二行。
如果模板允许它(他们没有),我想输入这样的东西来获取我的类型:
Rows<{double,int}, {string,rss_feed}>
从中确定的行数,或者如果我真的必须:
Rows<{double,int}, {string,rss_feed}, 2>
但这是不可能的。
可以做些什么,如果有的话,我将如何使用这个类模板?我如何实际获取我的矢量和信息对象并使用它们?
答案 0 :(得分: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&>
,其中A
和B
是可变参数模板中的对应类型Ts...
但是我们到底如何从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_type
和U_type
将是short
和short
。
现在我们可以找到可变参数模板上特定位置的类型,我们可以将它应用于错误的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!!
现在索引规则处理起来并不是很愉快,但我认为这可以解决这个问题。
答案 1 :(得分:3)
使用std::tuple
指定类型列表可以非常轻松地完成此操作。我们需要做的就是声明主模板采用两个参数,然后创建一个部分特化,其中这些类型参数是元组。在部分特化中,我们可以使用参数推导来捕获元组的模板参数,并将它们重新用于我们的目的。我们可以创建一个新模板来指定类型列表(即Types<int,double>
),但是在这种情况下元组特别好,因为你需要有办法访问各行,无论如何std::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;
}
答案 2 :(得分: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];
};
这并没有给你相当你似乎提到的每行固定类型,但它应该解决你的一般问题。