第 10 章 C++20
C++20 如同 C++11 一样,似乎能够成为一个振奋人心的更新。例如,早在 C++11 时期就跃跃欲试呼声极高却最终落选的 Concept,如今已经箭在弦上。
C++ 组委会在讨论投票最终确定 C++20 有很多提案,诸如 Concepts/Module/Coroutine/Ranges/ 等等。
本章我们就来一览 C++20 即将引入的那些重要特性。
概念与约束
概念(Concepts)是对 C++ 模板编程的进一步增强扩展。简单来说,概念是一种编译期的特性, 它能够让编译器在编译期时对模板参数进行判断,从而大幅度增强我们在 C++ 中模板编程的体验。 使用模板进行编程时候我们经常会遇到各种令人发指的错误, 这是因为到目前为止我们始终不能够对模板参数进行检查与限制。 举例而言,下面简单的两行代码会造成大量的几乎不可读的编译错误:
#include <list> |
而这段代码出现错误的根本原因在于,std::sort 对排序容器必须提供随机迭代器,否则就不能使用,而我们知道 std::list 是不支持随机访问的。
用概念的语言来说就是:std::list 中的迭代器不满足 std::sort 中随机迭代器这个概念的约束(Constraint)。
在引入概念后,我们就可以这样对模板参数进行约束:
template <typename T> |
缩写为:
template<Sortable T> // T 是一个 Sortable 的类型名 |
甚至于直接将其作为类型来使用:
void sort(Sortable& c); // c 是一个 Sortable 类型的对象 |
我们现在来看一个实际的例子。下面用 requires 表达式定义一个概念 Addable,要求类型支持 + 运算且结果可转换回该类型,并用它来约束函数模板:
#include <concepts> |
当传入不满足约束的类型时,编译器会直接告诉我们「约束不满足」,而不是抛出一长串模板实例化的内部错误。
模块
模块(Modules)旨在解决传统头文件机制带来的诸多问题:重复解析、宏污染、包含顺序敏感以及缓慢的编译速度。一个模块用 export module 声明,并显式地 export 对外可见的实体:
// math.cppm —— 模块接口单元 |
使用方则用 import 代替 #include:
// main.cpp |
与本书其他示例不同,模块的编译需要工具链的专门支持,并且通常要分两步(先编译模块接口单元、再编译使用方),无法用
clang++ file.cpp这样的单条命令直接构建。具体的构建方式请参阅你所用编译器的文档。
范围
范围(Ranges)为标准库的算法与迭代器提供了更高层、可组合的抽象。借助范围适配器 (range adaptors) 与管道运算符 |,我们可以把多个惰性变换串联起来,代码更具声明式风格:
#include <iostream> |
这些视图都是惰性求值的:只有在真正遍历 result 时,过滤与变换才会逐元素地被计算,中间不会产生临时容器。
协程
协程(Coroutines)是可以被挂起与恢复的函数。一个函数只要在函数体中使用了 co_await、co_yield 或 co_return,它就是一个协程。需要强调的是,C++20 只提供了协程的语言机制以及 <coroutine> 中的底层支撑设施,而把 promise_type 这类「胶水」留给用户或库去实现(C++23 才提供了开箱即用的 std::generator)。
下面是一个最小的惰性生成器,利用 co_yield 逐个产出值:
#include <coroutine> |
关于合约与事务内存
需要澄清一个常见的误解:合约 (Contracts) 与 事务内存 (Transactional Memory) 并不属于 C++20。
- 合约曾被纳入 C++20 的工作草案,但在正式发布前被移除,目前作为一项重要特性正瞄准 C++26(参见本仓库的 C++26 追踪 issue #318)。
- 事务内存只以技术规范 (Technical Specification, TS) 的形式存在,从未被合并进 C++20 标准。
因此,本章不再将它们作为 C++20 的特性来介绍。
总结
总的来说,终于在 C++20 中看到 Concepts/Ranges/Modules 这些令人兴奋的特性, 这对于一门已经三十多岁『高龄』的编程语言,依然是充满魅力的。
进一步阅读的参考资料
欧长坤 © 2016-2026 版权所有, 采用知识共享署名-非商业性使用-禁止演绎 4.0 国际许可协议许可,代码使用 MIT 协议开源。
如果你认为本书对你起到了帮助,可以资助作者。