C++ 初探模板 (2) 实现一个tuple

·  阅读 345
C++ 初探模板 (2) 实现一个tuple

本文已参与「新人创作礼」活动,一起开启掘金创作之路。

可变长模板

在开始写tuple之前,我们首先要明确需要的东西,名叫可变长模板

他会将参数形成一个包

我们可以用这个特性,写出这样的代码

void print() {}
template <typename T, typename... U> void print(T t, U... u) {
  std::cout << t << "\n,"[!!sizeof...(u)];
  print(u...);
}
复制代码

现在,我们使用这个技巧,来构建我们的tuple

tuple 的结构

我们希望实现的是一个 零开销的,抽象,可以支持我们任意多个元素堆放在一块地址空间上的一种数据结构
现在,我们的地址空间大概要求长成这个样子

intstringdoubleint
1"hello"2.03

连续的,且类型不同的

当然,我们可以 “硬编码”, 每次需要什么类型,就定义出这么多个类型出来

比如

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;
}
复制代码
分类:
后端
标签:
收藏成功!
已添加到「」, 点击更改