本文已参与「新人创作礼」活动,一起开启掘金创作之路。
可变长模板
在开始写tuple之前,我们首先要明确需要的东西,名叫可变长模板
他会将参数形成一个包
我们可以用这个特性,写出这样的代码
void print() {}
template <typename T, typename... U> void print(T t, U... u) {
std::cout << t << "\n,"[!!sizeof...(u)];
print(u...);
}
现在,我们使用这个技巧,来构建我们的tuple
tuple 的结构
我们希望实现的是一个 零开销的,抽象,可以支持我们任意多个元素堆放在一块地址空间上的一种数据结构
现在,我们的地址空间大概要求长成这个样子
| int | string | double | int |
|---|---|---|---|
| 1 | "hello" | 2.0 | 3 |
连续的,且类型不同的
当然,我们可以 “硬编码”, 每次需要什么类型,就定义出这么多个类型出来
比如
struct tuple_int_double_string_int {
int a;
double b;
std::string c;
int d;
};
struct tuple_int_string_int_double {
int a;
std::string b;
int c;
double d;
};
struct tuple_int_double_string_double {
int a;
double b;
std::string c;
double d;
};
为什么不用更C++的方式一点呢?
首先,我们来写一个基于模板构建了极简的模板分发类
template <typename... Arg> class tuple;
template <> class tuple<> {};
template <typename Head, typename... Tail>
class tuple<Head, Tail...> : tuple<Tail...> {
public:
Head value_;
using Next = tuple<Tail...>;
public:
tuple() {}
tuple(Head value, Tail... tail) : value_(value), Next(tail...) {}
// protected:
Next next() { return *this; }
};
? 这里为什么要用继承呢 ? 为了保证零开销的同时,内存要连续存放,我们可以通过抽象,让其并不关心内部的具体存放
现在,如何使用这个类呢?
int main() {
tuple<int, double, std::string> t(1, 2.3, "haha");
std::cout << t.value_ << " " << t.next().value_ << " "
<< t.next().next().value_ << " \n";
return 0;
}
利用next 找到下一个位置的地方,然后我们就可以链式调用找到每一个值了 !
好麻烦啊,能不能指定下标获取第几个位置的值啊!
欸,可以,先粗暴一点,把next包裹起来就可以实现一个简单的 get
template <int N> struct get;
template <> struct get<0> {
template <typename T> auto operator()(T &y) -> decltype(y.value_) {
return y.value_;
}
};
template <int N> struct get {
template <typename T>
auto operator()(T &y) -> decltype(get<N - 1>()(y.next())) {
return get<N - 1>()(y.next());
}
};
std::cout << get<0>()(t) << " ";
std::cout << get<1>()(t) << " ";
std::cout << get<2>()(t) << " ";
可以看出上面的效率还是蛮低效的
我们的想办法再找一种写法来写 get
重写get
注意到一个关键的问题
我们是一个子类继承父类的结构
意思是我们的对象结构类似于这样子
<>
<int>
<int, double>
<int, double, string>
<int, double, string, int>
这样有什么好处呢?
向上转型,就可以得到父类地址存放的值
我们所有的值是倒序存放在内存中的,所以,子类转向父类,父类就可以访问到自己的成员函数了!
举个例子:
tuple<int, double, string, int> tup;
的父类是 tuple<double, string, int>;
我们只需要把子类指针转为父类指针,就可以实现安全的访问父亲的内存
现在我们需要考虑的是 如何把一个tuple的第 i 项的 父类类型给萃取出来
这里我们用一个类型萃取的小技巧就可以萃取出来
template <size_t idx, typename... Arg> class get_helper;
template <size_t idx, typename Head, typename... Tail>
struct get_helper<idx, tuple<Head, Tail...>> {
using value_type = typename get_helper<idx - 1, tuple<Tail...>>::value_type;
using next_type = typename get_helper<idx - 1, tuple<Tail...>>::next_type;
};
template <typename Head, typename... Tail>
struct get_helper<0, tuple<Head, Tail...>> {
using value_type = Head;
using next_type = tuple<Head, Tail...>;
};
template <> struct get_helper<0, tuple<>> {
using value_type = void;
using next_type = tuple<>;
};
我们递归的去寻找当前 idx的 value_type 和 next_type
next_type就是我们找的父类类型
现在,我们就可以写出我们的get出来了!
typename get_helper<idx, tuple<Head, Tail...>>::value_type
get(tuple<Head, Tail...> &tup) {
using next_type = typename get_helper<idx, tuple<Head, Tail...>>::next_type;
return ((next_type *)(&tup))->value_;
}
看上去和stl中的差不多耶
并且计算全丢给了编译期去做,我们只负责做了一个指针转换,时间代价不大
然后,我们就简单的实现出来了我们的 tuple
int main() {
tuple<int, double, std::string> t(1, 2.3, "haha");
std::cout << t.value_ << " " << t.next().value_ << " "
<< t.next().next().value_ << " \n";
std::cout << get<0>(t) << " ";
std::cout << get<1>(t) << " ";
std::cout << get<2>(t) << " ";
return 0;
}
( 那么,tie呢?
完整代码
#include <bits/stdc++.h>
template <typename... Arg> class tuple;
template <> class tuple<> {};
template <typename Head, typename... Tail>
class tuple<Head, Tail...> : tuple<Tail...> {
public:
Head value_;
using Next = tuple<Tail...>;
public:
tuple() {}
tuple(Head value, Tail... tail) : value_(value), Next(tail...) {}
// protected:
Next &next() { return *this; }
};
template <size_t idx, typename... Arg> class get_helper;
template <size_t idx, typename Head, typename... Tail>
struct get_helper<idx, tuple<Head, Tail...>> {
using value_type = typename get_helper<idx - 1, tuple<Tail...>>::value_type;
using next_type = typename get_helper<idx - 1, tuple<Tail...>>::next_type;
};
template <typename Head, typename... Tail>
struct get_helper<0, tuple<Head, Tail...>> {
using value_type = Head;
using next_type = tuple<Head, Tail...>;
};
template <> struct get_helper<0, tuple<>> {
using value_type = void;
using next_type = tuple<>;
};
template <size_t idx, typename Head, typename... Tail>
typename get_helper<idx, tuple<Head, Tail...>>::value_type
get(tuple<Head, Tail...> &tup) {
using next_type = typename get_helper<idx, tuple<Head, Tail...>>::next_type;
return ((next_type *)(&tup))->value_;
}
int main() {
tuple<int, double, std::string> t(1, 2.3, "haha");
std::cout << t.value_ << " " << t.next().value_ << " "
<< t.next().next().value_ << " \n";
std::cout << get<0>(t) << " ";
std::cout << get<1>(t) << " ";
std::cout << get<2>(t) << " ";
return 0;
}