C++输出任意tuple
tuple是C++11标准中引入的一个模板类,用于将多个值组合成一个单一的对象,类似于一个固定大小的数组,但是每个元素可以是不同的类型。tuple可以接受任意数量、任意类型的值,并将它们组合成一个元组。tuple的使用非常方便,可以通过std::make_tuple
函数创建一个元组。但是,也带来了一个问题,tuple中的参数类型和数量可以是任意的,那么想要输出一个tuple类型的数据就比较麻烦。本文记录一下常规输出tuple的方法和两种可以输出任意tuple类型数据的方法。
使用C++可变参数模板类实现tuple可以参考博客的另一篇文章。
常规输出tuple的方法
一般情况下,如果我们创建了一个自己知道内容的tuple数据,我们可以通过std::get<i>
的方法访问tuple中的元素,如下所示:
#include <iostream>
#include <tuple>
int main(int argc, char const *argv[])
{
std::tuple<int, float, std::string> my_tuple(42, 3.14, "Hello, world!");
std::cout << "("
<< std::get<0>(my_tuple) << ", "
<< std::get<1>(my_tuple) << ", "
<< std::get<2>(my_tuple) << ")"
<< std::endl;
return 0;
}
这样我们就可以得到我们想要的输出结果:
(42, 3.14, Hello, world!)
这种方法仅限于我们知道tuple中有多少数据的时候,如果我们想用一种通用的方法输出任意长度和类型的tuple类时,使用get
的方法就不再可行。下面将介绍两种方法用于输出任意类型的tuple。
递归和模板特化的方法
我们可以使用递归模板和特化模板的方法来实现对任意类型tuple的输出,代码的实现如下:
#include <iostream>
#include <tuple>
using namespace std;
template <typename Tuple, std::size_t N>
struct tuple_printer {
static void print(ostream& os, const Tuple& t) {
tuple_printer<Tuple, N - 1>::print(os, t);
os << ", " << std::get<N - 1>(t);
}
};
template <typename Tuple>
struct tuple_printer<Tuple, 1> {
static void print(ostream& os, const Tuple& t) {
os << std::get<0>(t);
}
};
template <typename... Args>
ostream& operator<<(ostream& os, const std::tuple<Args...>& t) {
os << "(";
tuple_printer<decltype(t), sizeof...(Args)>::print(os, t);
os << ")";
return os;
}
int main(int argc, char const* argv[]) {
auto data = make_tuple("hello", 1, 2.5, 3.14159);
cout << data;
return 0;
}
首先,我们需要定义一个tupel_printer
类模板,它有两个模板参数:Tuple表示要输出的tuple类型,N表示tuple中元素的个数。tupel_printer
中有一个静态成员函数print,该函数接受一个ostream&类型的参数和一个表示要输出的tuple对象的const引用参数。print函数使用递归模板来实现输出,首先调用tuple_printer<Tuple, N - 1>::print(os, t)来输出前N-1个元素,然后输出第N个元素,每个元素之间使用逗号隔开。接着,定义了一个特化版本的tupel_printer
类模板用于处理递归的边界,即当N为1时,只输出tuple中唯一的一个元素。最后,定义一个重载的<<符号用于输出tuple类型的数据。
上述代码的输出结果为:
(hello, 1, 2.5, 3.14159)
折叠表达式的方法
C++17中引入一种新特性——折叠表达式(fold expression)。折叠表达式是一种用于简化模板元编程的语法特性,它允许将某个二元操作符(如逗号运算符,加法运算符等)应用于一个参数包中的所有元素,并将结果合并成一个值。如下所示:
#include <iostream>
using namespace std;
template<typename... Args>
auto sum(Args... args) {
return (args + ...);
}
int main(int argc, char const *argv[])
{
cout << sum(1,2,3,4,5); // 输出15
return 0;
}
另外,也可以使用折叠表达式来打印一个参数包中所有的元素值:
#include <iostream>
using namespace std;
template<typename... Args>
void print(Args... args) {
((std::cout << args << " "), ...);
std::cout << std::endl;
}
int main() {
print(1, 2, 3, 4, 5); // 输出 "1 2 3 4 5"
return 0;
}
折叠表达式可以和其他语法特性组合使用以实现对tuple的遍历,比如c++17中的apply
或者C++14中的index_sequence
。
apply与折叠表达式
此时,我们就可以使用c++17提供的std::apply
方法结合折叠表达式实现对tuple类型数据的解包和输出,如下所示:
#include <iostream>
#include <tuple>
using namespace std;
template<typename... Args>
std::ostream& operator<<(std::ostream& os, const std::tuple<Args...>& t) {
os << "(";
std::apply([&os](const auto&... args) { ((os << args << ", "), ...); }, t);
return os << ")";
}
int main(int argc, char const* argv[]) {
auto data = make_tuple("hello", 1, 2.5, 3.14159);
cout << data;
return 0;
}
上述代码的执行结果为:
(hello, 1, 2.5, 3.14159, )
在上述的实现中,代码直接定义了一个重载<<运算符的模板函数,它接受一个std::ostream&类型的参数os和一个表示要输出的tuple对象的const引用参数。函数中,先输出一个左括号,然后使用std::apply函数来展开tuple中的所有元素,将lambda表达式应用到展开后的每一个元素上用于输出,元素之间用逗号隔开,最后输出一个右括号。因为使用了C++17的新特性,该方法相较于递归和模板特化的方法更加简洁,但是却不容易精准控制格式。
index_sequence与折叠表达式
index_sequence_for
可以生成参数包中对应的索引序列,使用index_sequence
和折叠表达式就能够非常精准地控制格式,如下所示:
#include <iostream>
#include <tuple>
using namespace std;
template<typename Tuple, std::size_t... Is>
void printTuple(std::ostream& os,const Tuple& t, std::index_sequence<Is...>) {
((os << (Is == 0 ? "" : ", ") << std::get<Is>(t)), ...);
}
template<typename... Args>
std::ostream& operator<<(std::ostream& os, const std::tuple<Args...>& t) {
os << "(";
printTuple(os, t, std::index_sequence_for<Args...>{});
os << ")";
return os;
}
int main(int argc, char const* argv[]) {
auto data = make_tuple("hello", 1, 2.5, 3.14159);
cout << data;
return 0;
}
上述代码的执行结果为:
(hello, 1, 2.5, 3.14159)
在上述实现中,同样也是通过定义重载<<运算符的模板函数来实现输出的,不同的是调用了一个printTuple的模板函数,这个函数通过折叠表达式解包tuple数据的索引,实现对数据的访问和格式控制。这种方式即简洁也更容易实现对tuple数据的精准访问控制,效果相对比较好。
Comments | NOTHING