30. C++20/23 新特性:概念、协程、范围、模块、编译期计算

好,咱们今天聊点硬核的。C++20 和 C++23 带来的这几个新特性——概念(concepts)、协程(coroutines)、范围(ranges)、模块(modules)、编译期计算(consteval)——说实话,是我从业十几年来见过的最有冲击力的一次更新。它们不是小修小补,而是从根上改变了我们写 C++ 的方式。

我个人习惯把这五个特性分成两类:写库的人狂喜(概念、范围、模块),和写业务逻辑的人狂喜(协程、编译期计算)。当然,你如果全都要,那更好。

5.1 概念(Concepts)——给模板加上“身份证”

先说说概念。C++ 模板一直有个痛点:你传错了类型,编译器给你吐出一堆天书般的错误信息。我记得刚入行那会儿,看到模板编译错误,第一反应是“我是不是写错了语法”,后来才发现是类型不匹配。那错误信息能翻好几屏,你根本不知道问题出在哪。

概念就是来解决这个问题的。它让你给模板参数加上约束,说白了就是“你得是某种类型才能进来”。

核心思想:requires 关键字或 concept 定义类型约束,让编译器在调用点就报错,而不是等到实例化时。

#include <concepts>
#include <iostream>

// 定义一个概念:必须是整数类型
template <typename T>
concept Integral = std::is_integral_v<T>;

// 使用概念约束函数模板
template <Integral T>
T add(T a, T b) {
    return a + b;
}

int main() {
    std::cout << add(3, 5) << std::endl;   // 正确
    // std::cout << add(3.14, 2.86);        // 编译错误:double 不满足 Integral
    return 0;
}

你看,Integral 这个概念一定义,编译器就能在调用 add(3.14, 2.86) 时直接告诉你“double 不是整数类型”,而不是等到模板内部展开才报错。这体验提升不是一点半点。

我的经验: 在项目中,我习惯把常用的概念封装到头文件里,比如 NumericIterableCallable。这样团队里其他人写模板时,直接 template <Numeric T> 就行,不用再写一堆 std::enable_if 了。

5.2 协程(Coroutines)——异步代码的“语法糖”

协程这个东西,说实话,我一开始是抗拒的。因为它的关键字太多了:co_awaitco_yieldco_return……而且你得自己实现一个 promise_type 和 awaitable 对象,门槛不低。

但用熟了之后,你会发现它真的香。协程让你用同步的写法写异步代码,不用再搞回调地狱或者状态机。

#include <iostream>
#include <coroutine>
#include <future>

// 一个简单的协程:模拟异步任务
struct Task {
    struct promise_type {
        Task get_return_object() { return {}; }
        std::suspend_never initial_suspend() { return {}; }
        std::suspend_never final_suspend() noexcept { return {}; }
        void return_void() {}
        void unhandled_exception() { std::terminate(); }
    };
};

Task async_hello() {
    std::cout << "Hello from coroutine!" << std::endl;
    co_return;
}

int main() {
    async_hello();
    return 0;
}

这个例子很简单,但你看,async_hello 函数里用了 co_return,它就是一个协程。编译器会自动生成状态机,把函数拆成多个片段。你不需要手动管理状态,协程自己会挂起和恢复。

避坑指南: 我曾经在项目里用协程做网络请求,结果忘了处理 final_suspend 的挂起策略,导致协程结束后资源没释放,内存泄漏了。记住:final_suspend 返回 std::suspend_never 还是 std::suspend_always,直接影响协程的生命周期管理。

5.3 范围(Ranges)——STL 算法的“流水线”

范围库是 C++20 里我最喜欢的新特性之一。它让你可以把多个算法串起来,像流水线一样处理数据。以前你要写嵌套的 std::transformstd::copy_if,代码又长又难读。现在用 viewsactions,一行搞定。

#include <ranges>
#include <vector>
#include <iostream>

int main() {
    std::vector<int> nums = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};

    // 用 ranges 实现:过滤出偶数,然后乘以 2
    auto result = nums 
        | std::views::filter([](int n) { return n % 2 == 0; })
        | std::views::transform([](int n) { return n * 2; });

    for (int n : result) {
        std::cout << n << " ";  // 输出:4 8 12 16 20
    }
    return 0;
}

你看,| 操作符把 filtertransform 串起来了。代码从左到右读,逻辑一目了然。而且 views 是惰性求值的,不会创建中间容器,性能也很好。

核心优势: 范围库让 STL 算法从“函数调用”变成了“管道操作”,可读性提升了一个档次。我个人在数据处理场景中已经全面改用 ranges 了。

5.4 模块(Modules)——告别头文件地狱

模块是 C++20 里最重量级的特性之一。它要解决的是 C++ 几十年的老问题:头文件重复包含、宏污染、编译慢。模块把接口和实现彻底分离,编译速度能快好几倍。

// math.ixx  —— 模块接口文件
export module math;

export int add(int a, int b) {
    return a + b;
}

// main.cpp  —— 使用模块
import math;
#include <iostream>

int main() {
    std::cout << add(3, 5) << std::endl;
    return 0;
}

你看,用 export module 定义模块,用 import 导入。没有头文件,没有宏,没有重复包含。编译器只编译一次模块,后续所有导入都直接复用编译结果。

我的建议: 新项目可以直接用模块替代头文件。但老项目迁移要小心,因为模块和传统头文件不能混用(至少目前编译器支持还不完美)。我一般先在工具库上试点,等稳定了再推广到业务代码。

5.5 编译期计算(Consteval)——强制在编译期执行

最后说说 consteval。C++ 之前有 constexpr,但它只是“可能”在编译期执行,具体看编译器心情。而 consteval 是“必须”在编译期执行,否则编译报错。

#include <iostream>

consteval int square(int n) {
    return n * n;
}

int main() {
    constexpr int result = square(5);  // 编译期计算
    std::cout << result << std::endl; // 输出 25

    // int x = 5;
    // int y = square(x);  // 编译错误:x 不是常量表达式
    return 0;
}

你看,square(5) 在编译期就算好了,运行时直接拿结果。而如果你传一个变量进去,编译器会直接报错,因为它没法在编译期确定值。

避坑指南: 我曾经在项目里用 consteval 写了一个复杂的哈希函数,结果因为参数里用了一个运行时变量,编译死活过不去。后来改成 constexpr 才解决。记住:consteval 只适合那些你确定参数一定是编译期常量的场景。

知识体系总览

下面这张图帮你理清这五个特性的定位和关系:

C++20/23 新特性知识体系 概念 (Concepts) 协程 (Coroutines) 范围 (Ranges) 模块 (Modules) 编译期计算 (Consteval) 模板约束 异步控制流 算法流水线 编译加速 编译期强制求值 共同目标:让 C++ 更安全、更高效、更易用 编译期检查 + 运行时性能 + 代码可读性

总结

这五个特性,每一个都值得你花时间去深入。概念让模板更安全,协程让异步更简单,范围让算法更优雅,模块让编译更快,编译期计算让性能更极致。它们不是孤立的,组合起来威力更大。

比如,你可以用概念约束协程的返回类型,用范围处理协程生成的数据流,用模块组织整个项目,用编译期计算优化热点路径。嗯,这才是现代 C++ 该有的样子。

一句话总结: C++20/23 不是让你写更复杂的代码,而是让你用更简单的方式写出更可靠的代码。我个人强烈建议,新项目直接上 C++20,老项目逐步迁移。


公众号:蓝海资料掘金营,微信 deep3321