C++ 2006–2020

 主页   资讯   文章   代码   电子书 

3. C++ 标准委员会

国际 C++ 标准委员会正式名称为 ISO/IEC JTC1/SC2/WG21,它是 C++ 发展的核心。自 1991 年成立以来,就一直如此。而从 1989 年开始到它成立之前,C++ 开发的中心则是美国国家标准学会(ANSI)的 C++ 标准委员会 [Stroustrup 1993]。C++ 没有腰缠万贯的所有者,也没有其他重要的资金提供来源,因此社区依赖于企业开发和开源项目。对于很多处于相互竞争的组织中的人来说,WG21 和各个国家的标准委员会是他们能够会面并联合解决问题的唯一场合。

委员会成员都是志愿者,也没有带薪的秘书处,虽然许多委员确实以其工作组织的代表身份出现。在每次会议上,都会有人自豪地声称就代表“自己”。也就是说,他们没有得到赞助,只代表自己。有些人换工作后,就会代表新组织,这种情况并不少见。许多人以“参加 C++ 标准委员会”作为接受新工作的条件。有人加入了委员会来学习 C++,有人则把“C++ 委员会成员”当作资格来引用(并非一定是真的)。

有些人仅参加过几次会议,不那么经常。另一方面,也有人一直参加了大多数会议,数十年没有间断。一开始时,还有现在,我们一年开三次会。在 1998 年标准之后的几年里,我们一年只开两次会。目前,除了面对面的会议,还有很多的电话会议进行补充,以及天天都有的大量电子邮件。

在这里,我会描述

  • 标准的作用(§3.1
  • 委员会的组织(§3.2
  • 委员会的结构对 C++ 设计的影响(§3.3

3.1 标准

标准委员会的目的是编写标准。制定标准的一个官方理由是“促进贸易,特别是减少国际贸易的技术壁垒和人为障碍”,及“提供实现经济、效率和互操作性的框架”。标准是规范,不是实现。它的目的是保持多个实现一致,并确定“一致性”在一个必须能够有效利用各种不同底层硬件的世界里到底意味着什么。许多程序员在理解这一点上存在问题。他们要么认为当前的编译器就是语言的定义,要么难以理解为什么很难在许多不同的、通常是竞争性的组织之间达成 100% 的协议。在 1990 年代,委员会考虑过制定形式规约(formal specification),但咨询过世界一流的专家后得出结论,规约技术的进展和成员的水平都还达不到制定 C++ 形式规约的程度。当然,也考虑过参考实现,但语言的复杂度,特别是与硬件使用和优化相关的问题,已经挫败了这种想法。如果有参考实现,它会太复杂,也会代价过大。要么就得把它简化到一种对最困难的问题没有帮助的程度,但这样的困难问题正是最需要参考实现的场合。再有,当 N 个彼此竞争的实现团队记录他们的决策、运行广泛的合规性测试并讨论它们的不同之处时,会有些意外收获;如果已经有了个复杂的参考实现,就可能掩盖掉这样的意外收获。对于 C++,从前端实现来说(Clang、EDG、GCC 和微软)N 至少为 4,至于后端,N 少说有十几个。

因此,标准委员会正在努力解决拥有多种实现带来的问题。另一条路是冒险搞单一文化。如果 C++ 技术只来源于一个组织,那么无论好坏,每个人都会得到相同的东西。一个控制“唯一真正实现”的组织将在社区中拥有主导话语权,他们出现的问题,就会影响到所有人。特别是,一旦有资金问题、商业顾虑、政治见解或者技术上的一意孤行,就会严重破坏语言及其用户群体。

无论好坏,C++ 社区选择了“半组织”的混乱,里面有一个很大的委员会加上多个编译器、工具及库的供应者。我们没有用统一所有权或独裁者模式。

3.2 组织

对于 C++17 和 C++ 20 的工作,每次面对面的 WG21 会议有多达 250 人出席,而总成员人数约为出席人数的两倍。此外,加拿大、芬兰、法国、德国、俄罗斯、西班牙、英国、美国等十几个国家都有国家标准委员会以及 C++ 标准技术联盟的付费支持成员。成员代表了一百多个组织。为了让大家有所了解,在此列举部分成员所属组织:苹果、Bloomberg、欧洲核子研究中心、Codeplay、EDG(Edison Design Group)、Facebook、谷歌、IBM、英特尔、微软、摩根士丹利、英伟达、Qt、高通、红帽、Ripple、美国 Sandia 国家实验室、拉珀斯维尔应用科技大学(HSR)和马德里卡洛斯三世大学。编译器供应者、硬件供应者、金融、游戏、库供应者、平台供应者、国家实验室(物理)等都有坚实的代表。早期 C++ 中突出的电信业者的身影已经减少,而过去极少的大学的身影似乎在增加。

显然,如此庞大的组织和个人组成的群体代表着千差万别的兴趣和技术背景,需要一个组织结构来运作。会议是围绕工作组(WG)和研究组(SG)进行组织的。2019 年的夏天,我们已经有了这样一些分组:

  • 核心工作组(Core WG 或 CWG)——编写语言的最终标准文本——主席 Michael Miller(EDG)。
  • 库工作组(Library WG 或 LWG)——为标准库编写最终标准文本——主席 Marshall Clow(C++ 联盟,之前代表高通)。
  • 演化工作组(Evolution WG 或 EWG)——处理语言建议——主席 Ville Voutilainen(Qt,之前代表 Symbio)。
  • 库演化工作组(Library Evolution WG 或 LEWG)——处理标准库提案——主席 Titus Winters(谷歌)。

研究组探索新领域并设计可能的标准化:

  • SG1 并发——并发和并行性主题——主席 Olivier Giroux(英伟达)。
  • SG5 事务性存储——探索事务性存储的构造——主席 Michael Wong(Codeplay,之前代表 IBM)。
  • SG6 数值——包括但不限于定点数、浮点数和分数——主席 Lawrence Crowl(“自己”,之前代表谷歌和 Sun)。
  • SG7 编译期编程——最初专注于编译期反射,然后扩展到一般的编译期编程——主席 Chandler Carruth(谷歌)。
  • SG12 未定义的行为和漏洞——系统地审查漏洞和未定义/未指定的行为——主席 Gabriel Dos Reis(微软,之前代表得州农工大学)。
  • SG13 人机界面和 I/O——精选的底层输出(例如图形、音频)和输入(例如键盘、指点设备)的 I/O 原语——主席 Roger Orr(英国标准(BSI))。
  • SG14 游戏开发和低延迟——游戏开发者和其他有低延迟要求的人感兴趣的主题——主席 Michael Wong(Codeplay,之前代表 IBM)。
  • SG15 工具——与针对标准 C++ 的开发者工具创建有关的主题,其中包括但不仅限于模块和包管理——主席 Titus Winters(谷歌)。
  • SG16 Unicode——与 C++ 中的 Unicode 文本处理相关的主题——主席 Tom Honermann(Synopsis)。
  • SG19 机器学习——主席 Michael Wong(CodePlay,之前代表 IBM)
  • SG20 教育——探索可以支持学习者和教师掌握今天的 C++ 的方法——主席 Jan Christiaan van Winkel(谷歌)
  • SG21 契约——在 C++20 失败后尝试设计出契约系统(§9.6.1)——主席 John Spicer(EDG)

2017 年成立了一个小组来解决与语言和标准库设计缺乏方向有关的问题 [Dawes et al. 2018]。该方向组(DG)的成员由召集人与工作组主席协商后任命,其成员是委员会、语言和标准库的长期贡献者。最初的成员是 Beman Dawes、Howard Hinnant、Bjarne Stroustrup、David Vandevoorde 和 Michael Wong。之后,Beman 退休,Roger Orr 加入。DG 的主席是轮流担任的,从我开始。DG 是咨询机构,其政策是只有在其成员一致同意的情况下才能提出意见。它维护有一份描述其建议的文档 [Dawes et al. 2018; Hinnant et al. 2019]。

工作组可持续十年以上,成员变化也很少。研究组则聚散自由,可能因兴趣使然,或者因为工作已经完成并提交给工作组进行最后的处理。例如,四个最重要的研究组已宣告胜利完成并解散:

  • SG2 模块——主席 Gabriel Dos Reis(微软,之前代表得州农工大学)。
  • SG3 文件系统——主席 Beman Dawes(“自己”)。
  • SG8 概念——主席 Andrew Sutton(俄亥俄州阿克伦大学,之前代表得州农工大学)。
  • SG9 范围——更新 STL,以使用概念,简化表示法,及提供无限序列和管道——主席,Eric Niebler(Facebook)。

SG4 网络,目前处于休眠状态,因为其结果正在等待被合并到标准(§8.8.1)中。另一个研究组 SG11 数据库,因缺乏共识和缺乏足够数量的志愿者完成工作而解散。

某些研究组会产出技术规范(TS),这些技术规范可能是具有重要意义的文件,也以标准本身的风格写就。它们具有一定的官方(ISO)地位,但不能提供国际标准(IS)所具有的长期稳定性。并发研究组(SG1)自 2006 年以来一直活跃,大部分时间由 Hans-J. Boehm(谷歌,之前代表过惠普实验室和 SGI)领导,它的地位已经接近 WG 了。

除了这些分组外,还有一个半官方的 C/C++ 联络组,由同时加入 C++ 委员会和 C 委员会(ISO/SC22/WG14)的成员组成。这个小组力图减少 C 和 C++ 之间的不兼容性,而 C++ 标准也会把每种不兼容之处记录下来。如果没有联络小组的不断努力,C 和 C++ 的兼容性远没有现在好。不过,即便如此,大多数从 C++ 导入 C 的特性都被修改过,而这就引入了一些不兼容性。

ISO 只需要也只认可三名正式官员:

  • 召集人——担任工作组主席,制定工作组会议时间表(召开会议),任命研究组,并向更高级别的 ISO(SC22、JTC1 和 ITTF)负责——Herb Sutter(微软),自 2002 年以来一直担任该职位的工作,除 2008–2009 年期间是由 P.J. Plauger(Dinkumware)担任。
  • 项目编辑——最终负责将委员会批准的更改应用于标准的工作草案——Richard Smith(谷歌);Pete Becker(Dinkumware)负责 C++11;Stefanus Du Toit(Intel)负责 C++14。
  • 书记——负责记录和分发 WG21 会议的会议纪要——Nina Ranns(Edison Design Group,之前代表 Symantec)。

各个国家的标准委员会有各自自己的官员和章程。

显然,这些年来这些职位由不同的人担任过,但尽管工作量通常很大,很少有人在职少于 5 年。我曾担任 EWG 的主席 24 年,到 2014 年才把这一职位移交给 Ville Voutilainen。

通常,较小的提案直接提交给 EWG 和/或 LEWG,较大的提案则从研究组开始。提案需要以书面形式提出,并有人进行演示。一般来说,处理一项重要的提案需要数次会议(通常为数年),并且需要数篇论文、修订论文和反复演示。最后,已经获得大力支持的提案将提交给整个委员会进行最终表决。召集人查看表决结果并裁定是否达成共识。共识不只是要多数。委员会更倾向于能在经过工作组处理和投票后获得一致同意,如果达不到,通常至少也需要 9 比 1 或 8 比 2 的优势。召集人很可能会认为 8 比 2 的多数票“未达成共识”。如果国家标准机构的负责人或几个主要委员表示强烈反对,就会发生这种情况。这样议题就会处于悬而未决的状态或者导致提案只被部分采纳。

标准会议令人筋疲力尽。通常,委员从早餐到午夜一直在讨论工作问题。大多数时候,正式会议在 8:30–12:30 和 14:00–17:30 举行,加上大多数时候都会进行的晚间会议(19:00–22:00)。正在准备提案的委员的工作时间比这些还要长。WG 和 SG 主席一般在大多数用餐时间都在开会。周一至周五是全天,而如果没有任何意外发生,大多数委员会成员到了星期六的 15:00 左右会收工。不过,当会议在诸如夏威夷科纳(Kona)之类的好地方举行时,委员会以外的人似乎都不愿意相信开会并不是什么度假。

在 WG 和 SG 里,每个出席者都可以投一票。委员会全体会议的正式投票则是每个到会的组织一票(这样,大型组织就不会有多票),再加上国家标准机构的票数。“技术性投票”和国家机构投票必须一致才算达成共识。

委员会 2006 年以前的历史记录在 [Stroustrup 1993, 1994, 2007] 中。C++ 基金会(§10.2)在其网站(isocpp.org/std)上维护了一份会及时更新的描述,涵盖组织、关键人物和委员会流程。

从 1989 年起,委员会的所有论文几乎都可以从一份文集中获取到 [WG21 1989–2020]。目前,该文集每年增加 500 多篇论文。另外,很多委员会的讨论是在已归档的邮件列表中进行的。每天可能有超过一百条邮件消息。要跟上委员会中发生的所有事情非常难,特别是由于很多事情需要专门的技术知识才能跟进。我将自己的 WG21 论文集保存在主页 [Stroustrup 1990–2020] 上。

传统上,ISO 标准每十年左右修订一次。例如,我们有 C89、C99 和 C11。如此长的修订周期是有问题的,如果新特性错过了特性冻结,我们就会要再等上 12 年左右才能将它加入标准。人们自然就会主张将即将通过的标准拖延一两年:“这个特性太重要了,不能等,因此得延迟一下标准的发布!”这就是为什么原本的 C++0x 结果成了 C++11,在 C++98 后过了 13 年。

在 C++11 之后,一些委员会成员希望缩短周期,召集人 Herb Sutter 建议我们采用列车模型。也就是说,列车在预定时间出发,任何没上车的人将不得不等待下一班。大家喜欢这个建议,也花了挺长时间讨论标准修订之间的合适间隔。我主张短点,3 年,因为再长(例如 5 年)就容易被“这个特性非常重要,等不了”这样的说法拖累,导致发布延迟。我们商定了三年的发布周期,Herb Sutter 补充建议采用交替发行大版本和小版本的英特尔“滴答”模型。这也得到了同意,因此在 C++11(§4)三年后,我们发布了 C++14(§5),它纳入了之前被延迟的特性并纠正了早期使用中发现的小问题。C++17 也按时交付,但可惜并不是一次大升级(§8)。C++20 在 2019 年 2 月通过投票,确定了完整的发布特性。最终技术性投票于 2020 年 2 月在布拉格完成。

3.3 对设计的影响

这样的工作组织方式、复杂的决策流程以及大量的参与者会如何影响 C++ 的发展?看看委员会的规模、组成及其流程,我认为,任何建设性成果居然能从中产生,都足以令人惊喜。这已经不只是“委员会的设计”了,而是“多委员会的联合设计”。

此外,委员会的管理结构非常薄弱,甚至缺乏最基本的管理工具:

  • 成员资格、发言或投票没有任何资质要求(例如,学历或实际经验)。支付 ISO 会员费(2018 年美国会员为 1280 美元)并参加两次会议,就能拥有正式投票权。在研究组和工作组中,任何人都可以发言与投票,即使这是他们的第一次参加会议。
  • 除了让提案得到采纳,以及看到改进后的标准而感到满足,并没有任何其他回报。不过,满足感确实是一个主要动力。
  • 没有真正的办法来阻止破坏性行为。非官方委员会管理人员所能做的只是有礼貌地提醒人们不要做别人认为具有破坏性的事情。然而委员们对于什么是有破坏性的,意见也不一致。

当考虑在一个大型委员会里演化一门语言的各种问题之前,请记住委员会里大部分时间和工作都是为了解决“小问题”;就是那些不会上升到语言设计哲学、学术出版物、或会议演示层面的问题。它们对于防止语言及其标准库被分割成方言,并保证在编译器和平台之间的可移植性至关重要。这些问题包括:命名、名称查找、重载决策、语法细节、构造的确切含义、临时变量的生存周期、链接,还有其他很多很多。许多问题需要技巧才能得以解决,而拙劣的解决方案可能带来让人吃惊而具有破坏性的后果。解决方案往往经过精心设计,以最大程度减少对现有代码的破坏。委员会每年解决数百个问题。我估计委员至少要为此花费他们时间和精力的三分之一,乃至于三分之二。这项工作往往被忽视和低估。如果你用过计算机或计算机化的设备(例如电话或汽车),你得感谢 CWG 和 LWG 的工作。

当关注由一个庞大的委员会引起的问题时,也请记住,这些问题本质是一种有钱人的烦恼:C++ 的标准化流程由数百位各种不同背景的热心人士所驱动,他们的经验各不相同,但都满怀理想主义。

委员会应起到过滤作用,也就是说,把坏提案挡在标准之外,同时,还要提升最后通过的提案的品质。委员会的存在,是要鼓励大家提出建议,并主动提供帮助。然而,并没有正式的提案征求流程。

不存在全职的 C++ 设计者,尽管有许多全职人员从事 C++ 编译器、库和工具方面的工作。直到最近,委员会中还很少有人从事应用程序开发,这是一个问题,因为它使委员会偏向于语言“律师”、高阶特性和实现问题,而不是直接解决大量 C++ 开发者的需求——很多成员只是间接地了解这些需求。最近新成员急剧增加,也许会部分缓解这个问题。

委员会中的教育工作者相对较少。这可能是个问题,因为委员会(理所当然地)高度重视“易于学习”,但是委员们对其含义有着非常不同的理念(经常会意见强烈)。这往往使关于“简单性”和“易用性”的讨论变得混乱。

当思考组织问题对 C++ 发展的影响时,请记住,ISO 流程本不是为 200 人的会议而设计的——典型的 ISO 编程语言委员会只有一二十人。平均而言,我们在某种程度上是通过识别和解决问题来进行管理。考虑下面这些观察到的问题:

  • 延迟:多阶段的流程为延迟、阻止提案和提案变化提供了很多机会。常常出现几十名委员坚持要满足他们的要求的情况,往往是通过阐释、扩展和寻找特例的方式。一个人眼中的过度延迟在另一个人看来却是尽职尽力。

    例如:概念(当前方案为 6 年(§6))、契约(从开始到失败花了 6 年(§9.6.1))、网络(15 年,仍在进行中(§8.8.1))和 constexpr(5 年(§4.2.7))。甚至 nullptr 被接受也花费了三年时间(§4.2.6)。

  • 孤立特性:大多数委员会成员喜欢特性的添加。另一方面,他们(非常合理地)深刻地担心破坏现有代码的可能性。这给了孤立特性系统性的优势,孤立特性是不影响语言和标准库其余部分的小提案。这样的小提案很少会对语言的使用产生重大影响,但却会增加学习和实现的复杂性。而且,到头来,它们往往还是会和其他特性发生令人惊讶的交互。

    例如:大多数在本语言演化总结中不值得提及的特性。结构化绑定(§8.2)和运算符 <=>§8.8.4)都需要多次会议去完善。

  • 后来者居上:有时经过多年的工作之后,提案已接近投票表决,一些一向未曾关注提案的委员此时进入讨论并提供了替代提案。这样的提案可能与原始提案有戏剧性的差异,或者只是一系列小的请求。这往往导致延迟、混乱、甚至有时是争执。这种时候,已经议定的问题又重被激活,而未经尝试(通常也未实现)的新想法和多年工作的成果获得了接近相等的权重。对老提案而言,瑕疵已经被发现过了,相应的技术折中也已经完成。人们很容易想象新事物的好处而忘记意外后果定律:意外后果总是会出现的。新的和相对未经审查的总是看起来比老的更好。这使得较早提案的拥护者变得具有防御性,从而分散了进一步完善“老提案”的精力。在这里“老”可能只是几年,或者就像概念(§6)那样十几年。有时,接受未经尝试的后期变更(所谓改进)是为了安抚反对派;这经常导致意外的后果。后期加入讨论的人们,通常不会认为有“冲刺的必要”,而是自然地希望他们自己的想法得到认真考虑(而通常并没有认真考虑老提案的细节和理由)。这就可能会与已经在老提案上投入多年工作的人们产生摩擦。

    例子:结构化绑定(语法更改,对位域的新增支持,笨拙的 get()§8.2))、概念(§6)。数字分隔符(§5.1)、点运算符(§8.8.2)、模块(§9.3.1)、协程(§9.3.2)、契约(§9.6.1)。

  • 热情总青睐新事物:唤起对新事物的热情比反对它们容易。每个提案都是为某人解决某事,支持者愿意花大量时间展现其价值。而要反对它们,有人就不得不说像这样的话:

    • “不,这个问题不是那么重要。”
    • “不,这种解决方案有缺陷。”
    • “不,你还没有充分记录解决方案。”
    • “不,你还没有仔细检查替代方案。”

    不管措辞怎么客气,这都让反对者看起来更像“坏人”,是他们阻碍了进步并否认支持者需求的合理性。更糟糕的是,拥护者总是比反对者花费更多的时间来准备论文和演讲。大多数人喜欢对自己相信的事物进行建设性的工作,而不是小心地拆除他人的工作。因此,支持者通常都很热情并且准备充分,而反对者总是显得意见含糊而不懂细节。然而,每项新特性都有其成本:如设计、规范、实现、修订、部署和教学(§9.5)。我害怕在演化工作组度过周四下午。那时,EWG 成员经过几天的大提案工作而感到疲倦,许多老成员(例如我)已经被拖入其他小组,参会者又急于看到有成果。这种时候,小提案就会只经受相对较少的审查而滑入标准。

    例如:条件中的显式测试(§8.7)、inline 变量(§8)、结构化绑定的后期更改(§8.2)。

  • 过度自信:相对于整个语言及标准库的复杂度,尤其是不同应用领域的 C++ 用户所面临的问题的复杂度,个人在日常工作中能获得的经验总是不足的。并非所有委员会成员都能意识到这一局限,或是能通过质疑自身经验的推广价值加以弥补。这就导致某些一般性有限的提案被过度推广。更糟糕的是,一些委员强烈反对某些提案,是因为他们不认为有必要解决该提案所针对的问题。语言设计需要一定的智力上的谦逊 [Stroustrup 2019b]。先想出来的解决方案很少是最好的,而未经进一步认真思考就提出轻率的反对意见和建议很少会带来改进。

    例子:出于对犯错者的保护,就不举例了。

  • 实现时机不当:在标准流程中,实现提案晚了有风险:特性出现严重缺陷、有潜在无法实现的部分、以及缺乏使用反馈;实现早了也有风险:特性以不完整的、次优的且难以使用的形式冻结。委员会中的许多人不会投票赞成尚未实现或以他们不信任的方式实现的提案。另一方面,许多实现者不愿意为委员会未批准的提案投入实现资源。这是一个困难而现实的两难困境。委员会经常听到“它已经实现了吗?”的问题。通常,“它是经过设计的吗?” 和“要如何使用?” 是更重要的问题。人们很容易在细节中迷失。我提出的走出这一困境的方法是,就建议的方向、提案的总体范围达成一致,然后从一个相对较小子集的详细设计和实现出发,以关键用例为指导前进。这样,我们可以相对较早地获得用户体验,并了解该特性如何与其他特性交互。这需要对这种语言应该是什么有一个长远的看法 [Stroustrup 1993, 1994, 2007](§1),(§11.2),否则语言就会沦为机会主义的零敲碎打。如果这个方法起作用,语言将从反馈和有机增长中受益。

    例子:模块(§9.3.1)、C++ 0x 概念(§6)和 <=>§8.8.4)。

  • 特性交互:最难处理的问题之一是特性的组合使用。一定程度上这是规范和实现的技术问题。因此,这会占用大量委员会时间。从设计的角度来看,更难的问题是要预计新特性在整个语言的语境中如何使用,这些语境包括其他正在考虑中的语言和库的新特性。每个特性都应设计成便于同其他特性结合使用。我担心这一点没有得到重视。很少有提案书提供详细的讨论,而委员会里关于特性交互的讨论往往简短或混乱。其结果之一是,个别特性趋于膨胀而只好把它孤立于语言的其余部分才能用起来。

    例子:tuple§4.3.4)和 <=>§9.3.4)。为 lambda 表达式(§4.3.1)中的动作指定专用语法的(失败)提案。

  • 篇幅和分心:千头万绪往往同时发生,没有人能全跟得上。那些尝试全部关注的人,就容易失去对真正重要课题的关注,而把注意力分散在一些事实证明并不那么重要的课题上。如今每年有超过 500 篇委员会论文,有些长达数十甚至数百页。与 2010 年代初相比,文献总篇幅翻了一番。我注意到,2018 年秋天的会前邮件(新论文汇总)的字数是莎士比亚全集的三倍。

    电子邮件的泛滥最让人分心,因为许多委员喜欢通过一波一波地爆发短邮件来进行技术讨论。在这样的讨论中掉队意味着失去对问题的跟踪,其结果可能是,共识只是从几个一直能跟得上讨论的人中间浮现。

    这种讨论不利于冷静而系统地权衡各种选择。有时候,它会导致不幸的特性滑入标准。有时候,它会导致不一致的设计理念体现于语言和标准库的不同部分,进而损害了互操作性。

    例子:anyoptionalvariant 的不同接口。概念(§6)。

  • 精确规范:标准是规范,而不是实现。但是,标准是用英语编写的,因此我们做不到数学般的精度。委员会的许多成员擅长数学,但不擅长数学的人更多,因此在规范中没办法使用数学记法。试图使英文文本精确而详尽,则会让文本变得生硬又难以理解。我常常很难理解标准中对我自己提案的描述。

    大多数委员是程序员,而不是设计师,因此规范有时看起来会像程序——用没有类型系统或编译器的低级语言写成的程序。有详尽的如果、那么、否则的说明,却很少写出不变量。更糟糕的是,很多词汇是继承自 C,而且是基于程序源代码文本中的标记,因此,更高级别的概念仅被间接提出。

    奇怪的是,标准库规范在结构上明显比语言规范更为正式。

  • 经院主义:当然有必要大力强调标准文本的正确性和准确性。但是,人们有时会忘记标准本身可能就是错误的,而仅根据标准文本的论证来讨论正确性。这样一来,根据标准文本所应反映的模型和使用上的论证,反倒可能被忽略。

  • 方向:哪些问题是真实的?重要吗?对于谁?哪些紧急?十年后,哪些解决方案仍然有意义?有些事情也许算个问题,但这并不意味着它必须在语言里有直接的解决方案。尤其是,委员会很难记住这一点:一种语言不可能对所有人来说都是万能的。更难以接受的是,它居然不能解决每个委员最紧急的问题 [Stroustrup 2018d]。

    例子:C++17(§8)和 C++ 20(§9)。

  • 专一关注:一些委员仅关注一个或两个课题,例如语言技术、易用性、“可教学性”、效率、使用单一编程风格、在单个行业中使用、在单个公司中使用、单个语言特性等。对于专一关注的委员而言,这可能是一种非常有效的技巧,但这样做会让广泛的、平衡的进展变得困难。过分相信理论或个人经验则是这个问题的另一类例子。一个好的建议在许多领域都会推动进步,但通常不能在所有这些方面都达到完美。

  • 原则的不适当应用:将一般原则应用于具体事例通常很困难。有时,我们会不与其他原则进行必要折中,就去严格应用某项原则。折中的必要性是《设计和演化》一书 [Stroustrup 1994] 将设计原则称为“经验法则”的原因之一。有时,似乎没有经验基础就凭空冒出来一个原则。有时,一个提案严格遵循了某一个原则,而另一个提案则忽略它。有原则的设计很困难;它需要品味、经验以及原则。实用的语言设计不只是从第一原理出发进行演绎的练习。通常,多种原则之间必须进行权衡。

  • 倾向专家的偏见:想象别人的问题总是困难的。委员会成员几乎都是某方面的专家。在日常工作中,他们通常是处理最细微、最复杂问题的人。这样的问题在“外面”的数十亿行常规 C++ 代码中一般不常见,而且也不是大多数 C++ 程序员所苦恼的问题。但是,对委员会来说,专家级的问题通常就是紧急问题,也是最容易通过流程的问题。

    例子:支持 enable_if 和类型特征(§4.5.1)在标准库中的使用简直水到渠成,但接受概念(§6)却大费周章。

  • 聪明的问题:委员会成员一般是聪明人,他们中许多人无法抵御机灵的解决方案。此外,他们也很难断定,并非每个问题都值得解决,而拥有解决方案也并不意味着我们必须将其纳入标准。这会带来过于精巧的特性,带来大多数程序员用不着的特性。公平起见,也需要指出,许多程序员也很聪明,有时也会以使用过分机灵的语言和标准库特性为乐。

    例子:在有些提案中,即使简单用法也需要用上严肃的模板元编程。

  • 不愿妥协:大多数委员会成员都有强烈的意见,但要在一个大型团体中达成共识需要妥协。分辨哪些妥协无关紧要,而哪些妥协事关基本原则,有时会很困难。后一类妥协可能对语言造成破坏,应该避免。不幸的是,当委员们坚信自己所担忧的才至关重要时,他们比起心态开放的委员就有了有关键的战术优势。有些人能做到从整体上关注语言而不纠结于个别话题,但他们往往得向不能如此的人们屈服。而反过来,那些从不认真质疑自己的原则或需求的人,倒往往可以向别人视为必要的技术妥协发动猛攻。取得进展需要关注整个社区,有自知之明,并懂得适当的谦逊 [Stroustrup 2019b]。

  • 缺乏优先级:从技术的角度来看,所有问题都是平等的:不精确的规范就是不精确规范,这一点与它未能正确规定的内容是什么不相干。任何可能从类型系统的漏洞中混进代码的错误原则上都可能造成死亡和毁灭。但是,现实世界中不同错误的影响可能大不相同。实际上,大多数晦涩的细节基本上没有破环性。有些人在研究设计细节时很难记住这一点。

    例子:在数字分隔符(§5.1)上花费的时间比在范围 for§4.2.2)上花费的时间更多。

  • 完美主义:一个标准预期会被几百万人用到,并且可以稳定数十年。人们自然希望它是完美的。这会导致特性膨胀(特性过多),尤其是导致单个特性的膨胀。程序员善于想象出问题,特性在委员会走流程的时候,委员们会坚持要它解决掉所有想象中的问题。这会导致严重的使命偏离,并导致只有专家才会喜爱的特性。这也可能导致特性一直无法加入标准。

    例子:. 运算符(§8.8.2)、网络库(§8.8.1)和异常规约(§4.5.3)。

  • 少数人的阻挠:共识流程可以防止某些类型的错误,尤其是防止多数人的暴政。但是,它很容易受到个人和小团体的阻挠。这可以是好事(避免错误),但是当它在提案流程的各个阶段一再发生,或正好在最后一刻发生时,就会具有破坏性了。

    例子:constexpr§4.2.7)、. 运算符(§8.8.2)、模块(§9.3.1)和协程(§9.3.2)。

  • 内聚的团体:许多工作组和研究组都拥有稳定的核心人员群体,这些年来他们形成了内聚的技术观、共享的词汇表和特定的运作方式。这会使“外部人员”难以交流和贡献。这也可能使设计跨越 WG 边界的特性(例如同时具有库和语言部分的特性)变得困难。每个小组都往往会设计出适合其自身组织结构领域的内容,再次印证了老格言,即系统的结构总是长得像创造它的组织的结构。

    例子:范围 for§4.2.2)和可能需要更改语言的并发机制(§4.1.3)。anyoptionalvariant§8.3)的接口差异。

从积极的一面来看,基于个人敌意或针锋相对的行为非常罕见。从这个意义上讲,委员会是非常专业的。

幸运的是,并非每个提案都受所有这些现象的影响,并且大多数其他大型项目也会遇到这类问题。但是,以其 ISO 标准所代表的 C++ 语言,整体上反映出了这些现象。它们不是新问题,但是自 C++11 起出现得越来越多。我怀疑它们是由以下因素共同造成的

  • 委员会人数增加
  • 新人的涌入
  • 成员的专业化(分散化)
  • 成员对 C++ 历史的了解有所减少

尽管存在这些严重的问题,但标准制定流程仍屡屡成功,原因之一是很多人不断努力将负面影响降到最低。方向组(Direction Group)的建立就是这方面的努力的一部分(§3.2)[Dawes et al. 2018; Stroustrup 2018d]。另见(§11.4)。工作组主席、笔记记录员、会议组织者和编辑组的不懈努力是无形的,但却至关重要。例如,Jens Maurer 数十年来一直在 CWG 中做笔记,帮助提案者编写标准文本,安排网络访问,为无法出席的成员安排电话接入,安排会议室,告知成员当地旅行的可能性,等等。

有其他方案吗?在理想的世界里,我会建议限定由一小部分(大约 5 人)的全职受信任专家委员做决定,而由大团队完成(例如超过 350 人的委员会)完成讨论、提案、以及大部分流程。但我不认为 C++ 会发展成这样,因为:

  • 没有人喜欢放弃权力(在这种情况下是投票权)。
  • 要为固定的全职专家团队保持稳定的资金投入需要非同小可的技能(而这种技能在 C++ 社区还没有出现)。
  • 成功的时刻不会发生激进的变化;只有 C++ 使用量的显著下降才能促进委员会进行剧烈的组织创新(那时多半已经为时已晚)。

我不认为公司控制是可行的替代方案,因为:

  • 公司期望投资回报。
  • 公司的支持往往几年后就会消失。
  • 公司往往选择差异化的优势,而不是惠及所有人的进步。

我也不认为完全开放的审议流程(成千上万的投票者)是可行的:

  • 超过千人的投票就会失去品味。
  • 大群体的成员和意见没法在几十年里保持稳定。

对许多大型开源项目起作用的分级审批程序可能至少提供了部分方案,但是在 C 和 C++ 的标准化开始时,这方面的经验很少。当这样一个系统运行良好时,你在审批层级中的地位越高,审批者的知识基础就越广阔,他们关注的领域也就越广泛。在组织结构顶部,我们会找到一人或多人,他们对所有知识都有所了解、对所有用户都有所关心。与此鲜明对比的是,随着提案接近最终批准,ISO 流程会稀释专业知识和关注领域:全体会议上,许多委员对他们不感兴趣、领域经验有限且没有密切关注的提案进行投票。人们努力想负起责任,但是这真的很难。还要从大局角度来看待每个提案,把它们当作其中的一部分,那就几乎不可能了。

这样看来,WG21 的工作还不算糟糕。我确实担心这样的工作模式能否使 C++ 长久保持连贯、并且与时俱进。从另一个角度来看,出席 C++ 标准会议的有 200 多人,比其他标准的团体要大一个数量级,而 ISO 的流程本来就是为那种较小的团体设计的。另外,委员的多样性远远超过了过去的老三样:头发斑白的专家、公司代表、以及国家机构代表。混乱有可能爆发。

我从温斯顿·丘吉尔的格言中得到些许安慰,“民主是最糟糕的政府形式,除了所有那些人类一再尝试过的其他形式”。

特别要指出,我不认为经常被建议的“仁慈的终身独裁者”模式可以规模化,而且,不管怎么说,该模型从来就没对 C++ 适用过。

在我心目中,启动语言设计项目的理想模式是单个人或一小群密切配合的朋友。但我看不到这种方式可以规模化。一门成熟的语言需要数十甚至数百个人来解决他们必须面对的各种问题。即使只是与相关的标准、行业组织进行协调,也会让一个小规模、紧密配合的团体彻底应接不暇。

3.4 提案检查清单

C++98 有个“如何编写提案”的指南 [Stroustrup et al. 1992],但奇怪的是,演化组并没有为提给 C++14、C++17 或 C++20 的提案准备一份检查清单。有一份针对标准库提案的检查清单 [Meredith 2012]。对于 C++20,国家标准机构负责人的一份说明 [van Winkel et al. 2017] 和 Direction Group 的一份文件 [Hinnant et al. 2019] 给出了一些指导。以下是一个简短而不完整的问题清单,这些问题几乎总会被提给一项提案:

  • 要解决的问题是什么?将为什么样的用户提供服务?新手?专家?
  • 解决方案是什么?阐明它所基于的原则。给出简单的使用案例和专家级的使用案例。
  • 有哪些替代解决方案?库解决方案是否足够?为什么现有功能不够好?
  • 为什么解决方案需要在标准中?
  • 采用该技术存在哪些障碍?从现有的技术过渡可能需要多久?
  • 已经实现了吗?在实现过程中遇到了或预期会遇到哪些问题?有用户体验吗?
  • 会不会有很大的编译期开销?
  • 该特性是否能融入到现有工具和编译器的框架中?
  • 与变通方案相比,会有运行期开销吗?在时间上?在空间上?
  • 会有兼容性问题吗?会破坏现有的代码吗?ABI 会被破坏吗?
  • 新功能将如何与现有功能和其他新功能交互?
  • 解决方案是否容易教授?教给谁?谁来教?
  • 标准库会受到怎样的影响?
  • 该提案是否会导致对未来标准进一步扩展的要求?
  • 该特性在标准里如何措辞表达?
  • 用户在使用新功能时可能会犯哪些错误?
  • 就整个 C++ 社区的利益而言,该提案是否属于前 20 名?前 10?
  • 该提案是否属于特定子社区的前三名?哪个子社区?
  • 该提案是解决某一类问题的通用机制还是某个特定问题的特定解决方法?如果是针对一类问题,是哪一类问题?
  • 该提案在语义、语法和命名方面是否与语言的其余部分一致?

理想的情况是,一项提案能够回答所有这些问题,甚至更多,但这种情况很少发生。特别是,在最初的提案中,理由往往非常薄弱,因为提案者认为所处理的问题的重要性和他们建议的解决方案非常明显。然而,后续的论文、修改、电子邮件讨论和演化组的面对面讨论通常都会涉及这些问题,但很少对各个提案进行系统的或一致的检查。成员们倾向于关注技术细节(例如,语法、歧义、优化机会和命名),而不是重新探讨根本问题。有时,我所认为的糟糕的提案会混进去。原因通常是提案者的极大热情加上反对者的分心、礼貌和疲惫 [Stroustrup 2019b]。