2020:C++20 概念、協程與模組——四十年来最大更新#
事件#
2020 年 12 月,C++20(ISO/IEC 14882:2020)發佈。這是 C++11 之後最大的更新,四大支柱(The Big Four):Concepts、Ranges、Coroutines、Modules 同時問世。
關鍵語法/特性#
- Concepts(概念):
template<std::integral T>、requires子句——把 STL 一直以來的「隱性契約」變成顯性語法,錯誤訊息從數百行模板錯誤變成一行「T 不滿足 concept」。 - Ranges:
views::filter、views::transform、管線語法v | filter(f) | transform(g)——STL 演算法的組合式進化。 - Coroutines(協程):
co_await、co_yield、co_return——語言層面的非同步與產生器。 - Modules:
import std;——取代#include前置處理器,編譯速度與封裝性革命。 - 三路比較(spaceship 運算子):
auto operator<=>(const T&)——一個運算子生成全部六種比較。 std::format:型別安全的格式化(借鑒 Python/Rust 的 fmt)。constexpr大擴充:constexpr虛擬函式、constexpr動態配置、std::string/vector可編譯期使用。std::span、指定式初始化.x=、consteval、constinit。
為何重要#
- Concepts 實現了 20 年的願望:1994 年 STL 的「concept」只是文件上的隱性契約,2020 年終於成為語法——泛型程式設計的錯誤訊息從此可讀。
- Modules 取代 40 年的
#include:前置處理器的文字替換是 C/C++ 編譯慢、封裝差的根源——模組讓「介面與實作分離」真正落地。 - Coroutines 讓非同步成為語言特性:以前要靠庫(asio、協程庫的堆疊 hack),現在
co_await即可——與 Rust 2019 的 async/await、C# 2012 同一時代潮流。 - spaceship 與 format:日常程式碼的簡潔與安全。
理論與實用原因#
- 理論原因:Concepts 源自 Stroustrup 與 Dos Reis 的論文(2006)與 Haskell 的 type class;Coroutines 源自 Gor Nishanov 的堆疊式/無堆疊式協程理論;Modules 源自「模組系統」的數十年研究(Clang Modules 2012 先行實驗)。
- 實用原因:模板錯誤訊息惡名昭彰;
#include讓大型專案編譯以小時計;非同步回呼地獄(callback hell)難以維護——三大痛點一次回應。
彌補了什麼缺陷#
彌補了 C++「泛型契約不可表達、編譯緩慢、非同步只能靠庫、樣板錯誤訊息不可讀、比較運算子要寫六個」的缺陷——這是對 C++ 四十年技術債的一次總償還。
程式範例#
Concepts:錯誤訊息從爆炸到一行
// C++17 舊寫法:契約隱性,錯誤訊息動輒數百行
template <typename T>
T half(T x) { return x / 2; }
half("hello"); // 編譯錯誤:模板實例化錯誤訊息爆炸
// C++20 新寫法:契約顯性,錯誤訊息一行「T 不滿足 integral」
#include <concepts>
template <std::integral T> // 用標準 concept 限制 T 必須是整數
T half2(T x) { return x / 2; }
// half2("hello"); // error: constraints not satisfied(一行就能看懂)
// 自訂 concept + requires 子句
#include <concepts>
template <typename T>
concept Addable = requires(T a, T b) {
a + b; // 要求 T 支援 + 運算
};
template <Addable T> // 只允許滿足 Addable 的型別
T sum(T a, T b) { return a + b; }
Ranges:管線取代傳統迴圈
// 舊寫法:中間容器 + 迴圈,囉嗦且多一次記憶體配置
std::vector<int> v{1, 2, 3, 4, 5, 6};
std::vector<int> tmp;
for (int x : v)
if (x % 2 == 0) tmp.push_back(x);
for (int& x : tmp) x = x * x;
// C++20 新寫法:管線組合,惰性求值、無中間容器
#include <ranges>
namespace views = std::ranges::views;
auto result = v | views::filter([](int x) { return x % 2 == 0; })
| views::transform([](int x) { return x * x; });
協程:co_yield 產生器示意
#include <coroutine>
// 簡化版 generator:語言層面支援,不用再手寫狀態機
struct Generator {
struct promise_type {
int current_value;
Generator get_return_object() { return {this}; }
std::suspend_always initial_suspend() { return {}; }
std::suspend_always yield_value(int v) { current_value = v; return {}; }
std::suspend_always final_suspend() noexcept { return {}; }
void return_void() {}
void unhandled_exception() {}
};
promise_type* p;
bool move_next() { p->current_value; /* 恢復協程 */ return true; }
int value() const { return p->current_value; }
};
Generator counter() {
for (int i = 0; i < 3; ++i)
co_yield i; // 掛起並交出值,狀態自動保留
}
// 非同步同樣用語法解決:auto r = co_await some_async_task();
spaceship 運算子與 std::format
// 舊寫法:比較運算子要手寫六個,容易遺漏或寫錯
struct Point17 {
int x, y;
bool operator==(const Point17&) const = default;
bool operator!=(const Point17&) const = default;
bool operator<(const Point17& o) const { return x < o.x || (x == o.x && y < o.y); }
bool operator>(const Point17& o) const { return o < *this; }
bool operator<=(const Point17& o) const { return !(o < *this); }
bool operator>=(const Point17& o) const { return !(*this < o); }
};
// C++20 新寫法:一個運算子生成全部六種比較
#include <compare>
struct Point20 {
int x, y;
auto operator<=>(const Point20&) const = default; // 編譯器代勞
};
// std::format:型別安全,不像 printf 的 %s 配錯型別會崩潰
#include <format>
std::string s = std::format("point = ({}, {})", 3, 4);