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