定义具有可变数量和类型的数据成员的类或结构通常很有用,这些数据成员在编译时定义。典型示例是 std::tuple,但有时需要定义自己的自定义结构。这是一个使用复合定义结构的示例(而不是像 std::tuple 那样的继承。
从一般(空)定义开始,它也作为后续专业化中递归终止的基本情况:
template<typename ... T>
struct tuple {};
这已经允许我们定义一个空结构,tuple<> 数据,尽管这还不是很有用。
接下来是递归案例特化:
template<typename T, typename ... Rest>
struct tuple<T, Rest ...>
{
tuple(const T& first, const Rest& ... rest)
: first(first)
, rest(rest...)
{}
T first;
tuple<Rest ... > rest;
};
这足以让我们创建任意数据结构,例如 tuple data(1, 2.1, "hello")。
那么发生了什么?首先,请注意这是一种专业化,它的要求是至少存在一个可变参数模板参数(即上面的T),而不关心包Rest 的具体构成。知道 T 存在允许定义其数据成员 first。其余数据递归打包为tuple<Rest ... >rest。构造函数初始化这两个成员,包括对其余成员的递归构造函数调用。
您可以将其可视化如下:
tuple <int, float>
-> int first
-> tuple <float> rest
-> float first
-> tuple <> rest
-> (empty)
继续帮助类。这次我们需要一个空的前向声明和两个专业化。首先声明:
template<size_t idx, typename T>
struct helper;
现在是基本情况(idx==0 时)。在这种情况下,我们只返回第一个成员:
template<typename T, typename ... Rest>
struct helper<0, tuple<T, Rest ... >>
{
static T get(tuple<T, Rest...>& data)
{
return data.first;
}
};
在递归的情况下,我们递减 idx 并为其余成员调用助手:
template<size_t idx, typename T, typename ... Rest>
struct helper<idx, tuple<T, Rest ... >>
{
static auto get(tuple<T, Rest...>& data)
{
return helper<idx-1, tuple<Rest ...>>::get(data.rest);
}
};
通过一个示例,假设我们有tuple<int, float> 数据并且我们需要data.get<1>()。这会调用helper<1, tuple<int, float>>::get(data)(第二个特化),然后又调用helper<0, tuple>::get(data.rest),最后返回(通过第一个特化,因为现在 idx 为 0)data.rest.first。
原来如此!这是整个功能代码,在主函数中使用了一些示例:
完整代码
#include <type_traits>
#include <iostream>
using namespace std;
namespace my {
template <typename ...Ts>
struct tuple {};
template <typename T, typename ...Ts>
struct tuple <T, Ts...> {
tuple(T first, Ts... rest) :
first(first), rest(rest...){}
T first;
tuple<Ts...> rest;
};
namespace detail {
template <int N, typename ...Ts>
struct helper;
template <typename T, typename ...Ts>
struct helper <0, tuple<T, Ts...>> {
static auto get(tuple<T, Ts...> ds){
return ds.first;
}
};
template <int N, typename T, typename ...Ts>
struct helper <N, tuple<T, Ts...>> {
static auto get(tuple<T, Ts...> ds){
return helper<N-1, tuple<Ts...>>::get(ds.rest);
}
};
}
template <int N, typename ...Ts>
auto get(tuple<Ts...> ds){
return detail::helper<N, decltype(ds)>::get(ds);
}
}
int main(){
my::tuple <int, bool, float> test = {5, false, 10.5};
std::cout << my::get<0>(test) << endl;
std::cout << my::get<1>(test) << endl;
std::cout << my::get<2>(test) << endl;
}
Reference