夏季 ISO C++ 标准会议报道

学术   2024-07-08 07:59   广东  
原文链接:
https://herbsutter.com/
    周六,ISO C++委员会在美国密苏里州圣路易斯市举行了第四次C++26会议。
    我们的主持人比尔·西摩为我们从周一到周六为期六天的会议安排了高质量的设施。我们有超过180名与会者,其中约三分之二是现场参会,其他人通过Zoom远程参会,正式代表了20多个国家。每次会议我们都会定期有从未参加过的新与会者,这次有近20名新的首次参会者,大部分是现场参会。再次欢迎他们所有人!
    我们也经常有新的国家加入,这次我们欢迎了首次正式代表哈萨克斯坦和印度的参与者。现在,我们有 29 个国家定期正式参与:奥地利、保加利亚、加拿大、中国、捷克共和国、丹麦、芬兰、法国、德国、冰岛、印度、爱尔兰、以色列、意大利、日本、哈萨克斯坦、韩国、荷兰、挪威、波兰、葡萄牙、罗马尼亚、俄罗斯、斯洛伐克、西班牙、瑞典、瑞士、英国和美国。
    这是周六会议结束后现场与会者的合影(有些人已经离开去赶飞机了)。感谢 John Spicer 拍摄这张照片。我们的主持人 Bill Seymour 坐在带黄点的前排。再次非常感谢 Bill 的邀请!
    该委员会目前有 23 个活跃的小组,其中 16 个小组在整个星期内同时开会。有些小组整周开会,有些小组则开会几天或一天中的某个部分和/或晚上,具体取决于他们的工作量。您可以在此处找到 ISO 程序的简要摘要。
    这次,委员会采用了 C++26 的下一组功能,并在其他功能方面取得了重大进展,这些功能现在预计将在 C+26 之前完成。
    三个主要特性取得了长足进展:
  • 用于并发和并行的 P2300 std::execution 正式被采纳并合并到 C++26 工作论文中
  • P2996 Reflection 已获得设计批准,目前正在进行针对 C++26 的规范措辞审查
  • P2900 Contracts 取得了长足进展,有机会成为 C++26 的一部分。
P2300 std::execution 正式被 C++26 采纳
    获准合并到 C++26 草案标准的主要特性是 Michał Dominiak、Georgy Evtushenko、Lewis Baker、Lucian Radu Teodorescu、Lee Howes、Kirk Shoop、Michael Garland、Eric Niebler 和 Bryce Adelstein Lelbach 编写的 P2300“std::execution”(https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2024/p2300r9.html)(又名“执行器”,又名“发送者/接收者”)。在之前的会议上,它已经通过了 C++26 的设计审批,但这是一篇很长的论文,因此库措辞小组 (LWG) 的规范措辞审查花费了额外的时间,随着问题的出现,该论文必须与 LEWG 一起迭代以澄清具体设计并进行调整。
    P2300 旨在支持并发性和并行性。我使用的定义:并发性意味着异步执行独立工作(例如,在不同的线程上,或使用协程),以便每个线程都可以响应并以自己的速度前进。并行性意味着使用更多硬件(内核、矢量单元、GPU)更快地执行单个计算,这是重新启用“免费午餐”的关键,即能够今天发布应用程序可执行文件,该可执行文件自然会在较新的硬件上运行得更快,并且将来会提供更多的计算吞吐量(其中大部分新吞吐量现在以更多并行性的形式发布)。
    对于并发性,回想一下,在 C++20 中我们已经添加了协程,但在初始状态下,它们更像是一个“编写协程的便携式工具箱”,而不是一个完全集成的功能(例如,我们不能仅使用标准中的内容来 co_await std::future)。从那时起,我们就知道我们想要在上面添加库以使协程更易于使用,包括 std::future 集成和 std::task 库,这些库仍在进行中。喜欢 std::execution 的一个重要原因是它与协程配合良好,并且是迄今为止使用我们已经拥有的协程支持的最大可用性改进。
[编辑以添加:] Eric Niebler 提供了三个我想在这里包括的示例和描述:
  • 示例 #1:1 个线程上的协作多任务处理(Godbolt)。这显示了嵌入式应用程序(例如)如何使用原始发送者在保证零分配的单个线程上进行协作式多任务处理。
  • 示例 2:使用协同程序的协作式多任务处理(Godbolt)。相同,但使用 P2300 协同程序支持和第三方任务类型。唯一的分配是针对协同程序框架本身。此示例利用了这样一个事实:可等待对象是隐式发送者,并且发送者可以在协同程序中等待。
  • 示例 3:多生产者多消费者任务系统(Godbolt)。启动用户指定数量的生产者 std::jthreads,这些线程将工作安排到系统执行上下文中,直到它们被要求停止。使用 async_scope 干净地关闭,并且所有生产者线程在 main() 返回时都隐式加入。
上述三个示例说明了 Eric 描述的几种技术:
    * 如何在只有一个线程且没有分配器的嵌入式系统上协同执行多任务
    * 如何实现多生产者、多消费者任务系统
    * 如何将 P2300 与协程一起使用
    * 如何编写自定义发送方算法
    * 如何将 P2300 组件与提供符合标准扩展的第三方库一起使用
    * 如何使用 P3149 中提出的 async_scope 生成可变数量的工作并等待所有工作完成
    * 如何使用 P2079 中提出的 ABI 稳定系统上下文来避免本地主机过度订阅
    Ville Voutilainen 报告编写了一个并发分块 HTTPS 下载,该下载与 C++20 协程的 co_await 和 Qt UI 进度条很好地集成,使用 P2300 的参考实现(加上预计将在 2019 年标准化的发送方感知任务类型)未来,但第三方的(如下面的 exec::task)现在也可以使用了),以及他自己的 Qt 适配代码(大约 180 行,最终将作为 Qt 的一部分发布)。代码很短,可以在这里展示:
exec::task<void> HttpDownloader::doFetchWithCoro(){ bytesDownloaded = 0; contentLength = 0; reportDownloadProgress(); req = QNetworkRequest(QUrl(QLatin1String("https://ftp.funet.fi/pub/Linux/kernel/v5.x/linux-5.19.tar.gz"))); QNetworkReply* reply = nam.head(req); co_await qObjectAsSender(reply, &QNetworkReply::finished); updateContentLength(reply, contentLength); contentLengthUpdated(contentLength); reply->deleteLater(); while (bytesDownloaded != contentLength) { req = setupRequest(req, bytesDownloaded, chunkSize); QNetworkReply* get_reply = nam.get(req); co_await qObjectAsSender(get_reply, &QNetworkReply::finished); updateBytesDownloaded(get_reply, bytesDownloaded); reportDownloadProgress(); get_reply->deleteLater(); }}
    有关并行性,请参阅 2022 年 12 月 HPC Wire 文章“新的 C++ 发送器库支持可移植异步”,作者为 Eric Niebler、Georgy Evtushenko 和 Jeff Larkin,其中描述了 std::execution 的跨平台并行性能。“跨平台”意味着跨不同的并行编程模型,使用分布式内存和共享内存,以及跨不同的计算机架构。(HT:感谢 Mark Hoemmen 和其他人提醒我们这篇文章。) P2300 的 NVIDIA 合著者报告称,并行性能与 CUDA 代码相当。
    Mikael Simberg 报告说,HPC 社区展示 P2300 的另一个并行性示例是 DLA_Future (GitHub),它实现了分布式 CPU/GPU 特征求解器。它可选地使用 std::execution 的参考实现,并计划在 C++26 标准库中发布后无条件使用 std::execution。在该 repo 中,一个高级示例是这个分布式 Cholesky 分解代码 (GitHub)(注意:它仍然使用最近被删除的 start_detached,并计划在可用时使用 async_scope)。
    另请参阅 P2300 本身以获取这两种技术的更多示例。
反射(Reflection)设计已获 C++26 批准
    语言演进工作组 (EWG) 批准了 Wyatt Childers、Peter Dimov、Dan Katz、Barry Revzin、Andrew Sutton、Faisal Vali 和 Daveed Vandevoorde 提出的反射提案 P2996R2“C++26 反射”的设计,并且现已开始语言规范措辞审查,目前正在为 C++26 进行审查。(更新以添加:库演进工作组 (LEWG) 仍在审查设计的库部分。)
    这很重要,因为反射(包括生成)将是自 C++98 以来 C++ 迄今为止添加的最具影响力的功能,它将主导未来十年及更长时间的 C++ 使用。这是分水岭事件;C++ 发生了翻天覆地的变化。我这样说有三个原因:
    首先,反射和生成是 C++ 有史以来改进库构建的最强大工具:它将使编写以前不可行或不可能的 C++ 库成为可能,并且它对编写库的影响可能比该语言从 C++11 到现在添加的所有其他库编写改进的总和还要大(例如,lambdas、auto、if constexpr、requires、类型特征)。
    其次,反射和生成将简化 C++ 语言的发展:它将减少向 C++ 添加尽可能多的未来一次性或“狭窄”语言扩展的需要,因为我们将能够使用反射和生成在普通的 C++ consteval 代码中将它们中的许多编写为编译时库。这本身将有助于减缓语言未来复杂性的增长。而且这已经发生了;近年来,SG7(负责编译时编程的小组)已经重定向了一些狭义语言提案,以探索如何使用反射来编写它们。
    第三,反射和生成是另一种可能大大简化我们编写 C++ 代码的方式的基础,即我的元类提案,它“只是”一个小型(但功能强大)的薄扩展,位于反射和生成之上……有关详细信息,请参阅本文末尾的 Coda。
仍以 C++26 时间框架为目标:契约
    我们花了整整四天的小组时间研究契约提案 P2900“C++ 契约”,该提案由 Joshua Berne、Timur Doumler、Andrzej Krzemieński、Gašper Ažman、Tom Honermann、Lisa Lippincott、Jens Maurer、Jason Merrill 和 Ville Voutilainen 提出:周一下午和周二在语言设计 (EWG) 上花了一天半的时间,周二在安全组 (SG23) 上进行了一次平行会议,周三和周四在契约子组 (SG21) 上花了两天时间,然后在周五午餐后回到 EWG 再花四分之一天时间进行另一场关于虚拟函数契约的会议。总之,我们解决了很多设计问题,并在其中几个问题上取得了良好的共识。我们还有进一步的工作要做,以便就其他未解决的设计问题达成共识,但共识正在逐渐改善,未解决的问题列表也逐渐变短……我们拭目以待!我谨慎乐观地认为,我们有 50% 的机会在 C++26 中获得合同,这意味着我们必须在未来 11 个月内解决剩余的分歧,以便在不晚于明年 6 月的最后期限之前满足 C++26 的功能冻结。
C++26 的采用:核心语言更改/功能
    以下是一些额外的亮点……请注意,这些链接指向每篇论文的最新公开版本,有些在会议上进行了调整,然后才获得批准;这些链接会跟踪更新的版本,并在其上传到公共网站后自动找到它。
    核心语言采用了 6 篇论文,包括以下内容……
    P0963R3“结构化绑定声明作为条件”,作者:Zhihao Yuan。这允许使用初始化器代替 if、while、for 和 switch 语句中的条件的结构化绑定声明,因此您可以更方便地分解,并且只有当返回的未分解对象求值为 true 时才执行分支。谢谢,Zhihao!
C++26 采用:标准库更改/功能
    除了上面已经介绍的 P2300 std::execution 之外,标准库还采用了其他 11 篇论文,包括以下内容……
    编号最低的论文获得批准,这意味着它已经“烘焙”了最长时间,是我们中的一些人期待已久的:P0843R11“inplace_vector”,作者:Gonzalo Brito Gadeschi、Timur Doumler、Nevin Liber 和 David Sankel。论文概述说明了一切——谢谢作者们!
    本文提出了 ,这是一种可动态调整大小的数组,其容量在编译时固定,并且是连续的就地存储,也就是说,数组元素存储在向量对象本身内。它的 API 与 非常相似,因此易于教授和学习,就地存储保证使其在不需要动态内存分配的环境中非常有用。inplace_vectorstd::vector<T, A
    此容器在 C++ 的标准实践中被广泛使用,现有技术包括 boost::static_vector<T, Capacity> 或 EASTL,因此我们认为将其作为 C++ 标准库的一部分公开将非常有用,这将使其可以用作词汇表类型。
    P3235R3“std::print more types faster with less memory”由 Victor Zverovich 撰写,我投票将其评为“论文标题中的最佳推销技巧”奖!如果您喜欢 std::print,那么它更强大、更快、更简洁(谁会不投票支持它呢?!),因为它扩展了之前在 P3107 中提供的优化的适用性,这些优化最初仅适用于内置类型和字符串类型,现在适用于更多标准库类型。感谢所有格式化的 I/O,Victor!
    Peter Sommerlad 的 P2968R2“使 std::ignore 成为一流对象”正式认可在赋值左侧使用 std::ignore。最初,std::ignore 仅用于 std::tie,但许多人注意到(并推荐和依赖)在每个实现中,您也可以使用它来忽略表达式的结果,只需编写 即可。甚至 C++ 核心指南的 ES.48“避免强制类型转换”也建议“使用 std::ignore = 忽略 [[nodiscard]] 值。”从 C++26 开始,该建议将从“已经在实践中发挥作用”升级为“正式合法”。谢谢 Peter!std::ignore = expression;
其他进展
    所有子组都在继续取得进展,毫无疑问,其他旅行报告中将涵盖更多进展。以下是更多亮点……
  • SG1(并发):讨论了 24 篇论文,并推进了“zap”系列论文。令许多人(包括我)高兴的是,concurrent_queue 终于接近完成!并发队列是标准库中严重缺失的基础并发原语之一,很高兴看到它越来越接近实现。
  • SG6(数值):包括数量和单位库在内的多个提案取得了更多进展。
  • SG7(编译时编程):向主要子组转发了另外六篇论文,其中大部分与反射有关。
  • SG9(范围):继续致力于 C++26 的范围扩展,进展良好。
  • SG15(工具):开始批准将生态系统论文发送给主要小组,例如元数据格式和对构建系统的支持。
  • SG23(安全):审查了几种不同的安全改进提案。小组投票赞成支持 Ryan McDougall、Jean-Francois Campeau、Christian Eltzschig、Mathias Kraus 和 Pez Zarifian 的 P3297“C++26 需要契约检查”。
    编辑以添加,为了完整性,其他演讲如下:首先,Bjarne Stroustrup 介绍了他的 Profiles 后续论文 P3274R0“Profiles 开发框架”。然后是 P3297,我之所以提到它,是因为它与其他小组就本周主要讨论的契约主题进行了沟通(见上文)。然后 Thomas Köppe 介绍了 P3232R0“用户定义的错误行为”。然后,Sean Baxter 做了一个信息演示(还没有论文),介绍了他在 Circle 编译器中探索向 C++ 添加借位检查的工作。
    感谢所有小组专家整周工作,本周取得了如此多的成就!
下一步是什么
    我们的下一次会议将于 11 月在波兰弗罗茨瓦夫举行,由诺基亚主办。
    再次感谢本周会议现场和在线参加的 180 多位专家,以及通过国家机构参与标准化的更多专家!
    但我们不会放慢脚步……我们将继续举行小组 Zoom 会议,然后在短短几个月后,我们将再次亲自开会 + Zoom,继续为 C++26 添加功能。再次感谢阅读本文的每个人对 C++ 及其标准化的关注和支持。
尾声:从反射到元类函数和 P0707
    我之所以选择反射和生成作为我在 2017 年向委员会提出的 Cpp2 的第一个“主要”功能,以及一个主要的应用用例(以我的“元类”论文 P0707 的形式),是因为它是 Cpp2 中最大的简化来源,但它也是 Cpp2 中最危险的部分——它“与我们在 C++ 中所做的最不同”,所以我不确定委员会和社区是否会接受这个想法,而且它是“实施起来最危险的”,因为在 C++ 中从未尝试过使用编译时函数来帮助生成类代码。
    我最初版本的 P0707 大部分内容都是在恳求‘这就是为什么委员会应该给我们反思和生成’。当我在 2017 年多伦多会议上反思报告之后立即向委员会介绍它时,我是这样开始我的演讲的:“嗨,我是 Herb,我是他们的客户”,指着反思报告者,“因为这是关于我们可以在反思的基础上构建什么的。”这仍然是正确的;自 2019 年以来我没有更新 P0707 的主要原因是因为我不需要……反思工作需要先存在,而且它一直在不断进步。
    历史记录:Andrew Sutton 的 Lock3 反射实现是为我的项目创建并资助的,该项目现在称为 Cpp2 ,但当时称为 Cppx 并使用基于 Lock3 Clang 的反射实现;这就是为什么 Lock3 实现可以在 cppx.godbolt.org 上获得(再次感谢 Matt!你真是个摇滚明星)。C++20 consteval 也直接源于这项工作,因为我们意识到我们需要一些必须在编译时运行的函数来处理静态反射和生成。
    既然反射即将成为标准,我计划更新我的 P0707 论文,以完成为 ISO C++ 提出元类。P0707 元类(又名类型元函数)实际上只是 P2996 之上的一层薄薄的层。要了解原因,请考虑以下简单代码:
// Example 1: Possible with P2996consteval { generate_a_widget_class(); } // let’s say that executing this consteval function generates something like: // class widget { void f() { std::cout << "hello"; } };
    使用 P2996,示例 1 可以编写一个名为 generate_a_widget_class 的 consteval 函数,该函数可以在编译时调用并生成(又称为注入)该小部件类型,就好像它是由程序员在源代码中的同一位置手写的一样。
    接下来,让我们稍微概括一下这个例子,给它一个现有类型的反射作为一些输入来指导生成的内容:
// Example 2: Possible with P2996 (^ is the reflection operator)class gadget { /*...*/ }; // written by the programmer by handconsteval{ M( ^gadget ); } // generates widget from the reflection of gadget // now this generates something like: // class widget { /* some body based on the reflection of what’s in gadget */ };
    仍然只使用 P2996,示例 2 就可以编写一个名为 M 的 consteval 函数,它将生成 widget 类,就好像是程序员在源代码中手写的一样,但能够引用 ^gadget… 例如,widget 可能会回显与 gadget 相同的部分或全部成员函数和成员变量,并添加其他内容。
    就这样,我们突然来到了 P0707 元类,因为 Cpp2(然后是 Cppx)元类的 Lock3 实现所做的就是“打包示例 2”,通过提供将 consteval 函数 M 应用于所定义类型的语法:
// Example 3: class(M) means to apply M to the class being defined// (not yet legal C++)class(M) widget{ /*...*/ }; // this proposed language feature would emit something like the following: // namespace __prototype { class widget { /*...*/ }; } // consteval{ M( ^__prototype::widget ); } // generates widget from __prototype::widget
    历史注释:我最初的提案 P0707R0 提出了语法“M 类”(例如,接口类、值类),而 SG7 小组反馈说,它更喜欢“类(M)”(例如,类(接口)、类(值))以简化解析。我对此很满意;语法不太重要,重要的是获得表达能力。
    因此,我对 P0707 的下一次修订计划是提出标准 C++ 的类(M)语法,作为 P2996 反射之上的进一步扩展,就像上面的示例 3 一样实现(Lock3 自 2017 年以来已经这样做了,所以我们知道它是有效的)。
为什么这对简化 C++ 如此重要?
    首先,正如我在 P0707 中展示的那样,这意味着我们可以使类更容易、更安全地编写,而不会出现错误的默认值和容易出错的样板代码。我们可以停止教授“0/1/3/5 规则”,停止教授 =delete 来摆脱我们不需要的生成特殊函数,因为在使用元函数编写类时,我们总是使用一个方便的词来选择我们正在编写的类型的一组默认值,并且可以得到我们想要的那个。
    其次,我们可以编写 Java/C# 风格的“class(interface)”,而无需将特殊的“接口”功能作为单独的类型类别添加到语言中,并且其效率和可用性与将接口嵌入语言的语言一样好。我们可以添加“class(value)”来调用在编译时运行的 C++ 函数,以便在没有将新的语言功能硬连线到编译器的情况下为值类型获取正确的默认值。我们可以添加 class(safe_union) 和 class(flag_enum) 等等。
    第三,正如我在 P0707 中所表达的那样,我希望反射+生成+元函数可以取代 Qt MOC、COM IDL、C++/CLI 扩展、C++/CX IDL,所有这些存在的主要原因是我们无法用标准 C++ 表达现在可以使用此功能表达的内容。我负责其中一些非标准技术;我领导了 C++/CLI 和 C++/CX 的设计,我对反射+生成+元函数的目标之一是,我希望我永远不需要再设计这样的语言扩展,而是能够在正常的(未来)标准 C++ 中很好地表达它们。而且我并不孤单;我的理解是,一旦反射和生成在 C++ 编译器中广泛可用,许多拥有上述技术的供应商已经热切地计划负责任地将此类当前附加技术过渡到基于标准反射和生成的实现。
    如果您对元函数如何工作的更多示例感兴趣,我强烈建议您重新观看两个演讲的部分内容:
  • 我的 CppCon 2023 演讲的中间部分,从 18:50 开始到 44:00,其中展示了许多今天使用我的 cppfront 编译器将它们转换为常规 C++ 的示例,包括“枚举”和“联合”元类函数的详细演练(使用 Cpp2 语法,但它们的工作效果与上面描述的当今 C++ 语法的微小扩展一样好)。
  • 我的原始 CppCon 2017 演讲从几分钟开始(该演讲的早期版本最初在 ACCU 2017 上首次亮相),演示了该方法并展示了在早期 Lock3 反射实现上运行的前几个示例。语法略有变化,但整个演讲在 2024 年仍然非常流行,因为它所依赖的反射和生成现在正在(终于!)进入标准。
    我期待着最终恢复更新 P0707,提议添加它描述的其余表达能力,作为当今 C++ 标准语法的扩展,建立在标准 C++26(我们希望!)反射和生成之上。我希望在 11 月的下一次会议上提出更新后的提案。我的第一步是尝试用 P2996 语法编写 P0707 元函数,以验证所有内容都已存在并按预期工作。到目前为止,我所知道的唯一一个我必须提议添加到今天的 P2996 上的额外反射支持是 is_default_accessiblity()(与 is_public() 等一起),用于查询成员是否具有类的“默认”可访问性,即在任何 public: 或 protected: 或 private: 访问说明符之前写入;这是“接口”等元函数所需要的,它们想要应用默认值,例如默认情况下使函数公开和纯虚拟,而无需用户编写 public: 或 virtual 或 =0。
    安全非常重要,我们也会为此努力。但如果不强调,反射(包括生成)的到来是一场翻天覆地的变化,它将推动我们未来十年及更长时间的 C++ 发展……这真的是一件大事,一股水涨船高,将提升所有其他船只,包括安全性和简单性。很快,我们将在很多年内无法参加一个没有大量使用反射的 C++ 会议……我这么说并不是因为我知道两个月后的 CppCon 2024 已经接受了几次反射演讲;这确实会在整个行业中被广泛讨论和使用,因为这个强大的功能有很多好处值得学习和使用。

控制工程研习
好好学习,天天向上
 最新文章