对于一个标准的 c++容器来说,我们可以很容易在运行时使用迭代器和 range-based for
loop 来遍历其中的每一个元素。但是对于std::tuple
,却不能像普通的容器那样去遍历它。
std::tuple 简介
std::tuple
是一个具有固定大小,包含不同类型值的集和。与之相似的是std::pair
,只不过std::pair
只能容纳两个元素,
而std::tuple
可以容纳许多元素:
1std::tuple<int, double, const char*> tup {42, 10.5, "hello"};
2
3// or with CTAD(class template argument deduction), C++17:
4std::tuple deducedTup {42, 10.5, "hello"}; // 自动推导类型
访问std::pair
中的元素只需要访问.first
和.second
成员即可:
然而std::tuple
并没有类似于.first
.second
的成员,标准库中我们只能通过std::get
来直接访问其中的元素:
1std::tuple tp {1, 2, "hello", "world", 10.5};
2
3// by index
4std::get<0>(tp) += 10;
5std::get<1>(tp) *= 20;
6std::cout << std::get<2>(tp) << '\n'; // hello
7std::cout << std::get<3>(tp) << '\n'; // world
8
9// by type
10std::cout << std::get<double>(tp) << '\n'; // 10.5
如何遍历 tuple
对 tuple 有了初步的了解,也知道了如何去获取 tuple 中的元素,那么接下来我们就来一步步构造对 tuple 遍历的方法。
准备工作
首先我们来编写下面这样一段代码,通过向模板中传递一系列想要访问的元素下标,来完成对tuple
的遍历:
1template<typename T> void print(const T& t)
2{
3 std::cout << t << ',';
4}
5
6template<typename TupleT, std::size_t... Is> void print_tuple(const TupleT& tp)
7{
8 (print(std::get<Is>(tp)), ...);
9}
10
11int main(int argc, char* argv[])
12{
13 std::tuple tp{1, 2, "hello", "world"};
14 print_tuple<decltype(tp), 0, 1, 2, 3>(tp);
15 return 0;
16}
代码比较简单,模板参数除了tuple
类型以外,还需要传递一组"non-type"模板参数,也就是指定要访问的元素下标,
然后通过 c++17 的 fold expression 来展开调用print
方法,打印对应下标的值。
使用 index_sequence 优化
在上面的代码中,我们需要手动传递元素的下标,那么有没有一种更好的办法,让编译器自动为我们生成元素下标,从而简化调用呢?
答案是可以的。在 c++14 中引入了std::index_sequence
和std::make_index_sequence
,这样的话我们的程序可以做以下优化:
1template<typename T> void print(const T& t)
2{
3 std::cout << t << ',';
4}
5
6template<typename TupleT, std::size_t... Is>
7void print_tuple(const TupleT& tp, std::index_sequence<Is...>)
8{
9 (print(std::get<Is>(tp)), ...);
10}
11
12
13int main(int argc, char* argv[])
14{
15 std::tuple tp{1, 2, "hello", "world"};
16 print_tuple(tp, std::make_index_sequence<4>());
17 return 0;
18}
以上代码通过std::make_index_sequence
和std::index_sequence
在编译期自动生成了元素下标的"non-type"模板参数,
但是在调用的时候还需要手动传 tupe 的 size,因此我们使用std::tuple_size
来继续对代码进行优化:
1template<typename T> void print(const T& t)
2{
3 std::cout << t << ',';
4}
5
6template<typename TupleT, std::size_t... Is>
7void print_tuple(const TupleT& tp, std::index_sequence<Is...>)
8{
9 (print(std::get<Is>(tp)), ...);
10}
11
12
13int main(int argc, char* argv[])
14{
15 std::tuple tp{1, 2, "hello", "world"};
16 print_tuple(tp, std::make_index_sequence<std::tupe_size_v<decltype(tp)>>());
17 return 0;
18}
接下来我们再对代码进行一些封装,从而让我们的使用再简单一些:
1template<typename T> void print(const T& t)
2{
3 std::cout << t << ',';
4}
5
6template<typename TupleT, std::size_t... Is>
7void print_tuple_manual(const TupleT& tp, std::index_sequence<Is...>)
8{
9 (print(std::get<Is>(tp)), ...);
10}
11
12template<typename TupleT, std::size_t tuple_size = std::tuple_size_v<TupleT>>
13void print_tuple(const TupleT& tp)
14{
15 print_tuple_manual(tp, std::make_index_sequence<tuple_size>());
16}
17
18int main(int argc, char* argv[])
19{
20 std::tuple tp{1, 2, "hello", "world"};
21 print_tuple(tp);
22 return 0;
23}
打印 tuple
虽然我们已经能够简单的向print_tuple
函数传递一个tuple
实例来实现对tuple
内元素的打印了,但是
我们还是希望对打印的格式进行一个优化,下面我们来为打印加上括号和下标:
1template<typename TupleT, std::size_t... Is>
2void print_tuple_manual(const TupleT& tp, std::index_sequence<Is...>)
3{
4 auto print = [](const auto& x, std::size_t idx) {
5 if (idx > 0) {
6 std::cout << ", ";
7 }
8 std::cout << idx << ": " << x;
9 };
10 std::cout << '(';
11 (print(std::get<Is>(tp), Is), ...);
12 std::cout << ')';
13}
14
15template<typename TupleT, std::size_t tuple_size = std::tuple_size_v<TupleT>>
16void print_tuple(const TupleT& tp)
17{
18 print_tuple_manual(tp, std::make_index_sequence<tuple_size>());
19}
20
21int main(int argc, char* argv[])
22{
23 std::tuple tp{1, 2, "hello", "world"};
24 print_tuple(tp); // (0: 1, 1: 2, 2: hello, 3: world)
25 return 0;
26}
\<\<操作符
下面我们继续优化,为std::tuple
实现<<
操作符:
1template<typename TupleT, std::size_t... Is>
2std::ostream& print_tuple_manual(std::ostream& os, const TupleT& tp, std::index_sequence<Is...>)
3{
4 auto print = [&os](const auto& x, std::size_t idx) {
5 if (idx > 0) {
6 os << ", ";
7 }
8 os << idx << ": " << x;
9 };
10 os << '(';
11 (print(std::get<Is>(tp), Is), ...);
12 os << ')';
13 return os;
14}
15
16template<typename TupleT, std::size_t tuple_size = std::tuple_size<TupleT>::value>
17std::ostream& operator<<(std::ostream& os, const TupleT& tp)
18{
19 return print_tuple_manual(os, tp, std::make_index_sequence<tuple_size>{});
20}
21
22int main(int argc, char* argv[])
23{
24 std::tuple tp{1, 2, "hello", "world"};
25 std::cout << tp << std::endl; // (0: 1, 1: 2, 2: hello, 3: world)
26 return 0;
27}
评论