C++26 解除 constexpr 建構子的虛擬繼承限制
WG21 P3533R2 · Hana Dusíková · 2025-02-17(定案版本,2026 年併入 C++26 標準草案)
C++26 拿掉了 [dcl.constexpr] 第 3.2 段最後一條「constexpr 不能用」清單:只要一個類別帶有 virtual base class,它的建構子或解構子就不能宣告成 constexpr,這條限制自此刪除。提案是 P3533R2,作者 Hana Dusíková,R2 版本定案於 2025 年 2 月,cppreference 上對應的 feature test macro __cpp_constexpr_virtual_inheritance 已定義為 202506L,確認併入了今年的 C++26 工作草案。
原本的問題
C++20 已經開放 constexpr 函式裡可以用 virtual 函式呼叫、動態配置;C++23 又放行了例外處理。virtual inheritance 卻一直被獨立列在禁止清單裡,寫在 [dcl.constexpr]p3.2:「如果函式是建構子或解構子,其所屬類別不得有任何 virtual base class」。這條規則比其他限制更早訂下,但沒人再回頭檢討它是否還有必要。
後果是所有走「鑽石繼承」(diamond inheritance,兩個中介類別共享同一個虛擬基底)的物件模型,天生就無法在編譯期建構。P3533R2 點名了兩個受害者:std::ios_base 與串流類別的階層長期用 virtual base 去避免基底重複配置,因此無法 constexpr 化;<chrono> 裡需要編譯期剖析的型別,同樣因為內部用了虛擬繼承而卡住。換句話說,不是程式邏輯不能算,是語法本身先擋掉了。
核心改動
P3533R2 的修改很小:直接刪掉 [dcl.constexpr]p3.2 那一整條子句,不留替代條件。R0 到 R1 曾經因為協程提案 P3367 加過但書,但 R2 確認 P3367 改瞄準 C++29 之後,這段但書也被拿掉,最終是乾淨的一行刪除。論文附的例子示範了原本會直接編譯錯誤的鑽石繼承,現在可以整個放進 constexpr 求值:
struct Left : virtual Common { };
struct Right : virtual Common { };
struct Child : Left, Right { unsigned x; };
// C++23 以前:非法,Child 的隱式建構子帶 virtual base,不能是 constexpr
// C++26(P3533R2 之後):合法
constexpr auto ch = Child{};
static_assert(&ch.Left::get_counter() == &ch.Right::get_counter());這條 static_assert 能過,代表編譯器在常數求值階段,已經能正確算出 Left、Right 兩條路徑指到同一個共享基底物件——這正是 virtual inheritance 的核心語意,過去只能在執行期驗證,現在編譯期也能驗證。論文另外指出,實作上真正麻煩的地方在 APValue 的物件表示法、建構/解構順序,以及常數求值裡如何計算動態型別,這些都要跟著補上,而不只是把禁止清單那一行拿掉那麼簡單。
影響範圍
受影響最直接的是標準庫實作者:libstdc++、libc++、MSVC STL 若打算把 std::ios_base、串流類別或 <chrono> 的剖析型別 constexpr 化,之前繞不過的虛擬繼承路障現在打通了,但仍要照論文提到的方向去補齊 APValue 相關的常數求值邏輯,不是換個編譯器版本就自動生效。
其次是用鑽石繼承做編譯期物件模型的函式庫作者——例如需要在 constexpr 環境下建構、比對共享基底位址的框架、或想在編譯期跑單元測試的程式庫。這些專案原本得靠組合(composition)或手動去虛擬化來繞過限制,現在可以檢查 __cpp_constexpr_virtual_inheritance >= 202506L 再決定要不要啟用真正的虛擬繼承路徑。
要注意的是,這是一篇面向 CWG 的措辭提案(wording-only),論文本身沒有寫出是哪一次全會(plenary)投票併入標準,只確認了 R2 是定案版本;實際支援時程仍要看各編譯器何時實作論文提到的 APValue、物件建構順序與動態型別計算這幾塊。在那之前,寫可攜程式碼時,虛擬繼承 + constexpr 仍應該用 feature test macro 保護,而不是假設所有 C++26 編譯器都已經到位。
對想在編譯期跑單元測試、或用 constexpr 做靜態驗證的測試框架作者來說,這條限制解除後,過去只能寫成「執行期才驗證得到」的多型物件圖,現在可以整個搬進 static_assert 裡跑;不用再為了繞開 virtual base 限制,額外維護一份 constexpr 專用、拿掉虛擬繼承的簡化物件模型。