1 聊聊结构化绑定-德赢Vwin官网 网
0
  • 聊天消息
  • 系统消息
  • 评论与回复
登录后你可以
  • 下载海量资料
  • 学习在线课程
  • 观看技术视频
  • 写文章/发帖/加入社区
会员中心
创作中心

完善资料让更多小伙伴认识你,还能领取20积分哦,立即完善>

3天内不再提示

聊聊结构化绑定

CPP开发者 来源:高性能架构探索 2023-08-03 15:34 次阅读
动机

std::mapinsert方法返回std::pair,两个元素分别是指向所插入键值对的迭代器与指示是否新插入元素的布尔值,而std::map::iterator解引用又得到键值对std::pair。在一个涉及std::map算法中,有可能出现大量的firstsecond,让人不知所措。

#include
#include

intmain()
{
typedefstd::mapMap;
Mapmap;
std::pairresult=map.insert(Map::value_type(1,2));
if(result.second)
std::cout<< "inserted successfully" << std::endl;
    for (Map::iterator iter = map.begin(); iter != map.end(); ++iter)
        std::cout << "[" << iter->first<< ", " << iter->second<< "]" << std::endl;
}

C++11标准库添加了std::tie,用若干引用构造出一个std::tuple,对它赋以std::tuple对象可以给其中的引用一一赋值(二元std::tuple可以由std::pair构造或赋值)。std::ignore是一个占位符,所在位置的赋值被忽略。

#include
#include
#include

intmain()
{
std::mapmap;
boolinserted;
std::ignore,inserted)=map.insert({1,2});
if(inserted)
std::cout<< "inserted successfully" << std::endl;
    for (auto&& kv : map)
        std::cout << "[" << kv.first << ", " << kv.second << "]" << std::endl;
}

但是这种方法仍远不完美,因为:

变量必须事先单独声明,其类型都需显式表示,无法自动推导;对于默认构造函数执行零初始化的类型,零初始化的过程是多余的;也许根本没有可用的默认构造函数,如std::ofstream

为此,C++17引入了结构化绑定(structured binding)。

#include
#include

intmain()
{
std::mapmap;
auto&&[iter,inserted]=map.insert({1,2});
if(inserted)
std::cout<< "inserted successfully" << std::endl;
    for (auto&& [key, value] : map)
        std::cout << "[" << key << ", " << value << "]" << std::endl;
}

结构化绑定这一语言特性在提议的阶段曾被称为分解声明(decomposition declaration),后来又被改回结构化绑定。这个名字想强调的是,结构化绑定的意义重在绑定而非声明。

语法

结构化绑定有三种语法:

attr(optional)cv-autoref-operator(optional)[identifier-list]=expression;
attr(optional)cv-autoref-operator(optional)[identifier-list]{expression};
attr(optional)cv-autoref-operator(optional)[identifier-list](expression);

其中,attr(optional)为可选的attributescv-auto为可能有constvolatile修饰的autoref-operator(optional)为可选的&&&identifier-list为逗号分隔的标识符,expression为单个表达式。

另外再定义initializer= expression{ expression }( expression ),换言之上面三种语法有统一的形式attr(optional) cv-auto ref-operator(optional) [ identifier-list ] initializer;

整个语句是一个结构化绑定声明,标识符也称为结构化绑定(structured bindings),不过两处“binding”的词性不同。

顺带一提,C++20中volatile的许多用法都被废弃了。

行为

结构化绑定有三类行为,与上面的三种语法之间没有对应关系。

第一种情况,expression是数组,identifier-list的长度必须与数组长度相等。

第二种情况,对于expression的类型Estd::tuple_size是一个完整类型,则称E为类元组(tuple-like)类型。在STL中,std::arraystd::pairstd::tuple都是这样的类型。此时,identifier-list的长度必须与std::tuple_size::value相等,每个标识符的类型都通过std::tuple_element推导出(具体见后文),用成员get()get(e)初始化。显然,这些标准库设施是与语言核心绑定的。

第三种情况,E是非union类类型,绑定非静态数据成员。所有非静态数据成员都必须是public访问属性,全部在E中,或全部在E的一个基类中(即不能分散在多个类中)。identifier-list按照类中非静态数据成员的声明顺序绑定,数量相等。

应用

结构化绑定擅长处理纯数据类型,包括自定义类型与std::tuple等,给实例的每一个字段分配一个变量名:

#include

structPoint
{
doublex,y;
};

Pointmidpoint(constPoint&p1,constPoint&p2)
{
return{(p1.x+p2.x)/2,(p1.y+p2.y)/2};
}

intmain()
{
Pointp1{1,2};
Pointp2{3,4};
auto[x,y]=midpoint(p1,p2);
std::cout<< "(" << x << ", " << y << ")" << std::endl;
}

配合其他语法糖,现代C++代码可以很优雅:

#include
#include

intmain()
{
std::mapmap;
if(auto&&[iter,inserted]=map.insert({1,2});inserted)
std::cout<< "inserted successfully" << std::endl;
    for (auto&& [key, value] : map)
        std::cout << "[" << key << ", " << value << "]" << std::endl;
}

利用结构化绑定在类元组类型上的行为,我们可以改变数据类型的结构化绑定细节,包括类型转换、是否拷贝等:

#include
#include
#include

classTranscript{/*...*/};

classStudent
{
public:
constchar*name;
Transcriptscore;
std::stringgetName()const{returnname;}
constTranscript&getScore()const{returnscore;}
template
decltype(auto)get()const
{
ifconstexpr(I==0)
returngetName();
elseifconstexpr(I==1)
returngetScore();
else
static_assert(I< 2);
    }
};

namespace std
{
template<>
structtuple_size
:std::integral_constant{};

template<>
structtuple_element<0, Student>{usingtype=decltype(std::declval().getName());};

template<>
structtuple_element<1, Student>{usingtype=decltype(std::declval().getScore());};
}

intmain()
{
std::cout<< std::boolalpha;
    Student s{ "Jerry", {} };
    const auto& [name, score] = s;
    std::cout << name << std::endl;
    std::cout << (&score == &s.score) << std::endl;
}

Student是一个数据类型,有两个字段namescorename是一个C风格字符串,它大概是从C代码继承来的,我希望客户能用上C++风格的std::stringscore属于Transcript类型,表示学生的成绩单,这个结构比较大,我希望能传递const引用以避免不必要的拷贝。为此,我写明了三要素:std::tuple_sizestd::tuple_elementget。这种机制给了结构化绑定很强的灵活性。

细节

#include
#include
#include

intmain()
{
std::pairpair{1,2.0};
intnumber=3;
std::tupletuple(number);
constauto&[i,f]=pair;
//i=4;//error
constauto&[ri]=tuple;
ri=5;
}

如果结构化绑定i被声明为const auto&,对应的类型为int,那么它应该是个const int&吧?i = 4;出错了,看起来正是如此。但是如何解释ri = 5;是合法的呢?

这个问题需要系统地从头谈起。先引入一个名字eE为其类型:

expression是数组类型A,且ref-operator不存在时,Ecv A,每个元素由expression中的对应元素拷贝(= expression)或直接初始化({ expression }( expression )否则,相当于定义eattr cv-auto ref-operator e initializer;

也就是说,方括号前面的修饰符都是作用于e的,而不是那些新声明的变量。至于为什么第一条会独立出来,这是因为在标准C++中第二条的形式不能用于数组拷贝。

然后分三种情况讨论:

数组情形,ET的数组类型,则每个结构化绑定都是指向e数组中元素的左值;被引类型(referenced type)为T;——结构化绑定是左值,不是左值引用:int array[2]{ 1, 2 }; auto& [i, j] = array; static_assert(!std::is_reference_v);类元组情形,如果e是左值引用,则e是左值(lvalue),否则是消亡值(xvalue);记Tistd::tuple_element::type,则结构化绑定vi的类型是Ti的引用;当get返回左值引用时是左值引用,否则是右值引用;被引类型为Ti;——decltype对结构化绑定有特殊处理,产生被引类型,在类元组情形下结构化绑定的类型与被引类型是不同的;数据成员情形,与数组类似,设数据成员mi被声明为Ti类型,则结构化绑定的类型是指向cv Ti的左值(同样不是左值引用);被引类型为cv Ti

至此,我想“结构化绑定”的意义已经明确了:标识符总是绑定一个对象,该对象是另一个对象的成员(或数组元素),后者或是拷贝或是引用(引用不是对象,意会即可)。与引用类似,结构化绑定都是既有对象的别名(这个对象可能是隐式的);与引用不同,结构化绑定不一定是引用类型。

(不理解的话可以参考N465911.5节,尽管你很可能会更加看不懂……)

现在可以解释riconst的现象了:编译器先创建了变量const auto& e = tuple;Econst std::tuple&std::tuple_element<0, E>::typeint&std::get<0>(e)同样返回int&,故riint&类型。

在面向底层的C++编程中常用union和位域(bit field),结构化绑定支持这样的数据成员。如果类有union类型成员,它必须是命名的,绑定的标识符的类型为该union类型的左值;如果有未命名的union成员,则这个类不能用于结构化绑定。

C++中不存在位域的指针和引用,但结构化绑定可以是指向位域的左值:

#include

structBitField
{
intf1:4;
intf2:4;
intf3:4;
};

intmain()
{
BitFieldb{1,2,3};
auto&[f1,f2,f3]=b;
f2=4;
autoprint=[&]{std::cout<< b.f1 << " " << b.f2 << " " << b.f3 << std::endl; };
    print();
    f2 = 21;
    print();
}

程序输出:

143
153

f2的功能就像位域的引用一样,既能写回原值,又不会超出位域的范围。

还有一些语法细节,比如get的名字查找、std::tuple_size没有valueexplicit拷贝构造函数等,除非是深挖语法的language lawyer,在实际开发中不必纠结(上面这一堆已经可以算language lawyer了吧)。

局限

以上代码示例应该已经囊括了所有类型的结构化绑定应用,你能想象到的其他语法都是错的,包括但不限于:

std::initializer_list初始化;因为std::initializer_list的长度是动态的,但结构化绑定的标识符数量是静态的。用列表初始化——auto [x,y,z] = {1, "xyzzy"s, 3.14159};;这相当于声明了三个变量,但结构化绑定的意图在于绑定而非声明。不声明而直接绑定——[iter, success] = mymap.insert(value);;这相当于用std::tie,所以请继续用std::tie。另外,由[开始可能与attributes混淆,给编译器和编译器设计者带来压力。指明结构化绑定的修饰符——auto [& x, const y, const& z] = f();;同样是脱离了结构化绑定的意图。如果需要这样的功能,或者一个个定义变量,或者手动写上三要素。指明结构化绑定的类型——SomeClass [x, y] = f();auto [x, std::string y] = f();;第一种可用auto [x, y] = SomeClass{ f() };代替;第二种同上一条。显式忽略一个结构化绑定——auto [x, std::ignore, z] = f();;消除编译器警告是一个理由,但是auto [x, y, z] = f(); (void)y;亦可。这还涉及一些语言问题,请移步P0144R23.8节。标识符嵌套——std::tuple, T4> f(); auto [ w, [x, y], z ] = f();;多写一行吧。[同样可能与attributes混淆。

以上语法都没有纳入C++20标准,不过可能在将来成为C++语法的扩展。

延伸

C++17的新特性不是孤立的,与结构化绑定相关的有:

类模板参数推导(class template argument deduction,CTAD,由构造函数参数推导类模板参数;拷贝省略(copy elision),保证NRV(named return value)优化;constexprif,简化泛型代码,消除部分SFINAE;带初始化的条件分支语句:语法糖,使代码更加优雅。


声明:本文内容及配图由入驻作者撰写或者入驻合作网站授权转载。文章观点仅代表作者本人,不代表德赢Vwin官网 网立场。文章及其配图仅供工程师学习之用,如有内容侵权或者其他违规问题,请联系本站处理。 举报投诉
  • 代码
    +关注

    关注

    30

    文章

    4779

    浏览量

    68521
  • 编译器
    +关注

    关注

    1

    文章

    1623

    浏览量

    49108
  • 结构化
    +关注

    关注

    0

    文章

    27

    浏览量

    10308

原文标题:聊聊结构化绑定

文章出处:【微信号:CPP开发者,微信公众号:CPP开发者】欢迎添加关注!文章转载请注明出处。

收藏 人收藏

    评论

    相关推荐

    结构化布线系统有哪些难题

      在布线系统中,结构化布线也是非常重要的一环,这里本文给大家主要讲解了结构化布线系统的规划、安装以及投资成本等问题,希望对您有所帮助。  结构化布线系统规划  大多数电缆厂商为它们的产品规定了15
    发表于 05-19 13:46

    TrustZone结构化消息是什么?

    大家好,我已阅读任何与TrustZone相关的内容,但我无法弄清楚这两个世界是如何相互沟通的。我所能找到的只是TrustZone API规范中的内容:客户端和服务可以通过两种机制进行通信:结构化
    发表于 03-20 08:58

    Deeplearningai结构化机器学习项目

    Deeplearningai 结构化机器学习项目 Week2 6-10
    发表于 05-18 15:12

    请问如何借助SC Express减少结构化测试次数?

    如何借助SC Express减少结构化测试次数?
    发表于 05-11 06:46

    怎么实现基于结构化方法的无线传感器网络设计?

    怎么实现基于结构化方法的无线传感器网络设计?
    发表于 05-31 06:34

    结构化设计分为哪几部分?结构化设计的要求有哪些

    结构化设计分为哪几部分?结构化设计的要求有哪些?结构化设计主要包括哪些部分?
    发表于 12-23 06:15

    ISSP结构化ASIC解决方案

    ISSP结构化ASIC解决方案 结构化专用集成电路(structured ASIC)对设计工程师而言还是一个新名词,然而目前已经有多家公司正计划涉足这一领域。快速硅
    发表于 12-27 13:32 1256次阅读
    ISSP<b class='flag-5'>结构化</b>ASIC解决方案

    结构化布线的综合说明

    结构化布线的综合说明 一、结构化布线系统简介     随着计算机和通信技术的飞速发展,网络应用
    发表于 04-14 17:16 743次阅读

    什么叫结构化的算法_算法和结构化数据初识

    结构化算法是由一些基本结构顺序组成的,就是把一个大的功能的实现分隔为许多个小功能的实现。在基本结构之间不存在向前或向后的跳转,流程的转移只存在于一个基本的结构范围内。一个非
    发表于 01-03 16:09 1.2w次阅读
    什么叫<b class='flag-5'>结构化</b>的算法_算法和<b class='flag-5'>结构化</b>数据初识

    结构化布线系统的四点注意事项

    布线系统结构化 结构化布线 title=结构化布线结构化布线 title=结构化布线结构化布线系
    发表于 10-16 10:52 1236次阅读

    安防监控视频结构化那些事儿

    即便不考虑各个监控系统之间的信息关联,光浏览这些视频就需要花费大量的人力物力。解决这一问题的核心技术即视频结构化描述技术,将海量视频或图片的非结构化数据提取并转化为结构化信息描述。
    的头像 发表于 03-20 10:20 3242次阅读

    FPGA模块设计与AlteraHardCopy结构化ASIC

    本文档的主要内容详细介绍的是FPGA模块设计与AlteraHardCopy结构化ASIC。
    发表于 01-20 17:03 6次下载
    FPGA模块<b class='flag-5'>化</b>设计与AlteraHardCopy<b class='flag-5'>结构化</b>ASIC

    CFD 设计利器:结构化和非结构化网格的组合使用

    在CFD的发展历史中,结构化网格出现最早,至今仍在使用。结构化网格有几个主要优点,如精度高、生成速度快、单元分布均匀。有些工具擅长绘制这类网格,例如CadenceFidelityAutomesh
    的头像 发表于 12-23 08:12 1877次阅读
    CFD 设计利器:<b class='flag-5'>结构化</b>和非<b class='flag-5'>结构化</b>网格的组合使用

    结构化布线的好处多吗

    结构化布线是网络系统中的重要组成部分,因为它为数据传输提供了强大、可扩展且可靠的基础。通过遵守全球公认的标准,结构化布线可促进高速连接、简化故障排除并确保未来的可扩展性。考虑到这些优势,企业应优先
    的头像 发表于 04-07 11:15 426次阅读

    什么是结构化网络布线?结构化网络布线有哪些好处?

    在电缆领域,结构化网络布线这个术语经常被提及。人们将其用作流行语,但它的真正含义是什么?结构化布线到底是什么? 为了了解真正的含义,让我们看它的一些相关定义。 根据光纤协会的说法,结构化布线是由
    的头像 发表于 04-11 11:54 516次阅读