跳轉到

2023:C++23 與模組時代——易用性持續打磨#

事件#

2023 年 10 月,C++23(ISO/IEC 14882:2024,俗稱 C++23)發佈。這一版延續「三年一版」節奏,以易用性與標準庫擴充為主軸。

關鍵語法/特性#

  • std::expected:expected<T, E>——型別安全的「值或錯誤」,取代例外與錯誤碼的二難(與 Rust 的 Result<T, E> 同一思想)。
  • if consteval:判斷當前是否編譯期求值。
  • 多維下標運算子:m[i, j, k]。
  • std::print / std::println:直接印出格式化字串(基於 std::format),終結 printf/cout 之爭。
  • Ranges 擴充:views::zip、views::chunk、views::enumerate、to<T>()。
  • std::mdspan、std::stacktrace、std::generator。
  • constexpr 持續擴充:constexpr 更多標準庫函式。
  • 靜態 operator()、#elifdef、Lambda 可帶屬性**。

為何重要#

  • std::expected 是錯誤處理的第三條路:例外(自動傳播但開銷與控制流隱晦)與錯誤碼(易被忽略)之間,「值或錯誤」進入型別系統——C++ 向 Rust 的 Result、Haskell 的 Either 看齊。
  • std::print 終結「Hello World 的百年之爭」:std::println("Hello, {}!", name);——型別安全、無格式符漏洞(%s 配錯型別的經典安全問題)。
  • Ranges 的 to<T>():管線運算可以直接產生容器——STL 泛型風格的最終形態。

理論與實用原因#

  • 理論原因:expected 源自代數資料類型(sum type)與「錯誤即值」的函數式理論;if consteval 是編譯期/執行期求值分流的語法化。
  • 實用原因:Boost 與 abseil 等庫已驗證 expected 十年;printf 的格式符漏洞(格式字串攻擊)是資安長年痛點。

彌補了什麼缺陷#

彌補了 C++「錯誤處理只有例外與錯誤碼兩條路、輸出有格式符安全漏洞、Ranges 無法直接產生容器」的缺陷。

程式範例#

std::expected:錯誤處理的第三條路

// 舊寫法一:錯誤碼——容易被呼叫者忽略
int parse_int(const char* s, int* out);   // 回傳 0/-1,沒人強制檢查
parse_int("abc", &v);                     // 忘了檢查回傳值,v 可能是垃圾

// 舊寫法二:例外——自動傳播但控制流隱晦、有開銷
int parse_int2(const std::string& s);     // 失敗時 throw
try { v = parse_int2(s); } catch (...) { /* 呼叫端被迫包 try */ }

// C++23 新寫法:值或錯誤進入型別系統(類似 Rust 的 Result<T, E>)
#include <expected>
std::expected<int, std::string> parse_int3(const std::string& s) {
    try { return std::stoi(s); }
    catch (...) { return std::unexpected("not a number"); }
}

auto r = parse_int3("42");
if (r) use(*r);            // 型別系統強迫你處理錯誤分支
else log(r.error());

std::println:終結 printf 的格式符漏洞

// 舊寫法:%s 配錯型別是經典資安漏洞(格式字串攻擊)
printf("Hello, %s!\n", name);        // name 若非 char* 會未定義行為
printf(user_input);                  // 使用者輸入含 %s 直接崩潰

// C 與 C++ 的 cout 寫法:安全但囉嗦
std::cout << "Hello, " << name << "!\n";

// C++23 新寫法:型別安全、簡潔(基於 std::format)
#include <print>
std::println("Hello, {}!", name);    // 編譯期檢查參數型別與個數

Ranges:views::enumerate 與 to<T>()

// 舊寫法:要 index 就得手算
std::vector<std::string> names{"a", "b", "c"};
for (std::size_t i = 0; i < names.size(); ++i)
    std::cout << i << ": " << names[i] << "\n";

// C++23 新寫法:enumerate 直接給 index + 值
#include <ranges>
for (auto [i, name] : names | std::views::enumerate)
    std::println("{}: {}", i, name);

// C++23 的 to<T>():管線末端直接產生容器,不必再手動建構
auto squares = std::views::iota(1, 6)
             | std::views::transform([](int x) { return x * x; })
             | std::ranges::to<std::vector>();   // 直接得到 vector<int>

import std;:模組取代 #include

// 舊寫法:前置處理器逐行文字替換,編譯慢、封裝差、重複解析標頭
#include <iostream>
#include <string>
#include <vector>

// C++23 新寫法:一行匯入整個標準庫,編譯速度與封裝性革命
import std;

int main() {
    std::println("module era!");
}

相關條目#