rjk::duck: 透過 C++26 Reflection 實現類型擦除

rjk::duck: 透過 C++26 Reflection 實現類型擦除

rjk::duck 函式庫利用 C++26 reflection 來實現類型擦除(type erasure),讓開發者只需定義一次 trait 介面,即可自動將其應用於任何符合該介面的類型。這種方法消除了對數百行手動樣板程式碼或像 Boost.TypeErasureFolly.Poly 等重型依賴的需求,同時保持了傳統 vtable dispatch 的效能。

使用 C++26 Reflection 自動化介面生成

rjk::duck 允許使用者透過使用 [[=rjk::trait]] 標註的 struct 來定義介面。接著,函式庫會使用 C++26 reflection 來檢查此 trait 並在編譯時期生成必要的類型擦除機制。

Trait 檢查與標記

函式庫透過使用 annotations_of 和 reflection operator ^^ 來檢查 [[=rjk::trait]] 標註,藉此識別 trait。一旦識別出 trait,members_to_tags 函式會檢查 struct 的成員,並篩選出具有識別碼的使用者宣告函式。這些函式會被轉換為內部 "tags"(例如 has_fn<"name", signature>),作為生成 vtable 的藍圖。

Vtable 程式碼生成

透過使用 consteval 區塊和 define_aggregaterjk::duck 會生成一個包含 trait 中每個成員函式指標的 vtable struct。對於給定的類型 T,函式庫會建立一個靜態 vtable,其中每個插槽都填入了 wrapper 函式。此 wrapper 函式會執行從 void*T*static_cast 並呼叫原始成員函式。

進階重載解析 (Overload Resolution)

為了避免手動重新實作 C++ 重載解析,rjk::duck 使用了一種涉及 overload_setcandidate_wrapper 的技術。

函式庫並非搜尋精確的簽名匹配,而是生成一個包含所有符合特定識別碼之成員函式 wrapper 的可呼叫 overload_set。當 vtable 被填充時,編譯器自身的重載解析機制會在 erased_call 函式中使用,以選擇最適合底層類型的重載版本。

使用指標互換性優化記憶體

類型擦除的樸素實作通常需要每個成員函式 wrapper 儲存一個指向父物件的後向指標(back-pointer),以便存取 vtable 和底層數據。這會導致 duck 物件的大小隨著 trait 中函式數量增加而線性增長。

rjk::duck 使用 pointer-interconvertibility 來解決此問題。由於函式庫使用 standard layout types,因此它可以將成員函式 wrapper 的指標 reinterpret_cast 回父 duck 物件。這使得 vtable_function 物件可以作為空類別(使用 [[no_unique_address]]),確保 duck 實例的大小不論其 trait 中定義了多少個函式,都能保持不變。

效能調優與內聯 (Inlining)

雖然標準的 vtable dispatch 是高效的,但 rjk::duck 提供了一個選用的效能優化選項以減少間接層級。透過定義一個 [[=rjk::perf_options]] trait,使用者可以指定哪些函式應該被 "inlined"。

內聯函式會直接儲存在 duck 物件本身之中,而非儲存在外部 vtable 內。這以少量的額外記憶體(每個內聯函式 8 bytes)來換取呼叫時減少一次指標間接層級,這在 vtable 儲存在冷記憶體(cold memory)時特別有益。

技術需求與限制

目前,rjk::duck 是一個前沿實作,具有以下限制:

  • 編譯器支援:僅適用於使用 -std=c++26 -freflection 的 GCC。
  • 編譯時期執行:雖然透過 P2738,在 constexpr 上下文中進行 void* 轉型是變得可能的,但用於指標互換性的 reinterpret_cast 目前尚不允許在編譯時期使用,這限制了完整的 constexpr 支援。

社群觀點

圍繞該函式庫的討論突顯了現代 C++ reflection 的兩極化特性:

"我每天都在使用 C++,而這感覺起來像是完全不同的語言和哲學。"

批評者指出,靜態 reflection 可以很難除錯,且如果單個字元放錯位置,可能會導致極其冗長的編譯器錯誤訊息。其他人則質疑對編譯量時間的影響,並指出使用開發中階段的編譯器功能會使確定的基準測試變得困難。

Sources