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

相关系列文章

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.总结


1.简介

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

template<class... Types>
class tuple;

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

2.std::ignore介绍

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

#include <iostream>
#include <string>
#include <set>
#include <tuple>int main()
{std::set<std::string> set_of_str;bool inserted = false;std::tie(std::ignore, inserted) = set_of_str.insert("Test");if (inserted) {std::cout << "Value was inserted successfully\n";}
}

输出:Value was inserted successfully

3.创建元组

3.1.直接初始化方式

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

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

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

3.3.make_tuple方式

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

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

 std::tie定义为:

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

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

bool myBool;
std::string myString;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特定的元素,如:

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

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

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

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

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

注意:

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

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

4.3.递归遍历元素

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

template<class Tuple, std::size_t N>
struct VisitTuple {static void Visit(const Tuple& value) {VisitTuple<Tuple, N - 1>::Visit(value);std::cout << ' ' << std::get<N - 1>(value);return void();}
};template<class Tuple>
struct VisitTuple<Tuple, 1> {static void Visit(const Tuple& value) {std::cout << std::get<0>(value);return void();}
};template<class... Args>
void TupleVisit(const std::tuple<Args...>& value) {VisitTuple<decltype(value), sizeof ...(Args)>::Visit(value);
}

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

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

#include <iostream>
#include <tuple>
#include <utility>int add(int first, int second) { return first + second; }template<typename T>
T add_generic(T first, T second) { return first + second; }auto add_lambda = [](auto first, auto second) { return first + second; };template<typename... Ts>
std::ostream& operator<<(std::ostream& os, std::tuple<Ts...> const& theTuple)
{std::apply([&os](Ts const&... tupleArgs){os << '[';std::size_t n{0};((os << tupleArgs << (++n != sizeof...(Ts) ? ", " : "")), ...);os << ']';}, theTuple);return os;
}int main()
{// OKstd::cout << std::apply(add, std::pair(1, 2)) << '\n';// 错误:无法推导函数类型// std::cout << std::apply(add_generic, std::make_pair(2.0f, 3.0f)) << '\n'; // OKstd::cout << std::apply(add_lambda, std::pair(2.0f, 3.0f)) << '\n'; // 进阶示例std::tuple myTuple(25, "Hello", 9.31f, 'c');std::cout << myTuple << '\n';
}

输出:

3
5
[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的定义如下:

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

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

#include <iostream>
#include <tuple>template <class T>
void test(T value)
{int a[std::tuple_size_v<T>]; // 能用于编译时std::cout << std::tuple_size<T>{} << ' ' // 或运行时<< sizeof a << ' ' << sizeof value << '\n';
}int main()
{test(std::make_tuple(1, 2, 3.14));
}

可能的输出:3 12 16

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

std::tuple_element定义如下:

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

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

#include <iostream>
#include <tuple>template <class... Args>
struct type_list
{template <std::size_t N>using type = typename std::tuple_element<N, std::tuple<Args...>>::type;
};int main()
{std::cout << std::boolalpha;type_list<int, char, bool>::type<2> x = true;std::cout << x << '\n';
}

输出:true

7.std::forward_as_tuple

定义如下:

template< class... Types >
tuple<Types&&...> forward_as_tuple( Types&&... args ) noexcept;
template< class... Types >
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。例如:

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

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

8.std::tuple_cat

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

#include <iostream>
#include <string>
#include <tuple>// 打印任何大小 tuple 的辅助函数
template<class Tuple, std::size_t N>
struct TuplePrinter
{static void print(const Tuple& t){TuplePrinter<Tuple, N - 1>::print(t);std::cout << ", " << std::get<N-1>(t);}
};template<class Tuple>
struct TuplePrinter<Tuple, 1>
{static void print(const Tuple& t) {std::cout << std::get<0>(t);}
};template<class... Args>
void print(const std::tuple<Args...>& t) 
{std::cout << "(";TuplePrinter<decltype(t), sizeof...(Args)>::print(t);std::cout << ")\n";
}
// 辅助函数结束int main()
{std::tuple<int, std::string, float> t1(10, "Test", 3.14);int n = 7;auto t2 = std::tuple_cat(t1, std::make_tuple("Foo", "bar"), t1, std::tie(n));n = 10;print(t2);
}

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

9.std::swap

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

std::tuple<int, double, char> a1;
std::tuple<int, double, char> a2;
std::tuple<unsigned int, double, char> a3;
std::tuple<int, std::string, char> a4;
std::tuple<int, double, char, std::string> a5;
a1.swap(a2); //OK
a2.swap(a3); //编译出现error
a3.swap(a4);//编译出现error
a4.swap(a5);//编译出现error

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

#include <iostream>
#include <string>
#include <tuple>int main()
{std::tuple<int, std::string, float>p1{42, "ABCD", 2.71},p2;p2 = std::make_tuple(10, "1234", 3.14);auto print_p1_p2 = [&](auto rem) {std::cout << rem<< "p1 = {" << std::get<0>(p1)<< ", "     << std::get<1>(p1)<< ", "     << std::get<2>(p1) << "}, "<< "p2 = {" << std::get<0>(p2)<< ", "     << std::get<1>(p2)<< ", "     << std::get<2>(p2) << "}\n";};print_p1_p2("Before p1.swap(p2): ");p1.swap(p2);print_p1_p2("After  p1.swap(p2): ");swap(p1, p2);print_p1_p2("After swap(p1, p2): ");
}

输出:

Before p1.swap(p2): p1 = {42, ABCD, 2.71}, p2 = {10, 1234, 3.14}
After  p1.swap(p2): p1 = {10, 1234, 3.14}, p2 = {42, ABCD, 2.71}
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的元素作为构造函数的参数构造别的类型对象,如下例子:

#include <iostream>
#include <tuple>struct Foo
{Foo(int first, float second, int third){std::cout << first << ", " << second << ", " << third << "\n";}
};int main()
{auto tuple = std::make_tuple(42, 3.14f, 0);std::make_from_tuple<Foo>(std::move(tuple));
}

输出:42, 3.14, 0

11.总结

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

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

参考:std::tuple - cppreference.com

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/672915.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

C语言中的内存函数你知道多少呢?

目录 ​编辑 1. memcpy的使用和模拟实现 1.1函数介绍 ​编辑 1.2函数的使用 1.3模拟实现 2. memmove的使用和模拟实现 2.1函数介绍 2.2函数的使用 2.3模拟实现 3. memset函数的使用 3.1函数介绍 3.2函数的使用 ​编辑 4. memcmp函数的使用 4.1函数介绍 4.2函数…

debian12 - openssh-9.6.P1的编译安装

文章目录 debian12 - openssh-9.6.P1的编译安装概述笔记备注END debian12 - openssh-9.6.P1的编译安装 概述 在debian12上, 源码编译安装了openssl3.2 导致ssh失败. lostspeeddebian12d4x64:~$ openssl version OpenSSL 3.2.0 23 Nov 2023 (Library: OpenSSL 3.2.0 23 Nov 2…

HCIA-HarmonyOS设备开发认证V2.0-3.轻量系统内核基础

目录 一、前言二、LiteOS-M系统概述三、内核框架3.1、CMSIS 和 POSIX 整体架构3.2、LiteOS-M内核启动流程 四、内核基础4.1、任务管理4.2、时间管理(待续)4.3、中断管理(待续)4.4、软件定时器(待续) 五、内存管理5.1、静态内存(待续)5.2、动态内存(待续) 六、内核通信机制6.1、…

从差旅申请到易快报,企业如何通过聚道云实现流程自动化?

客户介绍 深圳某有限公司是一家专注于软件开发与解决方案。公司主要提供全面的技术支持和服务&#xff0c;涵盖了多个领域&#xff0c;包括但不限于教育、企业级服务、工具类软件等。该公司产品旨在为每一所学校、每一个学生及家庭提供简单高效的编程创作体验和专业服务&#…

Linux内核与驱动面试经典“小”问题集锦(4)

接前一篇文章&#xff1a;Linux内核与驱动面试经典“小”问题集锦&#xff08;3&#xff09; 问题5 问&#xff1a;Linux内核中内存分配都有哪些方式&#xff1f;它们之间的使用场景都是什么&#xff1f; 备注&#xff1a;这个问题是笔者近期参加蔚来面试时遇到的一个问题。这…

React+Antd实现省、市区级联下拉多选组件(支持只选省不选市)

1、效果 是你要的效果&#xff0c;咱们继续往下看&#xff0c;搜索面板实现省市区下拉&#xff0c;原本有antd的Cascader组件&#xff0c;但是级联组件必须选到子节点&#xff0c;不能只选省&#xff0c;满足不了页面的需求 2、环境准备 1、react18 2、antd 4 3、功能实现 …

【制作100个unity游戏之23】实现类似七日杀、森林一样的生存游戏14(附项目源码)

本节最终效果演示 文章目录 本节最终效果演示系列目录前言给树添加动画摇晃动画 效果&#xff08;后补效果&#xff09;源码完结 系列目录 前言 欢迎来到【制作100个Unity游戏】系列&#xff01;本系列将引导您一步步学习如何使用Unity开发各种类型的游戏。在这第23篇中&…

春节回家坐飞机后助听器就不好用了?如何过安检、拖运?

春节即将来临&#xff0c;很多人都要乘坐飞机回家或者出游。如果你是一位助听器使用者&#xff0c;你可能会有一些疑问&#xff1a;坐飞机能戴助听器吗&#xff1f;助听器会不会受到安检设备的影响&#xff1f;直接将助听器放在传送带上可以吗&#xff1f;……别担心&#xff0…

RabbitMQ-5.消费者的可靠性

消费者的可靠性 5.消费者的可靠性5.1.消费者确认机制5.2.失败重试机制5.3.失败处理策略5.4.业务幂等性5.4.1.唯一消息ID5.4.2.业务判断 5.5.兜底方案 5.消费者的可靠性 当RabbitMQ向消费者投递消息以后&#xff0c;需要知道消费者的处理状态如何。因为消息投递给消费者并不代表…

U盘显示空间小于实际U盘空间的解决方案

大家好,我是爱编程的喵喵。双985硕士毕业,现担任全栈工程师一职,热衷于将数据思维应用到工作与生活中。从事机器学习以及相关的前后端开发工作。曾在阿里云、科大讯飞、CCF等比赛获得多次Top名次。现为CSDN博客专家、人工智能领域优质创作者。喜欢通过博客创作的方式对所学的…

2024年【陕西省安全员C证】试题及解析及陕西省安全员C证复审考试

题库来源&#xff1a;安全生产模拟考试一点通公众号小程序 陕西省安全员C证试题及解析是安全生产模拟考试一点通总题库中生成的一套陕西省安全员C证复审考试&#xff0c;安全生产模拟考试一点通上陕西省安全员C证作业手机同步练习。2024年【陕西省安全员C证】试题及解析及陕西…

Java玩转《啊哈算法》解密回文之栈

菩萨清凉月&#xff0c;常游毕竟空&#xff0c;众生心垢净&#xff0c;菩提影现中。 这目录 这开头这代码地址栈案例代码优化建议类似扩展 这开头 各位女士们&#xff0c;先生们好&#xff01;本人最近在看《啊哈算法》&#xff0c;这本书写的确实还可以&#xff0c;很有趣味性…

IEC 104电力规约详细解读(三) - 遥信

1.功能简述 遥信&#xff0c;、即状态量&#xff0c;是为了将断路器、隔离开关、中央信号等位置信号上送到监控后台的信息。遥信信息包括&#xff1a;反应电网运行拓扑方式的位置信息。如断路器状态、隔离开关状态&#xff1b;反应一次二次设备工作状况的运行信息&#xff0c;如…

Leetcode刷题-(11~15)-Java+Python+JavaScript

算法是程序员的基本功&#xff0c;也是各个大厂必考察的重点&#xff0c;让我们一起坚持写算法题吧 遇事不决&#xff0c;可问春风&#xff0c;春风不语&#xff0c;即是本心。 我们在我们能力范围内&#xff0c;做好我们该做的事&#xff0c;然后相信一切都事最好的安排就可…

考研数据结构笔记(3)

顺序表存储结构 存储结构顺序结构定义基本操作的实现静态分配问题 动态分配代码功能 顺序表的特点: 顺序表小结顺序表的插入删除插入删除小结 顺序表的查找按位查找按值查找小结 存储结构 顺序结构 定义 线性表是具有相同数据类型的n(n>0)个数据元素的有限序列(每个数据元素…

JavaScript流程控制详解之循环结构(倒三角、九九乘法表)

循环结构 在JavaScript中&#xff0c;循环语句指的是在满足某个条件下重复执行 指定的一段代码。若条件结果为true,则重复执行&#xff0c;则进入循环&#xff0c;否则结束循环。 在JavaScript中&#xff0c;循环语句如下&#xff1a; while语句do…while语句for语句 while…

shell脚本基础语法(.sh ./ sh bash source shell)

Linux 之 Shell 脚本基础语法 0. 学习一门语言的顺序 1. Shell 编程概述 1.1 Shell 名词解释 在 Linux 操作系统中&#xff0c;Shell 是一个命令行解释器&#xff0c;它为用户提供了一个与操作系统内核交互的界面。用户可以通过 Shell 输入命令&#xff0c;然后 Shell 将这些…

ONLYOFFICE 文档开发者版 8.0:API和文档生成器更新

随着 8.0 版新功能的发布&#xff0c;我们更新了编辑器、文档生成器和插件的 API。请阅读本文了解详情。 PDF 支持 我们在 documentType 参数中添加了 pdf 文档这一类型。现在完全支持PDF文件*&#xff0c;包括含有可填写字段的文件&#xff0c;并且可以在ONLYOFFICE PDF 编辑…

深入了解Spring Expression Language(SpEL)

深入了解Spring Expression Language&#xff08;SpEL&#xff09; Spring Expression Language&#xff08;SpEL&#xff09;是Spring框架中强大的表达式语言&#xff0c;它在运行时提供了一种灵活的方式来评估字符串表达式。SpEL的设计目标是在各种Spring配置和编程场景中提供…

亚马逊AWS考试认证系列 - Retake是考试的坚强后盾

为什么说Retake是考试的坚强后盾&#xff1f; 在AWS的认证考试体系里面&#xff0c;Retake 是一次重考的机会&#xff0c;允许考生在第一次未通过考试的情况下&#xff0c;再次免费参加考试&#xff0c;注意是免费的。这为考生提供了一个机会来重新准备并且免费再次进行考试。…