当前位置:   article > 正文

C++之std::tuple(一) : 使用精讲(全)_std::putle

std::putle

相关系列文章

C++之std::tuple(二) : 揭秘底层实现原理

C++三剑客之std::any(一) : 使用

C++之std::tuple(一) : 使用精讲(全)

C++三剑客之std::variant(一) : 使用

C++三剑客之std::variant(二):深入剖析

深入理解可变参数(va_list、std::initializer_list和可变参数模版)

std::apply源码分析

目录

1.简介

2.std::ignore介绍

3.创建元组

3.1.直接初始化方式

3.2.使用花括号初始化列表方式(C++11及以上版本)

3.3.make_tuple方式

3.4.使用std::tie()函数方式

4.元素访问

4.1.std::get()方式

4.2.使用结构化绑定(C++17及以上)

4.3.递归遍历元素

4.4.std::apply方式(C++17及以上)

5.获取std::tuple的size

6.获取元组中的元素类型

7.std::forward_as_tuple

8.std::tuple_cat

9.std::swap

10.std::make_from_tuple

11.项目实战

11.1.std::tuple的序列化和反序列化

11.2.获取std::tuple的数据大小

12.总结


1.简介

        C++11之后引入了std::tuple,俗称元组,元组(tuple)是一种用于组合多个不同类型的值的数据结构。元组可以将不同类型的数据打包在一起,类似于一个容器,可以按照索引顺序访问其中的元素。元组的大小在编译时确定,不支持动态添加或移除元素。std::tuple的定义如下:

  1. template<class... Types>
  2. class tuple;

        std::tuple类似互C语言的结构体,不需要创建结构体而又有结构体的特征,在某些情况下可以取代结构体而使得程序更加简洁,直观。std::tuple理论上可以定义无数多个不同类型的成员变量。特别是你需要在函数之间返回多个值时,或者需要一次性处理多个相关值时,使用元组可以简化代码并提高可读性。

2.std::ignore介绍

在标头 <tuple> 定义,任何值均可赋给而无效果的未指定类型的对象。目的是令 std::tie 在解包 std::tuple 时作为不使用的参数的占位符使用。例如:解包 set.insert() 所返回的 pair ,但只保存布尔值。

  1. #include <iostream>
  2. #include <string>
  3. #include <set>
  4. #include <tuple>
  5. int main()
  6. {
  7. std::set<std::string> set_of_str;
  8. bool inserted = false;
  9. std::tie(std::ignore, inserted) = set_of_str.insert("Test");
  10. if (inserted) {
  11. std::cout << "Value was inserted successfully\n";
  12. }
  13. }

输出:Value was inserted successfully

3.创建元组

3.1.直接初始化方式

  1. //显示初始化
  2. std::tuple<bool, int, double, std::string> a(true, 1, 3.0, "1112222");

3.2.使用花括号初始化列表方式(C++11及以上版本)

  1. //显示初始化
  2. std::tuple<bool, int, double, std::string> a{true, 1, 3.0, "1112222"};

3.3.make_tuple方式

  1. //显示初始化
  2. std::tuple<bool, int, double, std::string> a = make_tuple(true, 1, 3.0, "1112222");
  3. //隐式初始化
  4. auto b = make_tuple(true, 1, 3.0, "1112222");

3.4.使用std::tie()函数方式

 std::tie定义为:

  1. template<class... Types>
  2. constexpr tuple<Types&...> tie (Types&... args) noexcept;
std::tie生成一个tuple,此tuple包含的分量全部为实参的引用,与make_tuple完全相反。主要用于从tuple中提取数据。例如:
  1. bool myBool;
  2. int myInt;
  3. double myDouble;
  4. std::string myString;
  5. std::tie(myBool, myInt, myDouble, myString) = std::make_tuple(true, 1, 3.0, "1112222");

如果是要忽略某个特定的元素,还可以使用第2章节的std::ignore来占位,例如:

  1. bool myBool;
  2. std::string myString;
  3. std::tie(myBool, std::ignore, std::ignore, myString) = std::make_tuple(true, 1, 3.0, "1112222");

4.元素访问

4.1.std::get<index>()方式

使用std::get来访问std::tuple特定的元素,如:

  1. std::tuple<bool, int, std::string> a(true, 0, "sfsfs");
  2. bool b = std::get<0>(a);
  3. int c = std::get<1>(a);
  4. std::string d = std::get<2>(a);
  5. std::get<0>(a) = false;
  6. std::get<2>(a) = "s344242";

4.2.使用结构化绑定(C++17及以上)

在C++17及以上版本中,还可以使用结构化绑定 (structured bindings) 的方式来创建和访问元组,可以更方便地访问和操作元组中的元素。结构化绑定允许直接从元组中提取元素并赋值给相应的变量。例如:

  1. std::tuple<bool, int, std::string> myTuple(true, false, "Hello");
  2. auto [a, b, c] = myTuple;

这将自动创建变量a、b和c,并将元组中相应位置的值赋给它们。

注意:

元组是不可变的(immutable)一旦创建就不能更改其元素的值。但是,可以通过解构赋值或使用std::get<index>(tuple)来获取元组中的值,并将新的值赋给它们,从而修改元组中的值。

std::tuple不支持迭代器,获取元素的值时只能通过元素索引或tie解包。给定的索引必须是在编译期间就已经确定的,不能在运行期间动态传递,否则会产生编译错误

4.3.递归遍历元素

        由于 tuple 自身的原因,无法直接遍历,而 get<index> 中 index 必须为运行前设置好的常数
所以 tuple 的遍历需要我们手写,代码如下:

  1. template<class Tuple, std::size_t N>
  2. struct VisitTuple {
  3. static void Visit(const Tuple& value) {
  4. VisitTuple<Tuple, N - 1>::Visit(value);
  5. std::cout << ' ' << std::get<N - 1>(value);
  6. return void();
  7. }
  8. };
  9. template<class Tuple>
  10. struct VisitTuple<Tuple, 1> {
  11. static void Visit(const Tuple& value) {
  12. std::cout << std::get<0>(value);
  13. return void();
  14. }
  15. };
  16. template<class... Args>
  17. void TupleVisit(const std::tuple<Args...>& value) {
  18. VisitTuple<decltype(value), sizeof ...(Args)>::Visit(value);
  19. }

4.4.std::apply方式(C++17及以上)

利用可变参数的折叠表达式规则来访问std::tuple的元素,例如:

  1. #include <iostream>
  2. #include <tuple>
  3. #include <utility>
  4. int add(int first, int second) { return first + second; }
  5. template<typename T>
  6. T add_generic(T first, T second) { return first + second; }
  7. auto add_lambda = [](auto first, auto second) { return first + second; };
  8. template<typename... Ts>
  9. std::ostream& operator<<(std::ostream& os, std::tuple<Ts...> const& theTuple)
  10. {
  11. std::apply
  12. (
  13. [&os](Ts const&... tupleArgs)
  14. {
  15. os << '[';
  16. std::size_t n{0};
  17. ((os << tupleArgs << (++n != sizeof...(Ts) ? ", " : "")), ...);
  18. os << ']';
  19. }, theTuple
  20. );
  21. return os;
  22. }
  23. int main()
  24. {
  25. // OK
  26. std::cout << std::apply(add, std::pair(1, 2)) << '\n';
  27. // 错误:无法推导函数类型
  28. // std::cout << std::apply(add_generic, std::make_pair(2.0f, 3.0f)) << '\n';
  29. // OK
  30. std::cout << std::apply(add_lambda, std::pair(2.0f, 3.0f)) << '\n';
  31. // 进阶示例
  32. std::tuple myTuple(25, "Hello", 9.31f, 'c');
  33. std::cout << myTuple << '\n';
  34. }

输出:

  1. 3
  2. 5
  3. [25, Hello, 9.31, c]

        上面语句((os << tupleArgs << (++n != sizeof...(Ts) ? ", " : "")), ...);利用了C++17的折叠表达式,折叠表达式是C++17新引进的语法特性。使用折叠表达式可以简化对C++11中引入的参数包的处理,从而在某些情况下避免使用递归。如果有不是很明白的地方,可参考我的博客深入理解可变参数(va_list、std::initializer_list和可变参数模版)-CSDN博客

        关于std::applay的使用有不明白的地方,可以参考我的博客std::apply源码分析-CSDN博客

5.获取std::tuple的size

std::tuple_size的定义如下:

  1. template< class... Types >
  2. struct tuple_size< std::tuple<Types...> >
  3. : std::integral_constant<std::size_t, sizeof...(Types)> { };

提供对 tuple 中元素数量的访问,作为编译时常量表达式,计算std::tuple的大小。例如:

  1. #include <iostream>
  2. #include <tuple>
  3. template <class T>
  4. void test(T value)
  5. {
  6. int a[std::tuple_size_v<T>]; // 能用于编译时
  7. std::cout << std::tuple_size<T>{} << ' ' // 或运行时
  8. << sizeof a << ' '
  9. << sizeof value << '\n';
  10. }
  11. int main()
  12. {
  13. test(std::make_tuple(1, 2, 3.14));
  14. }

可能的输出:3 12 16

6.获取元组中的元素类型

std::tuple_element定义如下:

  1. template< std::size_t I, class... Types >
  2. class tuple_element< I, tuple<Types...> >;

可以使用std::tuple_element<index, tuple>::type来获取元组中特定索引位置的元素类型。

  1. #include <iostream>
  2. #include <tuple>
  3. template <class... Args>
  4. struct type_list
  5. {
  6. template <std::size_t N>
  7. using type = typename std::tuple_element<N, std::tuple<Args...>>::type;
  8. };
  9. int main()
  10. {
  11. std::cout << std::boolalpha;
  12. type_list<int, char, bool>::type<2> x = true;
  13. std::cout << x << '\n';
  14. }

输出:true

7.std::forward_as_tuple

定义如下:

  1. template< class... Types >
  2. tuple<Types&&...> forward_as_tuple( Types&&... args ) noexcept;
  3. template< class... Types >
  4. constexpr tuple<Types&&...> forward_as_tuple( Types&&... args ) noexcept;

用于接受右值引用数据生成 tuple, 与 std::make_tuple 不同的是它的右值是引用的,当修改其值的时候,原来赋值所用的右值也将修改,实质上就是赋予了它地址。同std::tie一样,也是生成一个全是引用的tuple,不过std::tie只接受左值,而std::forward_as_tuple左值、右值都接受。主要是用于不损失类型属性的转发数据。

注意此处 tuple 内的类型应为引用,否则相当于 std::make_tuple。例如:

  1. signed main(int argc, char *argv[]) {
  2. int a = 123, c = 456;
  3. float b = 33.f, d = .155;
  4. std::tuple<int&, float&, int&, float&> tu = std::forward_as_tuple(a,b,c,d);
  5. std::get<0> (tu) = 2;
  6. std::get<1> (tu) = 4.5f;
  7. std::get<2> (tu) = 234;
  8. std::get<3> (tu) = 22.f;
  9. std::cout << a << std::endl; // 2
  10. std::cout << b << std::endl; // 4.5
  11. std::cout << c << std::endl; // 234
  12. std::cout << d << std::endl; // 22
  13. return 0;
  14. }

注意:若参数是临时量,则 forward_as_tuple 不延续其生存期;必须在完整表达式结尾前使用它们。

8.std::tuple_cat

        此函数接受多个tuple作为参数,然后返回一个tuple。返回的这个tuple将tuple_cat的参数中的tuple的所有元素按所属的tuple在参数中的顺序以及其在tuple中的顺序排列成一个新的tuple。新tuple中元素的类型与参数中的tuple中的元素的类型完全一致。例如:

  1. #include <iostream>
  2. #include <string>
  3. #include <tuple>
  4. // 打印任何大小 tuple 的辅助函数
  5. template<class Tuple, std::size_t N>
  6. struct TuplePrinter
  7. {
  8. static void print(const Tuple& t)
  9. {
  10. TuplePrinter<Tuple, N - 1>::print(t);
  11. std::cout << ", " << std::get<N-1>(t);
  12. }
  13. };
  14. template<class Tuple>
  15. struct TuplePrinter<Tuple, 1>
  16. {
  17. static void print(const Tuple& t)
  18. {
  19. std::cout << std::get<0>(t);
  20. }
  21. };
  22. template<class... Args>
  23. void print(const std::tuple<Args...>& t)
  24. {
  25. std::cout << "(";
  26. TuplePrinter<decltype(t), sizeof...(Args)>::print(t);
  27. std::cout << ")\n";
  28. }
  29. // 辅助函数结束
  30. int main()
  31. {
  32. std::tuple<int, std::string, float> t1(10, "Test", 3.14);
  33. int n = 7;
  34. auto t2 = std::tuple_cat(t1, std::make_tuple("Foo", "bar"), t1, std::tie(n));
  35. n = 10;
  36. print(t2);
  37. }

输出:(10, Test, 3.14, Foo, bar, 10, Test, 3.14, 10)

9.std::swap

交换两个std::tuple的内容,前提是两个std::tuple的大小和元素类型必须相同,例如:

  1. std::tuple<int, double, char> a1;
  2. std::tuple<int, double, char> a2;
  3. std::tuple<unsigned int, double, char> a3;
  4. std::tuple<int, std::string, char> a4;
  5. std::tuple<int, double, char, std::string> a5;
  6. a1.swap(a2); //OK
  7. a2.swap(a3); //编译出现error
  8. a3.swap(a4);//编译出现error
  9. a4.swap(a5);//编译出现error

上面a1和a2的大小和元素类型都相同,因此可以交换。a2和a3、a3和a4、a4和a5类型不相同,因此不能交换。我们再看一个std::tuple交换的例子:

  1. #include <iostream>
  2. #include <string>
  3. #include <tuple>
  4. int main()
  5. {
  6. std::tuple<int, std::string, float>
  7. p1{42, "ABCD", 2.71},
  8. p2;
  9. p2 = std::make_tuple(10, "1234", 3.14);
  10. auto print_p1_p2 = [&](auto rem) {
  11. std::cout << rem
  12. << "p1 = {" << std::get<0>(p1)
  13. << ", " << std::get<1>(p1)
  14. << ", " << std::get<2>(p1) << "}, "
  15. << "p2 = {" << std::get<0>(p2)
  16. << ", " << std::get<1>(p2)
  17. << ", " << std::get<2>(p2) << "}\n";
  18. };
  19. print_p1_p2("Before p1.swap(p2): ");
  20. p1.swap(p2);
  21. print_p1_p2("After p1.swap(p2): ");
  22. swap(p1, p2);
  23. print_p1_p2("After swap(p1, p2): ");
  24. }

输出:

  1. Before p1.swap(p2): p1 = {42, ABCD, 2.71}, p2 = {10, 1234, 3.14}
  2. After p1.swap(p2): p1 = {10, 1234, 3.14}, p2 = {42, ABCD, 2.71}
  3. After swap(p1, p2): p1 = {42, ABCD, 2.71}, p2 = {10, 1234, 3.14}

10.std::make_from_tuple

std::make_from_tuple是以元组std::tuple的元素作为构造函数的参数构造别的类型对象,如下例子:

  1. #include <iostream>
  2. #include <tuple>
  3. struct Foo
  4. {
  5. Foo(int first, float second, int third)
  6. {
  7. std::cout << first << ", " << second << ", " << third << "\n";
  8. }
  9. };
  10. int main()
  11. {
  12. auto tuple = std::make_tuple(42, 3.14f, 0);
  13. std::make_from_tuple<Foo>(std::move(tuple));
  14. }

输出:42, 3.14, 0

11.项目实战

11.1.std::tuple的序列化和反序列化

利用QDataStream的序列化数据,重写操作符operator<<和operator>>,代码如下:

  1. template<typename... Ts>
  2. QDataStream& operator<<(QDataStream& dataStream, std::tuple<Ts...> const& theTuple)
  3. {
  4. std::apply
  5. (
  6. [&dataStream](Ts const&... tupleArgs){
  7. ((dataStream << tupleArgs), ...);
  8. }, theTuple
  9. );
  10. return dataStream;
  11. }
  12. template<typename... Ts>
  13. QDataStream& operator>>(QDataStream& dataStream, std::tuple<Ts...>& theTuple)
  14. {
  15. std::apply
  16. (
  17. [&dataStream](Ts&... tupleArgs){
  18. ((dataStream >> tupleArgs), ...);
  19. }, theTuple
  20. );
  21. return dataStream;
  22. }
  23. template<typename... Ts>
  24. QDataStream& operator<<(QDataStream& dataStream, std::tuple<Ts...> const& theTuple)
  25. {
  26. std::apply
  27. (
  28. [&dataStream](Ts const&... tupleArgs){
  29. (dataStream << ... << tupleArgs);
  30. }, theTuple
  31. );
  32. return dataStream;
  33. }
  34. template<typename... Ts>
  35. QDataStream& operator>>(QDataStream& dataStream, std::tuple<Ts...>& theTuple)
  36. {
  37. std::apply
  38. (
  39. [&dataStream](Ts&... tupleArgs){
  40. (dataStream >> ... >> tupleArgs);
  41. }, theTuple
  42. );
  43. return dataStream;
  44. }

11.2.获取std::tuple的数据大小

获取std::tuple的实际内容大小,但是std::tuple不能包含可变内容长度字段,代码如下:

  1. template<class Tuple, std::size_t N>
  2. struct stTupleContentSize{
  3. using first = typename std::tuple_element<N-1, Tuple>::type;
  4. using others = stTupleContentSize<Tuple, N-1>;
  5. static constexpr std::size_t = sizeof(first) + others::size;
  6. };
  7. template<class Tuple>
  8. struct stTupleContentSize<Tuple, 1>{
  9. using first = typename std::tuple_element<0, Tuple>::type;
  10. static constexpr std::size_t = sizeof(first);
  11. };

调用方法:

  1. using queryWaveParamData = std::tuple<int, bool, double, long>;
  2. constexpr int queryWaveParamDataSize = stTupleContentSize<queryWaveParamData, std::tuple_size_v<queryWaveParamData>>::size;

12.总结

std::tuple 是一种重要的数据结构,可以用于在函数参数之间传递数据,也可以作为函数的返回值。在实际项目中,我们可以灵活地使用 std::tuple,以简化代码,提高程序的性能。

后面我们将继续通过分析std::tuple源码的方式来更深层次讲解它的实现原理,值得期待哦。。。

参考:std::tuple - cppreference.com

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/Cpp五条/article/detail/305951
推荐阅读
相关标签
  

闽ICP备14008679号