[译]C++20:四巨头
英文原文:https://www.modernescpp.com/index.php/thebigfour
本文向你展示四巨头:concepts,ranges,coroutines 和 modules(四个名词目前尚未全都有既信达雅,又得到大部分业内人士认同的中文翻译,继续干脆全部保留英文原文。)。
C++20 提供了很多东西。在我给你们四巨头的第一印象前,这是 C++20 的概览。除了四巨头,还有很多特性影响着语言核心、库、以及 C++20 的并发能力。
编译器对 C++20 的支持
最简单的了解新特性的方法是上手把玩。这个方法马上引出一个问题:哪个编译器支持了 C++20 的哪些特性?经常地, cppreference.com/compiler_support能解答关于语言核心和库的这个问题。
简单说来,最最新的 GCC,Clang 和 EDG 编译器对语言核心提供了最好的支持。另外,MSVC 和苹果 Clang 编译器也支持了许多 C++20 的特性。
库也是类似的。GCC 对库的支持最好,接下来是 Clang 和 MSVC 编译器。
上面的截图只显示了表格开始的部分,但能给你一个不是特别满意的答案。甚至你使用所有最最新的编译器,也有很多特性没被任何编译器支持。
经常地,你能找到把玩新特性的变通方法。两个例子:
-
Concepts:GCC 支持上一个版本的 concepts。
-
std::jthread
:Github上有一份 Nicolai Josuttis 维护的实现草案。
简而言之。情况还不错。稍作修改,就可以尝试许多新功能。如有必要,我会提及这一点小技巧。
但是,现在让我给您鸟瞰这些新功能。当然,我们应该从四巨头开始。
四巨头
Concepts
使用模板进行泛型编程的关键思想是定义可以与各种类型一起使用的函数和类。通常,你用错误的类型实例化模板时,结果常常是几页的错误消息。这个悲伤的故事以 concepts 结束。Concepts 使你能够编写模板要求,这些要求可由编译器检查。你革新了我们思考和编写泛型代码的方式。原因如下:
-
模板要求是接口的一部分。
-
可以基于 concepts 进行函数的重载或类模板的特化。
-
由于编译器将模板参数的要求与实际模板参数进行比较,因此我们得到了改进的错误消息。
但是,这还不是故事的结局。
-
你可以使用预定义的 concepts 或定义自己的 concepts。
-
auto
和 concepts 的用法是统一的。您可以使用 concepts 代替auto
。 -
如果函数声明使用 concepts,它将自动成为函数模板。因此,编写函数模板与编写函数一样容易。
以下代码段向你展示了简单的 concept Integral
的定义和用法:
1template2concept bool Integral(){ 3 return std::is_integral ::value; 4} 5 6Integral auto gcd(Integral auto a, 7 Integral auto b){ 8 if( b == 0 ) return a; 9 else return gcd(b, a % b); 10}
Integral
是一个 concepts,要求它具有 std::is_integral
的类型参数 T
。 std::is_integral
是 type-traits
库中的一个函数,该函数在编译时检查 T
是否为整数。如果 std::is_integral
的值为 true
,则一切正常。如果不是,则会出现编译时错误。对于好奇的人——你应该好奇——这是我写的与 type-traits
库相关的文章。
gcd
算法基于欧几里得算法确定最大公约数。我使用了所谓的缩写函数模板语法来定义 gcd
。 gcd
从其参数和返回类型要求它们支持 concept Integral
。 gcd
是一种函数模板,它对参数和返回值提出了要求。当我删除语法糖时,也许你可以看到 gcd
的真正本质。
这是语义上等效的 gcd
算法。
1template2requires Integral () 3T gcd(T a, T b){ 4 if( b == 0 ) return a; 5 else return gcd(b, a % b); 6}
如果您看不到 gcd
的真正本质,则必须等待我几周后发表有关 concepts 的文章。
Ranges 库
Ranges 库是 Concepts 的第一位客户。它支持的算法将会:
-
能直接操作容器,你不需要用迭代器指定范围
-
能惰性求值
-
能被组合
简而言之:Ranges 库支持函数式模式。
好了,代码更胜于文字。下面的函数通过管道符号展示了函数组合:
1#include2#include 3#include 4 5int main(){ 6 std::vector ints{0, 1, 2, 3, 4, 5}; 7 auto even = [](int i){ return 0 == i % 2; }; 8 auto square = [](int i) { return i * i; }; 9 10 for (int i : ints | std::view::filter(even) | 11 std::view::transform(square)) { 12 std::cout << i << ' '; // 0 4 16 13 } 14}
even
是一个 lambda 函数用于判断 i
是否偶数,而且 lambda 函数 square
将 i
计算成它的平方。剩下的函数组合你要从左往右读: for (int i : ints | std::view::filter(even) | std::view::transform(square))
。将 ints
的每个元素都应用上过滤器 even
,并将每个出来的值映射到它的平方 square
。如果你熟悉函数式编程,这读起来就像散文。
Coroutines
Coroutines 是广义的函数,可以在保持其状态的同时暂停和恢复它们。Coroutines 是编写事件驱动的应用程序的常用方法。事件驱动的应用程序可以是模拟,游戏,服务器,用户界面甚至算法。Coroutines 通常也用于协作多任务。
我们没有 C++20 的具体 coroutines。我们将获得一个编写自己 coroutines 的框架。编写 coroutines 的框架包含 20 多个函数,你必须部分实现这些功能,而部分可能会重写它们。因此,你可以根据需要定制 coroutines。
让我向你展示特殊 coroutines 的用法。以下程序是一个用于生成无限数据流的生成器。
1GeneratorgetNext(int start = 0, int step = 1){ 2 auto value = start; 3 for (int i = 0;; ++i){ 4 co_yield value; // 1 5 value += step; 6 } 7} 8 9int main() { 10 11 std::cout << std::endl; 12 13 std::cout << "getNext():"; 14 auto gen = getNext(); 15 for (int i = 0; i <= 10; ++i) { 16 gen.next(); // 2 17 std::cout << " " << gen.getValue(); 18 } 19 20 std::cout << "\n\n"; 21 22 std::cout << "getNext(100, -10):"; 23 auto gen2 = getNext(100, -10); 24 for (int i = 0; i <= 20; ++i) { 25 gen2.next(); // 3 26 std::cout << " " << gen2.getValue(); 27 } 28 29 std::cout << std::endl; 30 31}
好吧,我必须补充几句话。这只是一个代码片段。函数 getNext
是一个 coroutine,因为它使用关键字 co_yield
。 getNext
有一个无限循环,返回 co_yield
之后的值。调用 next()
(第 2 行和第 3 行)将恢复 coroutine,随后的 getValue
调用将获取该值。在 getNext
调用之后,coroutine 再次暂停。它会暂停直到下一个 next()
调用。在我的示例中有一个大的未知数。这个未知数是 getNext
函数的返回值 Generator
。从这里开始,开始进行复杂的工作,这将成为 coroutine 详细文章的一部分。
感谢Wandbox在线编译器,我可以向您展示程序的输出。
Modules
对于 modules,我讲得非常少,因为文章已经太长了。
Modules 承诺:
-
更快的编译速度
-
与宏隔离
-
表达出代码的逻辑结构
-
不再需要头文件
-
处理丑陋的宏的变通方法
下一步?
在对四巨头有了个高层概览后,我将在下一篇文章中继续介绍语言核心特性。