C++17新特性 结构化绑定

一、Python中的相似功能

熟悉python的应该对下面的代码很熟悉

def return_multiple_values():
    return 11, 7

x, y = return_multiple_values()

函数返回一个元组,元组自动分配给了x和y。

二、C++11中的元组

c++11中就存在类似python元组的概念了:

std::tuple<int, int> return_multiple_values() {
    return std::make_tuple(11, 7);
}

int main() {
    int x = 0, y = 0;
    std::tie(x, y) = return_multiple_values();
    std::cout << "x=" << x << " y=" << y << std::endl;
}

这段代码和Python完成了同样的工作,但代码却要麻烦许多。一个原因是C++11必须指定return_multiple_values函数的返回值类型,另外,在调用return_multiple_values函数前还需要声明变量xy,并且使用函数模板std::tiexy通过引用绑定到std::tuple<int&, int&>

三、结构化绑定

对于第一个问题,可以使用C++14中auto的新特性来简化返回类型的声明

auto return_multiple_values() {
    return std::make_tuple(11, 7);
}

对于第二个问题,就必须使用C++17的新特性,结构化绑定。所谓结构化绑定是指将一个或者多个名称绑定到初始化对象中的一个或者多个子对象(或者元素)上,相当于给初始化对象的子对象(或者元素)起了别名,请注意别名不同于引用。

auto return_multiple_values() {
    return std::make_tuple(11, 7);
}

int main() {
    auto[x, y] = return_multiple_values();
    std::cout << "x=" << x << " y=" << y << std::endl;
}

其中auto是类型占位符,[x, y]是绑定标识符列表,其中xy是用于绑定的名称,绑定的目标是函数return_multiple_values()返回结果副本的子对象或者元素。

结构化绑定的目标不必是一个函数的返回结果,实际上等号的右边可以是任意一个合理的表达式,比如:

struct BindTest {
    int a = 42;
    std::string b = "hello structured binding";
};

int main() {
    BindTest bt;
    auto[x, y] = bt;
    std::cout << "x=" << x << " y=" << y << std::endl;
}

可以看到结构化绑定能够直接绑定到结构体上。将其运用到基于范围的for循环中会有更好的效果:

struct BindTest {
    int a = 42;
    std::string b = "hello structured binding";
};

int main() {
    std::vector<BindTest> bt{ {11, "hello"},  {7, "c++"},  {42, "world"} };
    for (const auto& [x, y] : bt) {
       std::cout << "x=" << x << " y=" << y << std::endl;
    }
}

四、深入理解结构化绑定

在结构化绑定时,编译器会生成一个等号右边对象的匿名副本,绑定的对象是这个匿名副本,不是右边对象,或者右边对象的引用。

BindTest bt;
const auto [x, y] = bt;

// 经过编译器处理
BindTest bt;
const auto _anonymous = bt;
aliasname x = _anonymous.a
aliasname y = _anonymous.b

_anonymous是编译器生成的匿名对象,const auto [x, y] = btauto的限定符会直接应用到匿名对象_anonymous上。也就是说,_anonymousconst还是volatile完全依赖auto的限定符。

4.1、示例一

看下面的代码:

int main() {
  BindTest bt;
  const auto[x, y] = bt;

  std::cout << "&bt.a=" << &bt.a << " &x=" << &x << std::endl;
  std::cout << "&bt.b=" << &bt.b << " &y=" << &y << std::endl;
  std::cout << "std::is_same_v<const int, decltype(x)>=" 
       << std::is_same_v<const int, decltype(x)> << std::endl;
  std::cout << "std::is_same_v<const std::string, decltype(y)>=" 
       << std::is_same_v<const std::string, decltype(y)> << std::endl;
}

结果如下

image-193526431

可以看到别名x并不是bt.a,因为它们的内存地址不同。另外,xy的类型分别与const intconst std::string相同也证明了它们是别名而不是引用的事实。可见,如果在上面这段代码中试图使用xy去修改bt的数据成员是无法成功的,因为一方面xy都是常量类型;另一方面即使xy是非常量类型,改变的xy只会影响匿名对象而非bt本身。

4.2、示例二

看下面的代码

int main() {
    BindTest bt;
    auto&[x, y] = bt;

    std::cout << "&bt.a=" << &bt.a << " &x=" << &x << std::endl;
    std::cout << "&bt.b=" << &bt.b << " &y=" << &y << std::endl;

    x = 11;
    std::cout << "bt.a=" << bt.a << std::endl;
    bt.b = "hi structured binding";
    std::cout << "y=" << y << std::endl;
}

结果如下

image-20240513193859400

虽然只是将const auto修改为auto&,但是已经能达到让bt数据成员和xy相互修改的目的了。别名真的是单纯的别名,别名的类型和绑定目标对象的子对象类型相同,而引用类型本身就是一种和非引用类型不同的类型

4.3、示例三

auto t = std::make_tuple(42, "hello world");
auto [x] = t;

以上代码是无法通过编译的,必须有两个别名分别对应bt的成员变量ab,使用结构化绑定无法忽略对象的子对象或者元素。

可以仿照C++11中std::tie使用std::ignore的方案:

auto t = std::make_tuple(42, "hello world");
int x = 0, y = 0;
std::tie(x, std::ignore) = t;
std::tie(y, std::ignore) = t;

虽然这个方案对于std::tie是有效的,但是结构化绑定的别名还有一个限制:无法在同一个作用域中重复使用。这一点和变量声明是一样的,比如:

auto t = std::make_tuple(42, "hello world");
auto[x, ignore] = t;
auto[y, ignore] = t;    // 编译错误,ignore无法重复声明

五、结构化绑定的三种类型

5.1、绑定到原生数组

绑定到原生数组即将标识符列表中的别名一一绑定到原生数组对应的元素上。所需条件仅仅是要求别名的数量与数组元素的个数一致,比如:

int a[3]{ 1, 3, 5 };
auto[x, y, z] = a;
std::cout << "[x, y, z]=[" << x << ", " << y << ", " << z << "]" << std::endl;

5.2、绑定到结构体和类对象

一些限制:

1、类或者结构体中的非静态数据成员个数必须和标识符列表中的别名的个数相同;

2、这些数据成员必须是公有的

3、这些数据成员必须是在同一个类或者基类中

4、绑定的类和结构体中不能存在匿名联合体

示例一:

class BindTest {
    int a = 42;        // 私有成员变量
public:
    double b = 11.7;
};

int main() {
    BindTest bt;
    auto[x, y] = bt;   // 编译失败,有私有成员变量
    auto[x] = bt;      // 编译失败,有私有成员变量
    
}

示例二

class BindBase1 {
public:
    int a = 42;
    double b = 11.7;
};
class BindBase2 {};
class BindBase3 {
public:
    int a = 42;
};

class BindTest1 : public BindBase1 {};
class BindTest2 : public BindBase2 {
public:
    int a = 42;
    double b = 11.7;
};
class BindTest3 : public BindBase3 {
public:
    double b = 11.7;
};

int main() {
    BindTest1 bt1;
    BindTest2 bt2;
    BindTest3 bt3;
    auto[x1, y1] = bt1;    // 编译成功
    auto[x2, y2] = bt2;    // 编译成功
    auto[x3, y3] = bt3;    // 编译错误,不在一个类或者基类内
}

auto[x1, y1] = bt1auto[x2, y2] = bt2可以顺利地编译,因为类BindTest1BindTest2的非静态数据成员要么全部在派生类中定义,要么全部在基类中定义。BindTest3却不同,其中成员变量a的定义在基类,成员变量b的定义在派生类,这一点违反了绑定结构体的限制条件,所以auto[x3, y3] = bt3会导致编译错误。

5.3、绑定到元组和类元组的对象

绑定到元组就是将标识符列表中的别名分别绑定到元组对象的各个元素。绑定到类元组要从绑定的限制条件讲起。绑定元组和类元组有一系列抽象的条件:对于元组或者类元组类型T

1.需要满足std::tuple_size<T>::value是一个符合语法的表达式,并且该表达式获得的整数值与标识符列表中的别名个数相同。

2.类型T还需要保证std::tuple_element<i, T>::type也是一个符合语法的表达式,其中i是小于std::tuple_size<T>::value的整数,表达式代表了类型T中第i个元素的类型。

3.类型T必须存在合法的成员函数模板get<i>()或者函数模板get<i>(t),其中i是小于std::tuple_size<T>::value的整数,t是类型T的实例,get<i>()get<i>(t)返回的是实例t中第i个元素的值。

理解上述条件会发现,它们其实比较抽象。这些条件并没有明确规定结构化绑定的类型一定是元组,任何具有上述条件特征的类型都可以成为绑定的目标。另外,获取这些条件特征的代价也并不高,只需要为目标类型提供std::tuple_sizestd::tuple_element以及get的特化或者偏特化版本即可。实际上,标准库中除了元组本身毫无疑问地能够作为绑定目标以外,std::pairstd::array也能作为结构化绑定的目标,其原因就是它们是满足上述条件的类元组。

可以利用这个特性简化代码:

int main() {
  std::map<int, std::string> id2str{ {1, "hello"}, {3, "Structured"}, {5, "bindings"} };

    for (const auto& elem : id2str) {
        std::cout << "id=" << elem.first << ", str=" << elem.second << std::endl;
    }
}

// ==> 简化为
for (const auto&[id, str]:id2str) {
    std::cout << "id=" << id << ", str=" << str << std::endl;
}

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

高速电流反馈运放总结

目录 前言 基础架构 CFB运算放大器拓扑结构的进步 前言 最近项目发现有震荡&#xff0c;发现是电流反馈型运放导致&#xff0c;所以对电流运放的知识做了全面的复习。 基础架构 现在&#xff0c;我们将详细考察高速运算放大器中非常流行的电流反馈(CFB)运算放大器拓扑结 构…

黑盒测试中的边界值分析

黑盒测试是一种基于需求和规格的测试方法&#xff0c;它主要关注软件系统输出的正确性和完整性&#xff0c;而不考虑内部代码的实现方式。在黑盒测试中&#xff0c;边界值分析是一种重要的测试技术&#xff0c;它可以帮助测试人员有效地发现输入和输出的问题。本文将从什么是边…

【数据结构】二叉排序树(查找+插入+删除+效率分析)完整代码+解析

3.1 二叉排序树 3.1.1 定义 二叉排序树的定义 又称二叉查找树&#xff08;BST&#xff0c;Binary Search Tree&#xff09; 二叉排序树是具有以下性质的二叉树&#xff1a; 左子树结点值<根结点值<右子树结点值 进行中序遍历&#xff0c;可以得到一个递增的有序序列。 3…

无需公网IP、无需云服务器,异地组网实现远程直连NAS、游戏联机

手机图片、视频太多&#xff0c;存储空间不够用怎么办?出门在外无法直连家中NAS&#xff0c;远程访问NAS速度慢&#xff1f;自建私有云、多媒体服务器&#xff0c;如何多人远程共享媒体资源&#xff1f;幻兽帕鲁、我的世界、泰拉瑞亚…局域网游戏&#xff0c;想远程多人联机&a…

Golang面向对象编程(二)

文章目录 封装基本介绍封装的实现工厂函数 继承基本介绍继承的实现字段和方法访问细节多继承 封装 基本介绍 基本介绍 封装&#xff08;Encapsulation&#xff09;是面向对象编程&#xff08;OOP&#xff09;中的一种重要概念&#xff0c;封装通过将数据和相关的方法组合在一起…

RobbitMQ基本消息队列的消息接收

1.先给工程引入依赖 父工程有了子工程就不用导了 <!--AMQP依赖&#xff0c;包含RabbitMQ--> <dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-amqp</artifactId> </dependency> 2.配置yml…

基于大数据+Hadoop的豆瓣电子图书推荐系统设计和实现

博主介绍&#xff1a;✌全网粉丝30W,csdn特邀作者、博客专家、CSDN新星计划导师、Java领域优质创作者,博客之星、掘金/华为云/阿里云/InfoQ等平台优质作者、专注于Java技术领域和学生毕业项目实战,高校老师/讲师/同行交流合作✌ 主要内容&#xff1a;SpringBoot、Vue、SSM、HLM…

linux学习:多媒体开发库SDL+视频、音频、事件子系统+处理yuv视频源

目录 编译和移植 视频子系统 视频子系统产生图像的步骤 api 初始化 SDL 的相关子系统 使用指定的宽、高和色深来创建一个视窗 surface 使用 fmt 指定的格式创建一个像素点​编辑 将 dst 上的矩形 dstrect 填充为单色 color​编辑 将 src 快速叠加到 dst 上​编辑 更新…

sqli-labs 第十七关

目录 找注入点&#xff1a; 源码分析&#xff1a; 测试&#xff1a; 奇怪现象&#xff1a; &#xff08;1&#xff09;&#xff1a;当我们输入的密码为字符进行注入时。 &#xff08;2&#xff09;&#xff1a;当我们输入的密码为整数时。 产生原因&#xff1a; 解决方法…

Docker:docker在项目中常用的一些命令

简介   Docker 是一个开源的容器化平台&#xff0c;它允许开发者将应用程序及其依赖项打包到一个可移植的容器中&#xff0c;并发布到任何安装了 Docker 引擎的机器上。这些容器是轻量级的&#xff0c;包含了应用程序运行所需的所有东西&#xff0c;如代码、系统库、系统工具…

SpringBoot集成Redis环境搭建及配置详解

前言 Redis作为当前最火的NoSQL数据库&#xff0c;支持很多语言客户端操作Redis。 而SpringBoot作为java当前最火的开发框架&#xff0c;提供了Spring-data-redis框架实现对Redis的各种操作。 在springboot1.5.x版本的默认的Redis客户端都是Jedis实现的&#xff0c;springboot…

大模型时代下两种few shot高效文本分类方法

介绍近年(2022、2024)大语言模型盛行下的两篇文本分类相关的论文&#xff0c;适用场景为few shot。两种方法分别是setfit和fastfit&#xff0c;都提供了python的包使用方便。 论文1&#xff1a;Efficient Few-Shot Learning Without Prompts 题目&#xff1a;无需提示的高效少…

浪潮信息企业级存储逆势增长 市场份额位列中国前二

2023年&#xff0c;中国企业级存储市场竞争激烈&#xff0c;在挑战重重之下&#xff0c;浪潮信息仍然实现逆势增长&#xff0c;销售额增幅达4.7%&#xff0c;市场份额相比2022年扩大0.6%&#xff0c;位列中国前二。另外&#xff0c;在高端和全闪存阵列细分市场&#xff0c;浪潮…

Vue3实战Easy云盘(三):文件删除+文件移动+目录导航+上传优化/文件过滤/搜索

一、文件删除 &#xff08;1&#xff09;选中了之后才可以删除&#xff0c;没有选中时就显示暗调删除按钮 &#xff08;2&#xff09;实现选中高亮功能 &#xff08;3&#xff09;单个删除 &#xff08;4&#xff09;批量删除 Main.vue中 <!-- 按钮3 --><!-- 如果sel…

鸿蒙内核源码分析(用户态锁篇) | 如何使用快锁Futex(上)

快锁上下篇 鸿蒙内核实现了Futex&#xff0c;系列篇将用两篇来介绍快锁&#xff0c;主要两个原因: 网上介绍Futex的文章很少&#xff0c;全面深入内核介绍的就更少&#xff0c;所以来一次详细整理和挖透。涉及用户态和内核态打配合&#xff0c;共同作用&#xff0c;既要说用户…

【Linux】文件描述符和重定向

目录 一、回顾C文件 二、系统文件I/O 2.1 系统调用 open 2.2 标志位传参 2.3 系统调用 write 2.4 文件描述符fd 2.5 struct file 2.6 fd的分配规则 2.7 重定向 2.7.1 基本原理&#xff1a; 2.7.2 系统调用 dup2 2.8 标准错误 一、回顾C文件 文件 内容 属性 对…

3分钟,学会一个 Lambda 小知识之【流API】

之前给大家介绍的 Lambda 小知识还记得吗&#xff1f;今天再来给大家介绍&#xff0c; 流API 的相关知识要点。 流API Stream是Java8中处理集合的关键抽象概念&#xff0c;它可以指定你对集合的&#xff0c;可以执行查找、过滤和映射等数据操作。 Stream 使用一种类似用 SQ…

资料如何打印更省钱

在日常工作和学习中&#xff0c;我们经常需要打印各种资料。然而&#xff0c;随着打印成本的不断提高&#xff0c;如何更省钱地打印资料成为了大家关注的焦点。今天&#xff0c;就为大家分享一些资料打印的省钱技巧&#xff0c;并推荐一个省钱又省心的打印平台。 首先&#xff…

冥想的时候怎么专注自己

冥想的时候怎么专注自己&#xff1f;我国传统的打坐养生功法&#xff0c;实际最早可追溯到五千年前的黄帝时代。   每天投资两个半小时的打坐&#xff0c;有上千年之久的功效。因为当你们打坐进入永恒时&#xff0c;时间停止了。这不只是两个半小时&#xff0c;而是百千万亿年…

深入探讨黑盒测试:等价类划分与边界值分析

文章目录 概要黑盒测试等价类划分边界值分析 设计测试用例小结 概要 在软件开发领域&#xff0c;测试是确保产品质量的关键步骤之一。而黑盒测试方法作为其中的一种&#xff0c;通过关注输入与输出之间的关系&#xff0c;而不考虑内部实现的细节&#xff0c;被广泛应用于各种软…