後端工坊 2026 年 5 月 21 日

2026-05-21 — C++26 copyable_function 修正 const-correctness、Rust 多層存在類型消除

primary=https://www.sandordargo.com/blog/2026/05/20/cpp26-copyable-function primary=https://wolfgirl.dev/blog/2026-05-20-erasing-existentials/

C++26 補齊 callable wrapper 家族:copyable_function 修正 std::function 的 const-correctness 缺陷

C++26 標準 (P2548R6, P0792R14) · 2026-05-20

C++26 將在標準庫新增 std::copyable_function(提案 P2548R6)與 std::function_ref(提案 P0792R14),填補 std::function 與 C++23 的 std::move_only_function 之間的設計空白。前者修正 const-correctness 缺陷並保留可複製語意,後者提供零開銷的非擁有式引用。

copyable_function:修正 std::function 的歷史錯誤

std::function 有一個由來已久的設計缺陷:它的 operator() 宣告為 const,卻可以呼叫儲存物件的非 const operator()。這個不一致無法在不破壞 ABI 的情況下修正,因此 C++26 引入全新的 copyable_function,從零開始正確實作 const-correctness。

具體行為由模板簽名中的限定詞控制:

std::copyable_function<int()> f;        // operator() 為非 const
std::copyable_function<int() const> g;  // operator() 為 const

copyable_function 支援 cv/ref qualifiers 與 noexcept,要求儲存的 callable 必須是可複製建構的。它可以隱式轉換為 move_only_function,反向則不行。移除了 target()target_type() 成員函式以降低實作複雜度。

function_ref:零開銷非擁有引用

std::function_ref 定位類似 std::string_view——不持有所有權、可平凡複製(trivially copyable)、無記憶體配置。沒有預設建構子和 operator bool,並禁止從非函式型別賦值以防懸掛引用。僅支援 constnoexcept 限定詞,不支援 ref-qualifiers。提案 P3961R1 修正 double indirection 問題並允許 noexcept 降級。

選用指南

  • function_ref:作為函式參數傳遞 callback,要求最低開銷
  • move_only_function(C++23):儲存不可複製的 callable,如 task queue 的任務
  • copyable_function:在新代碼中替代 std::function
  • std::function:不建議在新代碼中使用

原始來源:Sandor Dargo BlogP2548R6P0792R14


Rust 多層存在類型的消除:vtable 設計限制與手動繞解方案

wolfgirl.dev · 2026-05-20

Rust 的 dyn Trait 提供單層存在量化(existential quantification)的自動消除:編譯器生成 vtable,讓呼叫方無需知道具體型別。但當需要同時對多個型別參數進行存在量化時,例如 ∃S, B. S: Generic<B>,Rust 的型別系統目前不提供自動支援。

核心問題

直覺上希望寫成類似 Box<dyn Generic<?>> 的形式,但 vtable 無法儲存關聯型別的不同實例化版本——不同的 B 意味著不同的函式簽名,也就是不同的 vtable 佈局,這在靜態分派的 vtable 模型下是根本矛盾。試圖用關聯型別折疊多個存在量化會觸發 Rust 的一致性規則(coherence rules)impl<B, S: Generic<B>> SingleAssociated for S 違規,因為對同一個 S 可能存在多個 B 的實作。

使用 PhantomData<B> 包裝成 (S, PhantomData<B>) 可繞過一致性問題,但一旦要存入 vtable,佈局差異的問題依然存在。

可行的手動消除方案

文章提出兩種實用路徑。第一種是定義包裝 trait,手動轉發所需操作:

trait Erased {
    fn name(&self) -> &'static str;
}

這對不需要在方法簽名中使用 B 的情況有效。當方法簽名需要 B 的參數或回傳值時,第二種方案是透過 std::any::Any 進行型別消除:將涉及 B 的參數與回傳值包裝為 Box<dyn Any>,在呼叫端以 .downcast_ref() 恢復具體型別。

影響範圍

單層 dyn Trait 的自動消除不受影響,這是 Rust 日常使用的基礎。多層存在量化的需求主要出現在需要在資料結構中儲存多型 callable 或複雜 trait 階層的場景,例如插件系統、事件匯流排、泛型資料管線。目前的繞解方案需要大量樣板代碼,作者提到巨集自動生成理論可行,但尚無已知 crate 實作。

原始來源:wolfgirl.dev — Erasing Existentials


End of article
0
Would love your thoughts, please comment.x
()
x