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

news2024/9/20 12:33:10

相关系列文章

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()
{
    // OK
    std::cout << std::apply(add, std::pair(1, 2)) << '\n';
 
    // 错误:无法推导函数类型
    // std::cout << std::apply(add_generic, std::make_pair(2.0f, 3.0f)) << '\n'; 
 
    // OK
    std::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; // 2
    std::cout << b << std::endl; // 4.5
    std::cout << c << std::endl; // 234
    std::cout << d << std::endl; // 22
    return 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.coloradmin.cn/o/1437642.html

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

相关文章

【极数系列】Flink集成KafkaSink 实时输出数据(11)

文章目录 01 引言02 连接器依赖2.1 kafka连接器依赖2.2 base基础依赖 03 使用方法04 序列化器05 指标监控06 项目源码实战6.1 包结构6.2 pom.xml依赖6.3 配置文件6.4 创建sink作业 01 引言 KafkaSink 可将数据流写入一个或多个 Kafka topic 实战源码地址,一键下载可用&#xf…

2024最新CleanMyMac X优化Mac电脑系统体验分享

使用Mac电脑的用户都希望它们能够保持最佳性能。但有时&#xff0c;你可能会发现你的Mac运行变慢或响应迟缓。这可能是由于几个原因造成的&#xff0c;包括硬盘空间不足、系统缓存堆积、以及过多的启动项等。解决了这些问题&#xff0c;你可以显著提升你的Mac性能。本文将探讨导…

寒假作业2024.2.7

1请编程实现二又树的操作 1.1二又树的创建 1.2二又树的先序遍历 1.3二又树的中序遍历 1.4二又树的后序遍历 1.5二又树各个节点度的个数 1.6二叉树的深度 #include <stdio.h> #include <stdlib.h> #include <string.h> #include <math.h> #include <…

【JS逆向六】(上)逆向模拟生成某网站的【sig】和【payload】的值 仅供学习

逆向日期&#xff1a;2024.02.07 使用工具&#xff1a;Node.js 加密方法&#xff1a;未知 / md5标准库 文章全程已做去敏处理&#xff01;&#xff01;&#xff01; 【需要做的可联系我】 可使用AES进行解密处理&#xff08; 直接解密即可&#xff09;&#xff1a;在线AES加解…

[HCIE]防火墙基础配置

实验目的&#xff1a;1.允许主机 ping FW的物理接口&#xff1b; 2.允许FW访问trust区域的主机&#xff1b; 3.允许trust主机访问其他所有区域。 配置步骤&#xff1a; 1.配置所有接口地址&#xff08;略&#xff09; 2.将防火墙接口加入到各自的区域 firewall zone trust …

企业数字化转型面临什么挑战?

随着科技的飞速发展&#xff0c;企业数字化转型已成为当今商业世界的一大趋势。然而&#xff0c;在这场变革中&#xff0c;企业可能会面临诸多挑战。本文将结合实际案例&#xff0c;探讨企业在数字化转型过程中可能遇到的一些问题&#xff0c;并提供相应的解决建议。 1.技术挑…

C# CAD交互界面-自定义窗体(三)

运行环境 vs2022 c# cad2016 调试成功 一、引用 二、开发代码进行详细的说明 初始化与获取AutoCAD核心对象&#xff1a; Database db HostApplicationServices.WorkingDatabase;&#xff1a;这行代码获取当前工作中的AutoCAD数据库对象。在AutoCAD中&#xff0c;所有图形数…

研发误删的库,凭什么要 DBA 承担责任

镇楼图 三个角色 删库以及更宽泛的数据库变更场景中有三个角色&#xff0c;业务研发&#xff0c;DBA 以及使用的数据库变更工具&#xff1a; 业务研发通常指的是后端研发。国内最主流的技术栈还是 Java&#xff0c;此外 Go 也有一部分&#xff0c;另有全栈的则使用 Node。这些…

如何开始深度学习,从实践开始

将“如何开始深度学习”这个问题喂给ChatGPT和文心一言&#xff0c;会给出很有专业水准的答案&#xff0c;比如&#xff1a; 要开始深度学习&#xff0c;你可以遵循以下步骤&#xff1a; 学习Python编程语言的基础知识&#xff0c;因为它在深度学习框架中经常被使用。 熟悉线性…

如何在GitHub上创建自己的个人网站【github.io】

如何在GitHub上创建自己的个人网站【github.io】 1.创建一个自己的GitHub账号2.点击New按钮3.填写个人仓库相关的信息并创建4.安装git&#xff0c;下载你需要的版本。5.上传新的文件6.访问网站7.后话 1.创建一个自己的GitHub账号 GitHub网站 最后会出现像下面一样的界面 2.点…

spring boot学习第十一篇:发邮件

1、pom.xml文件内容如下&#xff08;是我所有学习内容需要的&#xff0c;不再单独分出来&#xff0c;包不会冲突&#xff09;&#xff1a; <?xml version"1.0" encoding"UTF-8"?> <project xmlns"http://maven.apache.org/POM/4.0.0"…

基于Java农产品商城系统设计与实现(源码+部署文档)

博主介绍&#xff1a; ✌至今服务客户已经1000、专注于Java技术领域、项目定制、技术答疑、开发工具、毕业项目实战 ✌ &#x1f345; 文末获取源码联系 &#x1f345; &#x1f447;&#x1f3fb; 精彩专栏 推荐订阅 &#x1f447;&#x1f3fb; 不然下次找不到 Java项目精品实…

OpenCV-31 获得形态学卷积核

OpenCV提供了获取卷积核的API&#xff0c;不需要我们手动创建卷积核。 通过下面API---getStructuringElement(shape&#xff0c;ksize&#xff0c;[, anchor]) shape是指卷积核的型状&#xff0c;注意不是指长宽&#xff0c;是指卷积核中1形成的形状。MORPH_RECT 卷积核中的1…

内网远程控制——anydesk

而通常这个时候我们有权限修改anydesk的配置文件&#xff0c;这里进行测试&#xff0c;起两个虚拟机&#xff0c;设定一个场景&#xff08;攻击机拿到了webshell&#xff0c;受害机开着windows defender&#xff0c;如何去渗透拿到受害机权限&#xff09; 情景复现 这里拿到了…

C#(C Sharp)学习笔记_If条件判断语句【五】

前言&#xff1a; 本期学习的是编程语言中的主要语句&#xff1a;if-条件判断语句。在这里我们会学到&#xff1a;if语法&#xff0c;if-else&#xff0c;和if嵌套。话不多说&#xff0c;我们开始吧&#xff01; 什么是条件判断语句&#xff1f; 条件语句是用来判断给定的条件…

WireShark使用教程(TCP/IP 部分情况居然变成三次挥手了???)

WireShark自学 WrieShark介绍WrieShark的应用常见协议包的抓取 WrieShark常用手段混杂模式 和 普通模式混杂模式打开方式普通模式 过滤器过滤器类型捕获过滤器显示过滤器语法捕获到的数据的列的含义常见的 Protocols - Values 键盘快捷键常用的过滤命令常用协议分析ARP 协议分析…

Unity类银河恶魔城学习记录3-4 EnemyBattleState P50

Alex教程每一P的教程原代码加上我自己的理解初步理解写的注释&#xff0c;可供学习Alex教程的人参考 此代码仅为较上一P有所改变的代码 【Unity教程】从0编程制作类银河恶魔城游戏_哔哩哔哩_bilibili Enemy.cs using System.Collections; using System.Collections.Generic; …

数据结构第十一天(栈)

目录 前言 概述 源码&#xff1a; 主函数&#xff1a; 运行结果&#xff1a; ​编辑 前言 今天简单的实现了栈&#xff0c;主要还是指针操作&#xff0c;soeasy! 友友们如果想存储其他内容&#xff0c;只需修改结构体中的内容即可。 哈哈&#xff0c;要是感觉不错&…

3D室内虚拟灭火体验为预防火灾提供全新方案

室内火灾常见于充电器未拔、电动车、油锅起火及煤气泄露等原因&#xff0c;由于室内空间小、易燃物多&#xff0c;因此极易造成较大的人员财产损失&#xff0c;3D仿真还原技术、通过1&#xff1a;1模拟还原火灾发生全过程&#xff0c;火灾VR安全培训提供全方位、真实感强的模拟…

【01】判断素数/质数(C语言)

目录 &#xff08;1&#xff09;素数特点&#xff1a;只能被1和本身整除 &#xff08;2&#xff09;代码如下&#xff1a; &#xff08;3&#xff09;运行结果如下 ​编辑 &#xff08;4&#xff09;函数引申 &#xff08;1&#xff09;素数特点&#xff1a;只能被1和本身…