diff --git a/03.md b/03.md index ba75285..0ca53ce 100644 --- a/03.md +++ b/03.md @@ -53,7 +53,7 @@ - **SG2 模块**——主席 Gabriel Dos Reis(微软,之前代表得州农工大学)。 - **SG3 文件系统**——主席 Beman Dawes(“自己”)。 - **SG8 概念**——主席 Andrew Sutton(俄亥俄州阿克伦大学,之前代表得州农工大学)。 -- **SG9 范围**——更新 STL,以使用概念,简化表示法,及提供无限序列和管道——主席,Eric Niebler(Facebook)。 +- **SG9 范围**——更新 STL,以使用概念,简化写法,及提供无限序列和管道——主席,Eric Niebler(Facebook)。 **SG4 网络**,目前处于休眠状态,因为其结果正在等待被合并到标准([§8.8.1](08.md#881-网络库))中。另一个研究组 **SG11 数据库**,因缺乏共识和缺乏足够数量的志愿者完成工作而解散。 @@ -149,9 +149,9 @@ ISO 只需要也只认可三名正式官员: 例子:`any`、`optional` 和 `variant` 的不同接口([§8.3](08.md#83-variantoptional-和-any))。概念([§6](06.md#6-概念))。 -- **精确规范**:标准是规范,而不是实现。但是,标准是用英语编写的,因此我们做不到数学般的精度。委员会的许多成员擅长数学,但不擅长数学的人更多,因此在规范中没办法使用数学记法。试图使英文文本精确而详尽,则会让文本变得生硬又难以理解。我常常很难理解标准中对我自己提案的描述。 +- **精确规范**:标准是规范,而不是实现。但是,标准是用英语编写的,因此我们做不到数学般的精度。委员会的许多成员擅长数学,但不擅长数学的人更多,因此在规范中没办法使用数学式的写法。试图使英文文本精确而详尽,则会让文本变得生硬又难以理解。我常常很难理解标准中对我自己提案的描述。 - 大多数委员是程序员,而不是设计师,因此规范有时看起来会像程序——用没有类型系统或编译器的低级语言写成的程序。有详尽的如果、那么、否则的说明,却很少写出不变量。更糟糕的是,很多词汇是继承自 C,而且是基于程序源代码文本中的标记,因此,更高级别的概念仅被间接提出。 + 大多数委员是程序员,而不是设计师,因此规范有时看起来会像程序——用没有类型系统或编译器的低级语言写成的程序。有详尽的如果、那么、否则的说明,却很少写出不变量。更糟糕的是,很多词汇继承自 C,而且是基于程序源代码文本中的标记,因此,更高级别的概念仅被间接提出。 奇怪的是,标准库规范在结构上明显比语言规范更为正式。 diff --git a/04.md b/04.md index b432d34..8f19b5a 100644 --- a/04.md +++ b/04.md @@ -493,7 +493,7 @@ void use(vector& v, list& lst) 它最初是由 Thorsten Ottosen(丹麦奥尔堡大学)提出的,理由是“基本上任何现代编程语言都内置了某种形式的 for each” [Ottosen 2005]。我通常不认为“别人都有了”是个好的论据,但在这一情况下,真正的要点是,简单的范围循环可以简化一种最常见的操作,并提供了优化的机会。所以,范围 `for` 完美符合我对 C++ 的总体设计目标。它直接表达应该做什么,而不是详细描述如何做。它的语法简洁,语义明晰。 -由于更简单和更明确,范围 `for` 语句消除了一些“微不足道”然而常见的错误: +由于更简单和更明确,范围 `for` 的写法消除了一些“微不足道”然而常见的错误: ```cpp void use(vector& v, list& lst) @@ -523,7 +523,7 @@ void use(const Matrix& m1, const Matrix& m2) } ``` -这里 `operator+` 让我们可以使用常规的数学记法,同时也是一个工厂函数返回大对象的示例。 +这里 `operator+` 让我们可以使用常规的数学写法,同时也是一个工厂函数返回大对象的示例。 通过 `const` 引用把 `Matrix` 传递给函数,一直是传统而高效的做法。而问题在于,如何以传值来返回 `Matrix` 而不用拷贝所有的元素。早在 1982 年,我曾通过一种优化方案来部分解决这一问题,即干脆将返回值分配在调用函数的栈帧上。它工作得很好,但它只是优化技术,不能处理更复杂的返回语句。而用户在按值返回“大对象”时,需要确保绝不会进行大量的数据复制。 @@ -543,9 +543,9 @@ public: }; ``` -当用于初始化或赋值的源对象马上就会被销毁时,**移动**就比**拷贝**要更好:移动操作只是简单地把对象的内部表示“窃取”过来。`&&` 表示构造函数是一个**移动构造函数**,`Matrix&&` 被称为**右值引用**。当用于模板参数时,右值引用的符号 `&&` 被叫做**转发引用**,这是由 John Spicer 在 2002 年的一次会议上,同 Dave Abrahams 和 Howard Hinnant 一起提出的。 +当用于初始化或赋值的源对象马上就会被销毁时,**移动**就比**拷贝**要更好:移动操作只是简单地把对象的内部表示“窃取”过来。`&&` 表示构造函数是一个**移动构造函数**,`Matrix&&` 被称为**右值引用**。当用于模板参数时,右值引用的写法 `&&` 被叫做**转发引用**,这是由 John Spicer 在 2002 年的一次会议上,同 Dave Abrahams 和 Howard Hinnant 一起提出的。 -这个 `Matrix` 的例子有个有意思的地方:如果 `Matrix` 的加法返回指针的话,那传统的数学记号(`a+b`)就不能用了。 +这个 `Matrix` 的例子有个有意思的地方:如果 `Matrix` 的加法返回指针的话,那传统的数学写法(`a+b`)就不能用了。 移动语义蕴含着性能上的重大好处:它消除了代价高昂的临时变量。例如: @@ -701,7 +701,7 @@ vector v = {7,8}; // 应该可以工作(显然,但是没有) 上一个例子令我非常不舒服,因为它违反了 C++ 的根本设计目标,即为内建类型和用户定义的类型提供同等的支持。特别是,因为对数组初始化有比 `vector` 更好的支持,这会鼓励人们使用容易出错的内建数组。 -当 C++0x 的工作从 2002 年开始的时候,Daniel Gutson、Francis Glassborow、Alisdair Meredith、Bjarne Stroustrup 和 Gabriel Dos Reis 曾进行了许多讨论和提议,来解决其中一些问题。在 2005 年,Gabriel Dos Reis 和我提出了**统一初始化**的写法,该写法可用于每种类型,并且在程序中的任何地方都具有相同的含义 [Stroustrup and Dos Reis 2005b]。这种写法有望大大简化用户代码并消除许多不易察觉的错误。该表示法是基于使用花括号的列表写法。举例来说: +当 C++0x 的工作从 2002 年开始的时候,Daniel Gutson、Francis Glassborow、Alisdair Meredith、Bjarne Stroustrup 和 Gabriel Dos Reis 曾进行了许多讨论和提议,来解决其中一些问题。在 2005 年,Gabriel Dos Reis 和我提出了**统一初始化**的写法,该写法可用于每种类型,并且在程序中的任何地方都具有相同的含义 [Stroustrup and Dos Reis 2005b]。这种写法有望大大简化用户代码并消除许多不易察觉的错误。这一写法基于使用花括号的列表写法。举例来说: ```cpp int a = {5}; // 内建类型 @@ -730,7 +730,7 @@ template int foo(T); int z = foo(X{1}); // 显式构造 ``` -其中许多的情形,例如为使用 `new` 创建的对象提供初始化器列表,干脆就没法使用以前的方式来完成。 +其中许多的情形,例如为使用 `new` 创建的对象提供初始化器列表,使用以前的写法根本就做不到。 可惜,对于这一理想,我们仅仅达到不完全的近似,我们有的方案只能算大致统一。有些人发现,使用 `{…}` 很别扭,除非 `…` 是同质对象的列表,而其他人则坚持 C 语言中对聚合和非聚合的区分,并且许多人担心没有显式类型标记的列表会导致歧义和错误。例如,以下写法被认为是危险的,不过最终还是被接受了: @@ -794,7 +794,7 @@ public: vector v3 {1,2,3,4,5}; // 具有 5 个元素的 vector ``` -令人遗憾的是,统一初始化(`{}`——初始化)的使用并不像我期望的那样广泛。人们似乎更喜欢熟悉的写法和熟悉的缺陷。我似乎陷入了 N+1 问题:你有 N 个不兼容和不完整的解决方案,因此添加了一个新的更好的解决方案。不幸的是,原始的 N 个解决方案并没有消失,所以你现在有了 N+1 个解决方案。公平地说,有一些细微的问题超出了本文的范围,这些问题只是在 C++14、C++17 和 C++20 中被逐步补救。我的印象是,泛型编程和对更简洁的表示法的推进正在慢慢增加统一初始化的吸引力。所有标准库容器(如 `vector`)都有初始化器列表构造函数。 +令人遗憾的是,统一初始化(`{}`——初始化)的使用并不像我期望的那样广泛。人们似乎更喜欢熟悉的写法和熟悉的缺陷。我似乎陷入了 N+1 问题:你有 N 个不兼容和不完整的解决方案,因此添加了一个新的更好的解决方案。不幸的是,原始的 N 个解决方案并没有消失,所以你现在有了 N+1 个解决方案。公平地说,有一些细微的问题超出了本文的范围,这些问题只是在 C++14、C++17 和 C++20 中被逐步补救。我的印象是,泛型编程和对更简洁写法的普遍推动正在慢慢增加统一初始化的吸引力。所有标准库容器(如 `vector`)都有初始化器列表构造函数。 ### 4.2.6 `nullptr` @@ -895,7 +895,7 @@ void f(int x) 与其他问题相比,这似乎并不重要,所以几十年来什么都没有发生。2006 年的一天,David Vandevoorde(EDG)、Mike Wong(IBM)和我在柏林的一家中餐馆吃了一顿丰盛的晚餐。我们在餐桌边聊起了天,于是一个设计浮现在一张餐巾纸上。这个讨论的起因是 IBM 的一项十进制浮点提案中对后缀的需求,该提案最终成了一个独立的国际标准 [Klarer 2007]。在大改后,该设计在 2008 年成为**用户定义字面量**(通常称为 UDL)[McIntosh et al. 2008]。当时让 UDL 变得有趣的重要发展是 `constexpr` 提案的进展([§4.2.7](#427-constexpr-函数))。有了它,我们可以保证编译期求值。 -照例,找到一种可接受的写法是一个问题。我们决定使用晦涩的 `operator""` 作为字面量运算符(literal operator)的写法是可以接受的,毕竟 `""` 是一个字面量。然后,`""x` 是后面跟着后缀 `x` 的字面量。这样一来,要定义一个用于 `complex` 数的 `Imaginary` 类型,我们可以定义: +照例,找到一种可接受的写法是一个问题。我们决定使用晦涩的 `operator""` 作为字面量运算符(literal operator)的写法是可以接受的,毕竟 `""` 是一个字面量。然后,`""x` 是用来表示字面量后面跟后缀 `x` 的写法。这样一来,要定义一个用于 `complex` 数的 `Imaginary` 类型,我们可以定义: ```cpp constexpr Imaginary operator""i(long double x) { return Imaginary(x); } @@ -1104,7 +1104,7 @@ sort(v.begin(),v.end(),[](int x, int y) { return x>y; }); > - 不能实例化包含任意长度参数列表的类模板和函数模板。 > - 不能以类型安全的方式传递任意个参数给某个函数 -这些都是重要目标,但我起初发现其解决方案过于复杂,记法太过晦涩,按我的品味其编程风格又太递归。不过在 Douglas Gregor 于 2004 年做的精彩演示之后,我改变了主意并全力支持这项提案,帮助它在委员会顺利通过。我被说服的部分原因是变参模板和当时的变通方案在编译时间上的对比测量。编译时间过长的问题随模板元编程的大量使用([§10.5.2](10.md#1052-元编程))变得越来越严重,对此变参模板是一项重大(有时是 20 倍)改进。可惜,变参模板越变越流行,也成了 C++ 标准库中必需的部分,以至编译时间的问题又出现了。不过,成功的惩罚(在当时)还是在遥远的将来。 +这些都是重要目标,但我起初发现其解决方案过于复杂,写法太过晦涩,按我的品味其编程风格又太递归。不过在 Douglas Gregor 于 2004 年做的精彩演示之后,我改变了主意并全力支持这项提案,帮助它在委员会顺利通过。我被说服的部分原因是变参模板和当时的变通方案在编译时间上的对比测量。编译时间过长的问题随模板元编程的大量使用([§10.5.2](10.md#1052-元编程))变得越来越严重,对此变参模板是一项重大(有时是 20 倍)改进。可惜,变参模板越变越流行,也成了 C++ 标准库中必需的部分,以至编译时间的问题又出现了。不过,成功的惩罚(在当时)还是在遥远的将来。 变参模板的基本思路是,递归构造一个**参数包**,然后在另一个递归过程来使用它。递归技巧是必须的,因为参数包中的每个元素都有它自己的类型(和大小)。 @@ -1173,7 +1173,7 @@ template using Vec = MyVector >; 其中的 `using` 语法,即要引入的名字总是出现在前面,则是我的建议。 -我和 Gabriel Dos Reis 一道把这个特性推广成一个(几乎)完整的别名机制,并最终得到接受 [Stroustrup and Dos Reis 2003c]。即便不涉及模板,它也给了人们一种记法上的选择: +我和 Gabriel Dos Reis 一道把这个特性推广成一个(几乎)完整的别名机制,并最终得到接受 [Stroustrup and Dos Reis 2003c]。即便不涉及模板,它也给了人们一种写法上的选择: ```cpp typedef double (*analysis_fp)(const vector&); @@ -1181,7 +1181,7 @@ typedef double (*analysis_fp)(const vector&); using analysis_fp = double (*)(const vector&); ``` -类型和模板别名是某些最有效的零开销抽象及模块化技巧的关键。别名让用户能够使用一套标准的名字而同时让各种实现使用各自(不同)的实现技巧和名字。这样就可以在拥有零开销抽象的同时保持方便的用户接口。考虑某通讯库(使用了 Concepts TS [Sutton 2017] 和 C++20 的简化记法)中的一个实例: +类型和模板别名是某些最有效的零开销抽象及模块化技巧的关键。别名让用户能够使用一套标准的名字而同时让各种实现使用各自(不同)的实现技巧和名字。这样就可以在拥有零开销抽象的同时保持方便的用户接口。考虑某通讯库(利用了 Concepts TS [Sutton 2017] 和 C++20 的写法简化)中的一个实例: ```cpp template @@ -1269,7 +1269,7 @@ void use() } ``` -进一步的记法简化被提议加入 C++20 [Spertus 2018],但没来得及成功通过: +进一步的写法简化被提议加入 C++20 [Spertus 2018],但没来得及成功通过: ```cpp tuple SVD(const Matrix& A) // 从返回语句中推导出元组模板参数 diff --git a/05.md b/05.md index 21fc7da..cf223ed 100644 --- a/05.md +++ b/05.md @@ -41,7 +41,7 @@ auto a = 1'234'567; // 1234567(整数) auto b = 1'234'567s; // 1234567 秒 ``` -尽管有严厉的警告指出使用单引号会破坏无数的工具,但实际效果似乎不错。单引号由 David Vandevoorde 提出 [Crowl et al. 2013]。他指出,在一些国家,特别是在瑞士的金融符号中,单引号被当作分隔符来使用。 +尽管有严厉的警告指出使用单引号会破坏无数的工具,但实际效果似乎不错。单引号由 David Vandevoorde 提出 [Crowl et al. 2013]。他指出,在一些国家,特别是在瑞士的金融写法中,单引号被当作分隔符来使用。 我的另一个建议,使用空白字符,则一直没有得到认同: @@ -119,9 +119,9 @@ Java 1.8 (~2013): x -> x * x; D 2.0 (~2009): (x) { return x * x; }; ``` -我认为,使用 `auto` 而且没有为 lambda 表达式引入特殊的不与函数共享的记法是正确的。此外,我认为在 C++14 中引入泛型 lambda 表达式,而没有引入概念,则是个错误;这样一来,对受约束和不受约束的 lambda 表达式参数和函数参数的规则和记法就没有一起考虑。由此产生的语言技术上的不规则(最终)在 C++20 中得到了补救([§6.4](06.md#64-c20-概念))。但是,我们现在有一代程序员习惯于使用不受约束的泛型 lambda 表达式并为此感到自豪,而克服这一点将花费大量时间。 +我认为,使用 `auto` 而且没有为 lambda 表达式引入特殊的不与函数共享的写法是正确的。此外,我认为在 C++14 中引入泛型 lambda 表达式,而没有引入概念,则是个错误;这样一来,对受约束和不受约束的 lambda 表达式参数和函数参数的规则和写法就没有一起考虑。由此产生的语言技术上的不规则(最终)在 C++20 中得到了补救([§6.4](06.md#64-c20-概念))。但是,我们现在有一代程序员习惯于使用不受约束的泛型 lambda 表达式并为此感到自豪,而克服这一点将花费大量时间。 -从这里简短的讨论来看,似乎委员会流程对记法/语法给予了特大号的重视。可能是这样,但是语法并非无足轻重。语法是程序员的用户界面,与语法有关的争论通常反映了语义上的分歧,或者反映了对某一特性的预期用途。记法应反映基础的语义,而语法通常偏向于对某种用法(而非其他用法)有利。例如,一个完全通用和啰嗦的记法有利于希望表达细微差别的专家,而一个为表达简单情况而优化的记法,则有利于新手和普通用户。我通常站在后者这边,并且常常赞成两者同时都提供([§4.2](04.md#42-c11简化使用))。 +从这里简短的讨论来看,似乎委员会流程对写法/语法给予了特大号的重视。可能是这样,但是语法并非无足轻重。语法是程序员的用户界面,与语法有关的争论通常反映了语义上的分歧,或者反映了对某一特性的预期用途。写法应反映基础的语义,而语法通常偏向于对某种用法(而非其他用法)有利。例如,一个完全通用和啰嗦的写法有利于希望表达细微差别的专家,而一个为表达简单情况而优化的写法,则有利于新手和普通用户。我通常站在后者这边,并且常常赞成两者同时都提供([§4.2](04.md#42-c11简化使用))。 ## 5.5 `constexpr` 函数中的局部变量 diff --git a/06.md b/06.md index cbef93e..7cc3ba4 100644 --- a/06.md +++ b/06.md @@ -101,8 +101,8 @@ template - 根据使用模式来指定原始约束——以处理重载和隐式类型转换。 - 多参数概念——例如 `Mergeable`。 - 类型和值概念——也就是说,概念既可以将值也可以将类型当作参数,例如 `Buffer`。 -- 模板的“类型的类型”简写记法——例如 `template …`。 -- “模板定义的简化记法”——例如 `void f(Comparable&);` 使泛型编程更接近于“普通编程”。 +- 模板的“类型的类型”简略写法—例如 `template …`。 +- “模板定义的简化写法”——例如 `void f(Comparable&);` 使泛型编程更接近于“普通编程”。 - `auto` 作为函数参数和返回值中约束最少的类型。 - 统一函数调用([§8.8.3](08.md#883-统一调用语法))——减少泛型编程与面向对象编程之间的风格差异问题(例如 `x.f(y)`、`f(x,y)` 和 `x+y`)。 @@ -187,7 +187,7 @@ concept BidirectionalIterator // BidirectionalIterator 是 ### 6.2.2 概念使用 -一个概念既可以用作 `where` 子句中的推断,也可以用作简写: +一个概念既可以用作 `where` 子句中的推断,也可以用在简略写法里: ```cpp template @@ -197,14 +197,14 @@ const T& min(const T& x, const T& y) return x // 简写记法 +template // 简略写法 const T& max(const T& x, const T& y) { return x>y ? x : y; } ``` -对于简单的“类型的类型”的概念,简写形式(最早在 [Stroustrup 2003] 中提出)很快变得非常流行。但是,我们很快发现,现有代码中的标识符中 `where` 太过于流行,于是将其重命名为 `requires`。 +对于简单的“类型的类型”的概念,简略写法(最早在 [Stroustrup 2003] 中提出)很快变得非常流行。但是,我们很快发现,现有代码中的标识符中 `where` 太过于流行,于是将其重命名为 `requires`。 ### 6.2.3 概念映射 @@ -285,7 +285,7 @@ T add(T x, T y) { 随着概念的使用越来越多,语义在概念(实际上是类型和库)设计中的作用变得越来越清晰,委员会中的许多人开始推动一种表达语义规则的机制。这并不奇怪,Alex Stepanov 喜欢说“概念全都是语义问题”。然而,大部分人那时都像对待其他语言功能一样对待概念,他们更关心语法和命名查找规则。 -2009年,Gabriel Dos Reis(在我大力支持下)提出了一种称为 `axiom`(公理)的记法并获得批准 [Dos Reis et al. 2009]: +2009年,Gabriel Dos Reis(在我大力支持下)提出了一种称为 `axiom`(公理)的写法并获得批准 [Dos Reis et al. 2009]: ```cpp concept TotalOrdering { @@ -575,7 +575,7 @@ Mergeable{In1,In2,Out} // 概念名称引导器 Out merge(In1, In1, In2, In2, Out); ``` -仅仅通过尝试,你就能学到很多东西,这真是令人惊叹!同样令人惊叹的是,对于那些尚未经历过这些问题的人,新颖的表示法和解决方案在他们那里也会遭遇巨大的阻力。 +仅仅通过尝试,你就能学到很多东西,这真是令人惊叹!同样令人惊叹的是,对于那些尚未经历过这些问题的人,新颖的写法和解决方案在他们那里也会遭遇巨大的阻力。 ### 6.3.5 概念和类型 @@ -664,22 +664,22 @@ concept Sequence = Has_begin && Has_end; ### 6.3.7 等效语法 -Concepts TS 支持在函数声明中使用概念的三种表示法: +Concepts TS 支持在函数声明中使用概念的三种写法: - 为通用起见,显式使用 `requires` 语句 -- 简写表示法,用于表示类型的类型 -- 自然表示法(也称为简短表示法、常规表示法等) +- 简略写法,用于表示类型的类型 +- 自然写法(也称为简短写法、常规写法等) -基本思想是,让程序员使用与特定声明的需求紧密匹配的表示法,而不会因使用更复杂声明所需的表示法而淹没该定义。为了使程序员可以自由选择表示法,尤其是允许在项目开发初期或维护阶段随着功能的变化而调整,这些风格的表示法被定义为等效的: +基本思想是,让程序员使用与特定声明的需求紧密匹配的写法,而不会因使用更复杂声明所需的写法而淹没该定义。为了使程序员可以自由选择写法,尤其是允许在项目开发初期或维护阶段随着功能的变化而调整,这些风格的写法被定义为是等效的: ```cpp -void sort(Sortable &); // 自然表示法 +void sort(Sortable &); // 自然写法 ``` 等同于 ```cpp -template void sort(S&); // 简写表示法 +template void sort(S&); // 简略写法 ``` 等同于 @@ -688,22 +688,22 @@ template void sort(S&); // 简写表示法 template requires Sortable void sort(S&); ``` -用户对此感到非常满意,并且倾向于在大多数声明中使用自然和简写表示法。但是,有些委员会成员对自然表示法感到恐惧(“我看不出它是一个模板!”),而喜欢使用最显式的 `requires` 表示法,因为它甚至可以表达最复杂的示例(“为什么你还要比那更复杂的东西?”)。我的解释是,我们对什么是简单有两种看法: +用户对此感到非常满意,并且倾向于在大多数声明中使用自然和简略写法。但是,有些委员会成员对自然写法感到恐惧(“我看不出它是一个模板!”),而喜欢使用最显式的 `requires` 写法,因为它甚至可以表达最复杂的示例(“为什么你还要比那更复杂的东西?”)。我的解释是,我们对什么是简单有两种看法: - 我可以用最简单、最快捷的方式编写代码 -- 我只需要学习一种表示法 +- 我只需要学习一种写法 我赞成前一种观点,认为这是洋葱原则([§4.2](04.md#42-c11简化使用))的一个很好的例子。 -自然表示法成为强烈反对概念的焦点。我——还有其他人——坚持这种优雅的表达 +自然写法成为对概念强烈反对的焦点。我——还有其他人——坚持这种优雅的表达 ```cpp -void sort(Sortable&); // 自然表示法 +void sort(Sortable&); // 自然写法 ``` 我们看到(过去和现在)这是有用而优雅的一步,可以使泛型编程逐渐变成一种普通的编程方式,而不是一种具有不同语法、不同源代码组织偏好(“仅头文件”)和不同编码风格(例如模板元编程([§10.5.2](10.md#1052-元编程)))的暗黑艺术。模块解决了源代码组织问题([§9.3.1](09.md#931-模块))。另外,更“自然”的语法解决了人们总是抱怨的关于模板语法过于冗长和笨拙的问题,我同意这些抱怨。在设计模板时,`template<…>` 前缀语法不是我的首选。由于人们总是担心能力不强的程序员滥用模板而引起混淆和错误,我被迫接受了这种写法。繁重的异常处理语法(`try { … } catch ( … ) { … }`)也是类似的故事 [Stroustrup 2007]。似乎对于每个新特性,许多人都要求有**醒目**的语法来防止实际和想象中的潜在问题。然后过一段时间后,他们又抱怨太啰嗦了。 -无论如何,有为数不少的委员会成员坚持认为自然表示法会导致混乱和误用,因为人们(尤其是经验不足的程序员)不会意识到以这种方式定义的函数是模板,和其他函数并不相同。我在使用和教授概念的多年里并没有观察到这些问题,因此我并不特别担心这样的假设性问题,但反对意见仍然非常强烈。人们就是**知道**这样的代码很危险。主要的例子是 +无论如何,有为数不少的委员会成员坚持认为自然语法会导致混乱和误用,因为人们(尤其是经验不足的程序员)不会意识到以这种方式定义的函数是模板,和其他函数并不相同。我在使用和教授概念的多年里并没有观察到这些问题,因此我并不特别担心这样的假设性问题,但反对意见仍然非常强烈。人们就是**知道**这样的代码很危险。主要的例子是 ```cpp void f(C&&); // 危险:C 是一个概念还是类型? @@ -734,9 +734,9 @@ C++0x 概念([§6.2](#62-c0x-概念))的惨败加剧了这种担忧,这导 在 2017 年,作为 C++20 的最早特性之一,WG21 将 Concepts TS [Sutton 2017] 中基础部分和无争议的部分通过投票进入了工作文件([§6.3.2](#632-概念使用)): - 为通用起见,显式使用 `requires` 语句;例如 `requires Sortable` -- 简写表示法,用于表示类型的类型;例如 `template` +- 简略写法,用于表示类型的类型;例如 `template` -自然表示法(例如 `void sort(Sortable&);`([§6.3.7](#637-等效语法)))因有争议而被排除在外。被排除在外的原因有以下几点: +自然写法(例如 `void sort(Sortable&);`([§6.3.7](#637-等效语法)))因有争议而被排除在外。被排除在外的原因有以下几点: - `void sort(Sortable&);` 是一个模板,但这不很明显。 - `void f(C&&);` 的含义取决于 `C` 是概念还是类型。 @@ -771,24 +771,24 @@ template void f(T&); // 建议被废止 在 2018 年中,我提出了一个最小折中方案 [Stroustrup 2018b]: - 保留 `template void f(T&);` 的含义; -- 使用前缀 `template` 来识别使用自然表示法的模板(例如 `template void f(Concept&)`) +- 使用前缀 `template` 来识别使用自然写法的模板(例如 `template void f(Concept&)`) 提议成功了,但是 Herb Sutter [Sutter 2018a] 提出的一个截然不同的建议也成功了 [Sutter 2018a]。我们当时处于一种非常特殊的境地,同时有两个截然不同且互不兼容的提案,每个都得到了 EWG 的大多数人的支持。这种僵局为 Ville Voutilainen(EWG 主席)提出一种变通方案打开了大门,这一方案在 2018 年 11 月得到了广泛的支持并被接受 [Voutilainen et al. 2018]: - 保留 `template void f(T&);` 的含义 -- 使用 `auto` 来识别使用自然表示法的模板参数,例如 `void f(Concept auto&);` +- 使用 `auto` 来识别使用自然写法的模板参数,例如 `void f(Concept auto&);` 举例来说: ```cpp -// 几乎自然的表示法: +// 几乎自然的写法: void sort(Sortable auto& x); // x 必须 Sortable Integral auto ch = f(val); // f(val) 的结果必须为 Integral Integral auto add(Integral auto x, Integral auto x); // 能用一个宽类型 // 来防止溢出 ``` -“自然表示法”已重命名为“缩写语法”,虽然它不仅仅是一个缩写。 +“自然写法”已重命名为“缩写语法”,虽然它不仅仅是一个缩写。 尽管我认为在这种 `auto` 的使用有些多余,分散和损害了我想使泛型编程变成“普通编程”的目标,但我还是支持这种折中方案。也许在将来的某个时候,人们会(正如当时 Herb Sutter 所暗示的那样)达成一致,让在概念名后的 `auto` 不再必要。不过,我并没有抱太大的希望;很多人认为为技术实现而定义的语法标记很重要。或许 IDE 的自动完成功能可以使用户免于手写这多余的 `auto`。 diff --git a/07.md b/07.md index 0866cff..e48224d 100644 --- a/07.md +++ b/07.md @@ -49,7 +49,7 @@ C++ 异常是在 1988–89 年设计的,旨在解决当时普遍存在的复 因此,大多数 C++ 实现仍然保留了非异常机制的错误处理方式。另一方面,也存在一些通过错误码无法提供良好解决方案的场景: - **构造函数失败**——由于构造函数没有返回值(不算被构造对象本身),单纯依赖 RAII 的方式必须替换为通过对对象状态的显式检查来处理错误。 -- **运算符**——没有办法从 `++`、`*`、`->` 中返回错误码。你将不得不使用非本地错误指示或使用繁琐的运算符名称,例如 `multiply(add(a,b),c)` 而不是 `(a+b)*c`。[^1] +- **运算符**——没有办法从 `++`、`*`、`->` 中返回错误码。你将不得不使用非本地的错误指示,或使用一种糟糕的写法,如 `multiply(add(a,b),c)` 而不是 `(a+b)*c`。[^1] - **回调**——使用回调的函数应该能够调用具有多种可能错误的函数(通常,回调是 lambda 表达式([§4.3.1](04.md#431-lambda-表达式)))。 - **非 C++ 代码**——我们无法通过那些没有专门做错误码处理的非 C++ 函数传递错误。 - **调用链深处新的错误类型**——必须准备调用链上的每个函数来处理或传播一种新的错误(例如,程序中的网络错误,而它并不是专门为通过网络访问数据而预先设计的)。 diff --git a/08.md b/08.md index c1c4fec..f7e33d3 100644 --- a/08.md +++ b/08.md @@ -5,8 +5,8 @@ C++17 有大约 21 个新的语言特性(取决于你的计数方式),包括: - 构造函数模板参数推导——简化对象定义([§8.1](#81-构造函数模板参数推导)) -- 推导指引——解决构造函数模板参数推导歧义的显式标注([§8.1](#81-构造函数模板参数推导)) -- 结构化绑定——简化标注,并消除一种未初始化变量的来源([§8.2](#82-结构化绑定)) +- 推导指引——解决构造函数模板参数推导歧义的明确写法([§8.1](#81-构造函数模板参数推导)) +- 结构化绑定——简化写法并消除一种未初始化变量的来源([§8.2](#82-结构化绑定)) - `inline` 变量——简化了那些仅有头文件的库实现中的静态分配变量的使用 [Finkel and Smith 2016] - 折叠表达式——简化变参模板的一些用法 [Sutton and Smith 2014] - 条件中的显式测试——有点像 for 语句中的条件([§8.7](#87-条件的显式测试)) @@ -158,7 +158,7 @@ auto {x,y,z} = f(); // 优美的调用语法,引入别名 auto [x,y,z] = f(); // 调用语法,引入别名 ``` -这是个小改动,但我认为是个错误。这个最后一刻的改动,导致了属性表达语法的小小复杂化(比如 `[[fallthrough]]`)([§4.2.10](04.md#4210-属性))。我对关于美学或作用域的论据并不买账,并且在 2014 年我就展示了关于为 C++ 添加函数式编程风格的模式匹配的想法,以 `{ … }` 表示用模式将值分解出来([§8.3](#83-variantoptional-和-any))。结构化绑定的设计就是为了适应这一总体方案。 +这是个小改动,但我认为是个错误。这个最后一刻的改动,导致了属性写法的小小复杂化(比如 `[[fallthrough]]`)([§4.2.10](04.md#4210-属性))。我对关于美学或作用域的论据并不买账,并且在 2014 年我就展示了关于为 C++ 添加函数式编程风格的模式匹配的想法,以 `{ … }` 表示用模式将值分解出来([§8.3](#83-variantoptional-和-any))。结构化绑定的设计就是为了适应这一总体方案。 这些并不是唯一的后期修改提案。每个提案都增加了或将增加复杂性。 @@ -276,7 +276,7 @@ void writer() 我认为这些例子很好体现了“简单的事情简单做”的哲学。有时,我同很多 C++ 程序员一样在想,“是什么让他们花了这么长时间?” -请注意使用从构造函数参数推导出来的模板参数是如何简化表达写法的([§8.1](#81-构造函数模板参数推导))。 +请注意使用从构造函数参数推导出来的模板参数是如何简化了写法的([§8.1](#81-构造函数模板参数推导))。 ## 8.5 并行 STL diff --git a/09.md b/09.md index 2d8fb8b..8470f8b 100644 --- a/09.md +++ b/09.md @@ -294,7 +294,7 @@ C++ 从 C 继承了只限于整型且不能调用函数的常量表达式。曾 ### 9.3.5 范围 -**范围库**始于 Eric Niebler 对 STL 序列观念的推广和现代化的工作 [Niebler et al. 2014]。它提供了更易于使用、更通用及性能更好的标准库算法。例如,C++20 标准库为整个容器的操作提供了期待已久的更简单的表示方法: +**范围库**始于 Eric Niebler 对 STL 序列观念的推广和现代化的工作 [Niebler et al. 2014]。它提供了更易于使用、更通用及性能更好的标准库算法。例如,C++20 标准库为整个容器的操作提供了期待已久的更简单的写法: ```cpp void test(vector& vs) @@ -337,7 +337,7 @@ constexpr auto tp = 2016y/May/29d + 7h + 30min + 6s + 153ms; cout << tp << '\n'; // 2016-05-29 07:30:06.153 ``` -该表示法很传统(使用用户定义的字面量[§4.2.8](04.md#428-用户定义字面量))),日期表示为 `年,月,日` 结构。但是,当需要时,日期会在编译期映射到标准时间线(`system_time`)上的某个点(使用 `constexpr` 函数([§4.2.7](04.md#427-constexpr-函数))),因此它极其快速,也可以在常量表达式中使用。例如: +这一写法很传统(使用用户定义的字面量[§4.2.8](04.md#428-用户定义字面量))),日期表示为 `年,月,日` 结构。但是,当需要时,日期会在编译期映射到标准时间线(`system_time`)上的某个点(使用 `constexpr` 函数([§4.2.7](04.md#427-constexpr-函数))),因此它极其快速,也可以在常量表达式中使用。例如: ```cpp static_assert(2016y/May/29==Thursday); // 编译期检查