这可以用c++中的模板来完成吗?
Can this be accomplished with templates in C++?
这是一个非常简单的类,表示三行双精度值,每行附加一些字符串信息:
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
s到行。例如(继续第2点中的示例),我可能希望将string
附加到第一行,但将自定义rss_feed
附加到第二行。
如果模板允许这样做(实际上它们不允许),我想输入像这样的东西来获得我的类型:
Rows<{double,int}, {string,rss_feed}>
从中确定的行数,或者如果我真的必须这样做:
Rows<{double,int}, {string,rss_feed}, 2>
但这是不可能的
可以做些什么,如果可以,我该如何使用这个类模板?我该如何得到矢量和信息对象并处理它们呢?
使用一些模板魔法,你的问题可以通过可变模板和模板递归来解决。
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!!
现在索引规则不是很好处理,但我认为这已经让大家明白了。
这可以很容易地使用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;
}
如果你知道编译时的(有限的)类型,我会使用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];
};
这并没有给你完全固定类型每行你似乎暗示,但它应该解决你的一般问题。
- 没有找到相关文章