跳轉到

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);

相關條目#