- はじめに — 全会一致ではなかったC++26
- 契約とは何か — 3分でわかる要約
- 核心の設計 — 検査するかどうかはソースコードが決めない
- 反対派の論証 — ヘッダのinline関数一つ
- 賛成派の反論 — 機能的安全と言語安全は別物である
- コナで直ったこと、そして最後まで入らなかったこと
- コンパイラの現実 — GCC 16.1一つ
- では、いつ使い、いつ使うべきでないか
- おわりに
- 参考資料
はじめに — 全会一致ではなかったC++26
2026年3月28日土曜日、英国ロンドンのクロイドンで、ISO C++委員会はC++26の技術作業を終えました。夏のCD(委員会原案)国際投票から上がってきた411件の国家機関(NB)コメントを最後まで処理したのち、最終承認投票(DIS)に送る文書を確定しました。
ところが、最後の本会議採決は全会一致ではありませんでした。
賛成114、反対12、棄権3
ハーブ・サター(Herb Sutter)はクロイドン・トリップレポートで、この非全会一致は「主に契約に対する根強い懸念のため」だと書いています。比較対象があります。2025年2月、ハーゲンベルクで契約をC++26作業草案に入れた際の採決は賛成100、反対14、棄権12でした。1年のあいだに反対は14から12へとほぼ変わらない一方、棄権は12から3へ減りました。サターの解釈はこうです — 棄権が異例なほど少ないのは、賛成・反対どちらであれ大半の専門家が自分の技術的判断にいまや確信を持っている、ということだ。
標準機能がこの段階まで来て二桁の反対票を受けるのは珍しいことです。何があったのか、そして今この機能が使えるのかを整理していきましょう。
なお本稿では、サターのトリップレポートは採決数の出典としてのみ使います。彼は契約賛成派で(P3911R2の共著者でもあります)、解釈やフレーミングは当事者のものです。論証そのものは両陣営が書いた原論文から直接引いています。
契約とは何か — 3分でわかる要約
契約(contract assertion)は、関数の事前条件・事後条件と、本体中間の単言を言語構文で書く機能です。形は3種類あります。
// 事前条件: 呼び出し側が守るべきこと
int divide(int a, int b)
pre (b != 0);
// 事後条件: 関数が保証すること。戻り値に名前を付けられる
int abs_value(int x)
post (r: r >= 0);
// 本体内の単言文
void process(std::span<int> data) {
contract_assert(!data.empty());
// ...
}
Cのassertマクロと比べると違いは明確です。assertはプリプロセッサマクロなので関数宣言には付けられません。事前条件は本体にしか書けず、ヘッダしか見ない呼び出し側には見えません。契約は宣言に付くのでインターフェースの一部になり、ツールが読み取れ、事後条件は戻り値を名前で参照できます。
ここまでは異論がありません。論争は次の点から始まります。
核心の設計 — 検査するかどうかはソースコードが決めない
C++26標準草案の[basic.contract.eval]は評価セマンティクスを4つ定義しています。
ignore— 何も効果がないobserve— 検査し、失敗すればハンドラを呼び、ハンドラが戻れば実行を継続するenforce— 検査し、失敗すればハンドラを呼び、戻れば実行を終了するquick-enforce— 検査し、失敗すればハンドラなしで即座に終了する
observe・enforce・quick-enforceが検査セマンティクス(checking semantics)で、enforce・quick-enforceが終了セマンティクス(terminating semantics)です。そして次の文がこの機能の性格を決めます。
It is implementation-defined which evaluation semantic is used for any given evaluation of a contract assertion.
どの契約単言が実際に検査されるかは実装定義です。ソースコードに書かれたものではなく、コンパイラとビルドフラグが決めます。直後の注はさらに踏み込みます。
The evaluation semantics can differ for different evaluations of the same contract assertion, including evaluations during constant evaluation.
同じ契約単言でも、評価するたびにセマンティクスが変わりうるということです。標準は「推奨慣行(Recommended practice)」として、全てをignoreに翻訳する選択肢と全てをenforceに翻訳する選択肢を提供し、デフォルトはenforceであるべきだと書いていますが、これは推奨であって要求ではありません。
さらにもう一段あります。
The evaluation A of a contract assertion using a checking semantic determines the value of the predicate. It is unspecified whether the predicate is evaluated.
検査セマンティクスを使っても述語(predicate)が実際に評価されるかどうかは未規定です。標準自身が挙げる例がこれです。
struct S {
mutable int g = 5;
} s;
void f()
pre ((s.g++, false)); // #1
void g() {
f(); // #1が検査セマンティクスを使っても s.g の増加が起きないことがある
}
副作用のある述語を使うと、その副作用は起きることもあれば起きないこともあります。実装は同じ値を出しつつ副作用のない代替評価を行ってもよいのです。
賛成派の論文P3846R0は契約を3つの性質で定義します — プログラムの正しさに対して冗長(redundant)であり、独立に検査可能であり、評価セマンティクスがソースコードとは独立に決まる。3つ目が意図された設計です。バグではありません。
反対派の論証 — ヘッダのinline関数一つ
反対派論文のタイトルは遠回しではありません。P3835R0「Contracts make C++ less safe -- full stop」、ジョン・スパイサー(EDG)、ヴィレ・ヴォウティライネン、ホセ・ダニエル・ガルシア・サンチェス、2025年9月3日。論証の核心はコード例一つに集約されます。
// z.h — サードパーティライブラリのヘッダ
inline void f(int x) {
contract_assert(x > 0);
}
// l1.cpp — ライブラリ実装。quick_enforceでコンパイルされる
#include "z.h"
void g() {
f(0); // 契約違反が検出されることを期待
}
// c1.cpp — クライアントコード。ignoreでコンパイルされる
#include "z.h"
extern void g();
int main() {
f(1);
g();
}
ライブラリは自分のコードが常にquick_enforceで検査されるようビルドしました。クライアントはignoreでビルドします。fはinline関数なのでODR上は定義が一つしか残りません。そのためリンク後に実際どのセマンティクスが適用されるか — ライブラリが意図した検査が生き残るかどうか — は、コンパイラがl1.cpp内で呼び出しをインライン化したか、リンカがどのTUのコピーを選んだかに左右されます。論文の文言をそのまま訳すと、プログラマにはどの呼び出しにどのセマンティクスが適用されるか知る方法がありません。
この問題は、ヘッダに契約単言を書くinline関数やテンプレート定義があればいつでも起こります。つまりヘッダオンリーライブラリ全般です。モジュールもこの問題を解決しないと論文は書いています。結論はこうです。
Contracts reduce the safety of C++ and should be removed until a version that is strictly an improvement in safety is provided.
同じ方向の論文はほかにもあります。P3829R0「Contracts do not belong in the language」(デイヴィッド・チザル、スパイサー、ガブリエル・ドス・レイス、ヴォウティライネン、ガルシア)、そしてP3573R0「Contract concerns」にはビャーネ・ストロヴストルップとダヴィデ・ヴァンデヴォールドが名を連ねています。軽い反対ではなかったということです。
国家機関コメントも付きました。P3846R0の付録がまとめた契約関連のNBコメントは17件で、スウェーデン・スペイン・米国・フランス・ルーマニア・チェコ・フィンランドの7つの国家機関から来ています。そのうちスペインのES-046はこう要約されます — 重大な検査が除去されうるうえ、新種のサプライチェーン攻撃を可能にするセキュリティ脆弱性が開く。複数の件が明示的にC++26からの削除を求めていました。
この「サプライチェーン攻撃」という表現が誇張に聞こえるなら、P3829R0が挙げる例を見るほうがいいでしょう。P3846R0がまとめたとおりに訳すとこうです — ある関数が一方のTUではquick_enforceで、別のTUではignoreでコンパイルされる。quick_enforce側を最適化する際、GCCは契約検査が強制されると仮定し、呼び出し側のnullチェックを除去する。ところがリンク時点でignore版が選ばれることがある。すると契約検査もなく、その検査を信じて消したnullチェックもありません。検査が一つ消えるのではなく、二つ消えます。P3829R0はこれをP2900の設計上の欠陥と位置づけ、解決には(1)混在モードをODR違反にする、(2)コンパイラの中間表現を大きく変える、(3)中核的な最適化を諦める、のいずれかが必要だと主張します。
これには賛成派からの反論があり、公平に伝える必要があります。P3846R0は、EWGがハーゲンベルクで混在モードに関する懸念を検討した末に契約のためにODRを変えることを拒否したこと、そしてGCCのその手続き間最適化の問題はリフレクターで「契約とは無関係のコンパイラバグ」と判定され、再現コードがGCCに報告されたと書いています。つまり賛成派はこれを言語設計の欠陥ではなく実装バグと見ています。
賛成派の反論 — 機能的安全と言語安全は別物である
賛成派の応答がP3846R0「C++26 Contract Assertions, Reasserted」です(2025年10月6日)。ティムール・ドゥムラー、ジョシュア・バーン、ガシュパー・アジュマンを筆頭に22名が署名しており、その中にはGCCのジェイソン・メリルとイアン・サンド、libc++のルイ・ディオンヌ、そしてClang実装を担当したエリック・フィセリエもいます。論文は17の「懸念」を一つずつ論駁します。
核心の反論は用語を切り分けるところから始まります。
- 機能的安全(functional safety) — プログラムが害を及ぼさず意図した機能を果たす可能性
- 言語安全(language safety) — 未定義動作の不在、あるいはその不在を証明できる能力
契約は前者のためのものであって後者のためのものではない、というのが論旨です。単言は検査されればバグを捕らえ、無視されればランタイム効果なしに意図を文書化する。セマンティクスを外部から設定できる能力は欠陥ではなく、広く採用されるための前提条件だとしています。
TU間のセマンティクス不一致(P3835R0の例)への回答はこうです。
Mixing translation units compiled with different build flags is an inevitable consequence of the C++ compilation model. (...) With C assert, mixed mode makes programs IFNDR, and with P2900, the behaviour is limited to one of the semantics incorporated into the program, which is a significant improvement. (...) The worst case is that an assertion will go unchecked, which by design cannot introduce a new bug into an existing program.
つまり最悪の場合でも単言が検査されないまま素通りするだけで、それは設計上、既存プログラムに新しいバグを持ち込まないということです。そしてプログラム全体に均一なセマンティクスを要求すれば「大規模には使えない機能」になると論文は釘を刺します。ヘッダのfが性能重視のループで使われるならignore以外は現実的でないことがあり、検証が薄い上位コードではquick_enforceが適切なこともある — この可変性を支えられない単言機能は役に立たない、という理屈です。
preとpostが常に検査されるよう意味を変える提案は、すでに東京会議で採決にかけられ強い合意で否決されたことも論文は指摘します。代わりに「必ず検査」を表現する経路としてラベル(P3400R1)をC++26以降の拡張として提示します。
両陣営の主張を並べると、実は事実として争っている点は狭いのです。振る舞いそのものには異論がありません。検査が消えうることも、ソースで強制する方法がないことも両陣営とも認めています。分かれるのは、それが「安全機能ではない」なのか「安全を損なう」なのかです。
コナで直ったこと、そして最後まで入らなかったこと
2025年11月のコナ会議で、委員会は月・火の2日間を契約フィードバックに充てました。結果はこうです。
契約は維持しつつバグを2つ修正する — 強い合意でした。採択された修正の一つがP3878R1「Standard library hardening should not use the 'observe' semantic」で、著者はヴォウティライネン、ジョナサン・ウェイクリー、スパイサー、ステファン・T・ラバヴェイです。反対派が書いたバグ修正論文が採択されたわけで、これは反対が政治ではなく技術だったことの傍証でもあります。
対照群があります。同じ会議でtrivial relocatability(P2786)は間に合わせて直せない決定的バグが見つかり、C++26から削除されました。委員会に機能を削る意志がなかったから契約が残ったのではないということです。同じ週に一方は削り、一方は残しました。
そして入らなかったものがあります。コナで委員会はP3911R0「[basic.contract.eval] Make Contracts Reliably Non-Ignorable」の最初の解法に近いものを3月会議まで推進することにしました。ルーマニアのNBコメント(RO 2-056)への応答として、ソースコードに「この単言は必ず強制されなければならない」と書ける構文を入れようというものでした。提案された形はこうです。
int divide(int a, int b)
pre! (b != 0); // 提案された構文: 常に強制される。C++26には入らなかった
論文はR2まで進み(2026年1月14日)、著者にアンドレイ・アレクサンドレスクとハーブ・サターが加わりました。ところが現行の標準草案の構文にはこれがありません。
precondition-specifier:
pre attribute-specifier-seq_opt ( conditional-expression )
postcondition-specifier:
post attribute-specifier-seq_opt ( result-name-introducer_opt conditional-expression )
[dcl.contract.func]の構文にpre!はなく、[basic.contract]のどこにも「non-ignorable」という語はありません。C++26の__cpp_contracts値はP2900R14の202502Lのままです。サターのクロイドンレポートも「機能は追加も削除もされていない」と書いています。
まとめるとこうです。C++26には、ある契約検査が必ず実行されるようソースコードに書く方法がありません。反対派の論証の核心がそのまま残った状態で標準が出ていき、それが反対票12票の中身です。
加えてC++26契約には仮想関数に契約を付けられません。これはP3097R3「Contracts for C++: virtual functions」としてC++29草案に入り(GCC状況表基準で__cpp_contracts >= 202606L)、GCCはまだ実装していません。インターフェースに事前条件を書くという発想から仮想関数が抜け落ちているのは、かなり大きな穴です。
コンパイラの現実 — GCC 16.1一つ
ここが実務者にとって最も重要な部分です。cprefereenceのC++26コンパイラ対応表でContracts(P2900R14)の行を見ると、GCC欄に16と書かれており、Clang・MSVC・Apple Clang・EDG eccp・Intel C++の欄はすべて空です。
2026年7月現在、C++26契約を実装したリリース版コンパイラはGCC 16一つだけです。GCC 16.1は2026年4月30日にリリースされました。標準が確定してから一か月後です。
使い方はこうです。
# 契約は -std=c++26 だけでは有効にならない。-fcontracts が必要
g++ -std=c++26 -fcontracts main.cpp
# 評価セマンティクスの選択 (デフォルトは enforce)
g++ -std=c++26 -fcontracts -fcontract-evaluation-semantic=quick_enforce main.cpp
GCC 16マニュアルとソース中のオプション定義を突き合わせるとこうなります。
| フラグ | 値 | デフォルト |
|---|---|---|
-fcontracts | 契約機能を有効化 | オフ |
-fcontract-evaluation-semantic= | ignore / observe / enforce / quick_enforce | enforce |
-fcontracts-client-check= | none / pre / all — 呼び出し側検査の挿入 | none |
-fcontracts-definition-check= | on / off — 被呼び出し側検査 | on |
-fcontracts-conservative-ipa | 手続き間最適化を制限 | オン |
-fcontract-checks-outlined | 検査を別関数に分離 | オフ |
小さな落とし穴が一つ — マニュアル本文は値をignored、observedと書いていますが、実際にコンパイラが受け付ける文字列はignore、observe、enforce、quick_enforceです(GCCソースgcc/c-family/c.optの列挙型定義基準、デフォルトInit(3) = enforce)。マニュアルをそのままコピペすると「unrecognized contract evaluation semantic」エラーに遭遇します。
違反ハンドラは次の関数を定義して差し替えます。
#include <contracts>
void handle_contract_violation(const std::contracts::contract_violation& v) {
// ロギングなど
}
この表で目を引くのは-fcontracts-conservative-ipaです。GCCの文書の説明をそのまま訳すとこうです — 「inline関数の本体があるTUで見えるとき、手続き間解析が処置を取るのを防ぐ。契約評価の条件がTUごとに異なりうり、そのような処置が誤りうるからだ」というものです。そしてデフォルトでオンになっています。
前の節で見たP3829R0のnullチェック除去シナリオがまさにこれです。反対派が「TUごとにセマンティクスが異なりうるので、それを前提に最適化するのは危険だ」と述べたその性質が、GCCでは手続き間最適化をデフォルトで保守的にするフラグとして具体化されています。-fcontract-disable-optimized-checksの説明に「検査段階が意図せず除去(elision)されるのを避けられる最適化」という表現が出てくるのも同じ文脈です。
これをどちら側の証拠と読むかは分かれます。反対派は「言語がコンパイラにこの負担を課している」と読み、賛成派は「実装が負担できる問題であり、実際に負担している」と読みます。ただ一つ確かなことがあります — この論争は抽象的な哲学論争ではなく、デフォルトでオンになっているコンパイラフラグとして存在するものだということです。
なお同じGCC 16はリフレクション(P2996R13)も実装していますが、そちらは-freflectionが別途必要です。そして未初期化読み取りの未定義動作をなくしたP2795R5(erroneous behavior)もGCC 16に入りました — これは契約と違って論争がなく、再コンパイルするだけで恩恵を受けられます。
では、いつ使い、いつ使うべきでないか
使うべきでない場合から。
- 信頼境界の検証に使わないでください。これが最も重要です。ユーザー入力、ネットワークから来たデータ、権限チェック — こういうものは契約で書いてはいけません。
ignoreでビルドすれば消えますし、ヘッダのinline関数であれば意図とは無関係に消えることがあります。信頼境界の検査はifと明示的なエラー処理で書いてください。P3846ですら「無視できない検査のための仕組みはすでにある — たとえばif文だ」と書いています。これは反対派の主張ではなく、賛成派論文の文です。 - 副作用のある述語を使わないでください。評価されるかどうかは未規定です。
- 移植性が必要なコードには使わないでください。2026年7月現在、GCC 16専用の機能です。ClangもMSVCもリリース版にはありません。
- 仮想関数インターフェースにはまだ使えません。C++29を待つ必要があります。
- 述語の中では外側の変数は
constになり、thisはconstポインタになります(const化)。非constな操作を呼ぶ検査はそのままではコンパイルが通りません。
価値がある場合。
- 社内コードベースで事前条件をインターフェースに文書化する用途。ヘッダだけを見て契約がわかるというのは
assertにはできなかったことで、これはignoreでビルドしても残る利益です。 - デバッグ・テストビルドで
enforce、リリースではignoreにする典型的なパターン。これはassertでやっていたことをより良い構文でやるだけのことで、実際に初期採用の大半はここになるでしょう。 - 単一コンパイラ(GCC)でビルドすることが確定している内部サービスで、全TUを同じセマンティクスでコンパイルする場合。P3846が「自然な初期戦略」と呼んでいるのがまさにこれです — TUごとに単一セマンティクスでコンパイルする。混在モードを使わなければ、P3835R0の問題はそもそも起きません。
つまりビルド全体を自分で管理していれば契約はうまく動きます。問題が起きるのは、サードパーティライブラリをヘッダとして取り込んだ瞬間、そしてライブラリ作者が「私の検査は常にオンでなければならない」と言いたくなった瞬間です。C++26はその主張をソースコードに書く構文を与えませんでした。
おわりに
まとめるとこうです。C++26契約は2026年3月28日、賛成114、反対12、棄権3で標準に残りました。20年の議論とSG21の5年の作業、そしてC++20で一度外れた経緯の末に出た結果です。機能そのものは本物です — 宣言に付く事前・事後条件はassertにはできなかったことをこなし、4つの評価セマンティクスは実務の多様な要求を反映しています。
同時に反対派の論証も本物です。検査が実行されるかどうかをソースコードが決められず、ヘッダのinline関数ではどのセマンティクスが適用されるかプログラマにはわかりません。これはバグではなく意図された設計であり、だからこそ「直る」性質のものではありません。ソースに強制を書く構文(P3911)は3月まで推進されましたがC++26には入らず、ラベル(P3400)はC++26以降の課題として残りました。
だからこの機能に対する向き合い方ははっきりしています。契約は安全機能ではなく、バグ検出ツールです。賛成派ですらそう言っています — 機能的安全のためのものであって、言語安全のためのものではないと。この区別を見落として「C++26は契約で安全になる」と読むと、まさに反対派が警告した仕方で痛い目に遭います。無視されうる検査を信頼境界に置くことになるからです。
そして2026年7月現在、使いたくてもGCC 16.1が唯一の選択肢です。サターはC++26の採用が速いと予想していますが、契約に関する限り今できることはGCC 16で実験してみることまでです。移植可能なプロダクションコードに入れるのは、ClangとMSVCが追いついてからの話です。
参考資料
- C++26 is done! — Trip report: March 2026 ISO C++ standards meeting (London Croydon, UK) — Herb Sutter — 採決数(114/12/3、100/14/12)とNBコメント411件の出典
- Trip report: November 2025 ISO C++ standards meeting (Kona, USA) — Herb Sutter — 契約維持の決定、P3878R1、trivial relocatabilityの削除
- P3846R0 — C++26 Contract Assertions, Reasserted (Doumler, Berne, Ažman 他19名) — 賛成派による17の懸念への応答、NBコメント付録
- P3835R0 — Contracts make C++ less safe -- full stop (Spicer, Voutilainen, Garcia) — inline関数の例
- P3829R0 — Contracts do not belong in the language
- P3911R2 — Make Contracts Reliably Non-Ignorable (Neațu, Alexandrescu, Teodorescu, Nichita, Sutter) — C++26に入らなかった
pre!提案 - [basic.contract.eval] — 標準草案の評価セマンティクス規定
- [dcl.contract.func] — 契約指定子の構文
- C++ Standards Support in GCC — Contracts P2900R14 → GCC 16、
__cpp_contracts >= 202502L - GCC 16 Release Series — Changes および GCC C++ Dialect Options — 契約関連フラグ
- GCC Releases — GCC 16.1は2026年4月30日リリース
- Compiler support for C++26 — cppreference — GCC 16以外は未対応
현재 단락 (1/133)
2026年3月28日土曜日、英国ロンドンのクロイドンで、ISO C++委員会はC++26の技術作業を終えました。夏のCD(委員会原案)国際投票から上がってきた411件の国家機関(NB)コメントを最後ま...