以下の内容はhttps://onihusube.hatenablog.com/entry/2026/03/01/204210より取得しました。


[C++]WG21月次提案文書を眺める(2025年10月)

文書の一覧

全部で74本あります。

もくじ

N5021 2026-11 Búzios Meeting Information (rev. 1)

2026年11月にブラジルのブジオスで行われる全体会議のインフォメーション。

以前のもの(N5020)の更新版のようです。

N5022 WG21 agenda: 3-8 November 2025, Kona, HI, USA

2025年11月にコナで行われる会議のアジェンダ。

N5023 2025-11 Kona meeting information (rev. 1)

2025年11月にハワイのコナで行われる全体会議のインフォメーション。

以前のもの(N4977)の更新版のようです。

N5024 2026-03 London meeting information

2026年03月にイギリスのロンドンで行われる全体会議のインフォメーション。

予定(2026年03月23日~28日)と場所、ホテルの案内などが記載されています。

N5027 2025-10 WG21 admin telecon meeting (revised 2025-09-29)

11月の全体会議に先立って行われる、WG21管理者ミーティングの案内。

N5028 C++26 CD summary of voting and comments

C++26 CD(N5013)に対して提出されたNBコメントをまとめた文書。

P1708R10 Basic Statistics

標準ライブラリにいくつかの統計関数を追加する提案。

以前の記事を参照

このリビジョンでの変更は

  • 統計量のデータ型を変更するためのオーバーロードを削除
  • 平均と分散(および標準偏差)を返す関数は、それらを専用の構造体で返すようになった
  • 尖度関数はその引数(sampleとexcess)を個別の値ではなく構造体として取るようになった
  • アキュムレータオブジェクトはより検討を行うために将来の提案に延期する
  • P2681から共分散の計算を導入

などです。

P2034R5 Partially Mutable Lambda Captures

ラムダ式の全体をmutableとするのではなく、一部のキャプチャだけをmutable指定できるようにする提案。

以前の記事を参照

このリビジョンでの変更は、実装経験の報告について追記したことです。

報告によると、GCCにおいては実装のための変更は大きくなく、短時間で完了したとのことです。

P2728R8 Unicode in the Library, Part 1: UTF Transcoding

以前の記事を参照

このリビジョンでの変更は

  • トランプの例を追加
  • utf-iteratorからiterator_interfaceを削除
  • code unit viewをP3117のtransform_viewとexpression-equivalentなRangeアダプタ(クロージャオブジェクト)に置換
  • null_sentinelnull_termをP3705に移動
  • std::uc名前空間を削除し、std::ranges/std::ranges::viewsに置換
  • ライブラリのEBを削除
  • イテレータを親のビューへのポインタを持つように変更し、借用とスタックサイズの二次増加を排除
  • charwchar_tのサポートを削除
  • 提案文書のほとんどの部分を書き直し、図を追加

などです。

このリビジョンでは提案内容そのものは大きく変わっていませんが、ユニコードの構造の説明や現在利用可能なAPIについての説明や、提案している設計の説明などが図やサンプルコードとともに追加されています。

P3086R5 Proxy: A Pointer-Semantics-Based Polymorphism Library

静的な多態的プログラミングのためのユーティリティ、"Proxy"の提案。

以前の記事を参照

このリビジョンでの変更は

  • IntroductionとMotivationを改訂
    • 型理論の観点から提案を提示
    • ポインタ中心のモデルを明確にし、現在の仮想ディスパッチとの関係を明確にした
  • 実装経験に基づく用語定義と理論的根拠において、以下の機能を統合
    • substitution_dispatch, proxy_viewobserver_facade
    • weak_proxyweak_facade
  • access_proxyを削除
  • 仮想関数、スマートポインタベースのラッパ、カスタムvtableベースの設計との比較分析を行い、委員会からのフィードバックに対応
  • リファレンス実装には存在するものの標準化は提案されていないヘルパマクロへの依存を削除し、提案するインターフェースを明確化

などです。

Better Lookups for map, unordered_map, and flat_map

連想コンテナからキーによって要素を検索するより便利な関数の提案。

以前の記事を参照

このリビジョンでの変更は

  • P2988(std::optional<T&>)がドラフトに統合されたことを反映して提案を更新
  • 説明を連想コンテナ要件セクションに移動することを推奨する編集コメントを追加
  • unordered_map::getに複雑度について追記

などです。

P3099R0 User-defined error messages for contract assertions

契約アサーションにユーザーがエラーメッセージを指定できるようにする提案。

C++26 Contractsでは、3種類の契約アサーションはいずれも契約条件式のみを取り、契約違反が発生した場合はデフォルトの違反ハンドラによって何かしらの診断メッセージが発行されます(おそらく、違反を起こしたアサーションのソースコード位置と条件式の文字列化などの情報が出力される)。

これらのアサーションにユーザーが追加のエラーメッセージを指定できると、出力されたエラーメッセージからその契約違反が発生した原因や解決方法などを素早く理解するために役立ちます。Cのアサートもそのような機能を持っていませんでしたが、assert(expr && "Reason")の様なワークアラウンドによって実質的に同じことが可能でした(これは契約アサーションでも可能です)。また、多くの非標準のアサーションライブラリではそのような追加の診断メッセージ指定をサポートしている場合がほとんどです。

また、C++26 Contractsの場合は違反ハンドラのカスタマイズによってそのようなエラー情報の扱いをユーザーが変更することができるため、違反ハンドラからでもユーザー指定のメッセージにアクセスできる必要があります。

ClangによるC++26 Contractsの先行実装では、ベンダー属性を利用してこれをサポートしています。

T& operator[] (size_t i) 
  pre [[clang::contract_message("Out-of-bounds access")]] (i < size()); 

この提案は、このclangの実装経験を参考にしつつ、契約アサーションにおいてユーザー指定のエラーメッセージをサポートしようとするものです。

提案では、エラーメッセージの指定方法はstatic_assertと構文的にも意味的にも同等なものになるようにしています。

T& operator[] (size_t i)
  pre (i < size(), "Out-of-bounds access");

3種類のアサーションすべてで、contract_assert(expr, message)のように契約条件式の後にエラーメッセージを指定します。この構文は、static_assertと一貫することに加えて条件式の前にメッセージが来ない事や構文ノイズが少ないことなどから選択されています。

このエラーメッセージはコンパイル時の文字列である必要があり、C++26のstatic_assertのエラーメッセージとして有効な文字列と同じものを受け付けます(文字列リテラルだけでなく、コンパイル時std::stringなどを渡せる)。実行時文字列をサポートしようとすると、契約違反が起きてから違反ハンドラが呼び出されるまでの間にユーザーコードを実行する必要性が生じてしまい、これはセキュリティリスクの懸念があるためここではサポートを延期しています(P3819R0のcontract_violation::evaluation_exception()と同じ理由)。

違反ハンドラの引数であるstd::contracts::contract_violationオブジェクトからは.message()という新しいAPIによってこのメッセージを取得できるようにします。

namespace std::contracts {
  class contract_violation {

    ...

    const char* message() const noexcept; 
    
    ...
  };
}

デフォルトの違反ハンドラおよび定数式における契約違反時の診断メッセージについては、このユーザー指定エラーメッセージを含めるようにすることを推奨事項としています(現在でも診断メッセージの出力は推奨事項)。

この提案によって、static_assert(expr, message)は定数評価中にenforceセマンティクスで評価されたcontract_assert(expr, message)と同等になるようになります。また、contract_assert(false, message)とすることで定数評価中に任意のメッセージを出力できるようにもなり、P2758R5で提案されている機能の一部を包含してもいます。

P3255R2 Expose whether atomic notifying operations are lock-free

std::atomicの通知・待機系関数がロックフリーとは限らない場合がある問題について修正する提案。

以前の記事を参照

このリビジョンでの変更は

  • 新しく見つかったaddress-freedom問題への対応のためにSG1に戻した
  • ターゲットをC++29へ変更
  • 提案の一部がDRであることを明確化し、文言を分割
  • 既存標準ライブラリ実装の説明を追加
  • 標準に従って、操作がロックフリーであるという意味についての説明を追加
  • address-freedom問題に関する説明を追加
  • ロックフリー操作をアドレスフリーにするという推奨はatomicの通知・待機操作には適用されないことを明確化

などです。

このリビジョンでは結局次の事を提案しています

  • std::atomic_flagの通知・待機操作は、既存の慣例に従ってロックフリーである必要がないことを明確化する
    • これは欠陥報告(DR)
  • is_lock_free, is_always_lock_free, std::atomic_is_lock_freeは、既存の慣例に従ってatomicの通知・待機操作には関連しないことを明確化
    • これもDR
  • std::atomic_flag, std::atomic, std::atomic_refに対して次のメンバを追加する。これらの値はatomicの通知・待機操作がロックフリーであるかどうかを示す
    • .notify_is_lock_free()
    • std::atomic_notify_is_lock_free()
    • ::notify_is_always_lock_free
  • std::atomic_flag, std::atomic, std::atomic_refに対して次のメンバを追加する。これらの値はatomicの通知・待機操作がシグナルハンドラ内で(UBなしで)使用できるかを示す
    • .wait_is_signal_safe()
    • std::atomic_notify_is_signal_safe()
    • ::notify_is_always_signal_safe
  • ロックフリーな通知操作がアドレスフリーであることを期待しないものとするように、標準規格における推奨プラクティスを更新

このリビジョンで言及されるようになったアドレスフリー(address-freedom)とは、アトミック操作の実装がアトミックオブジェクトのアドレスに依存しないようになっていることを言っており、標準規格においては推奨プラクティスとしてロックフリー操作がアドレスフリーであることを推奨しています。

しかし実装を調査したところ、atomicの通知・待機操作はロックフリーであってもアドレスフリーにならない場合があり、特に通知操作はそうならないであろうことが判明しました。ただしアドレスフリーとシグナルセーフはまた直交した概念であるため、アドレスフリーであってもシグナルセーフは達成できる場合があり、このために専用のクエリを導入しています。

P3351R3 views::scan

状態を持つ関数を使用可能なviews::transformであるRangeアダプタ、views::scanの提案。

以前の記事を参照

このリビジョンでの変更は

  • C++29をターゲットに変更
  • partial_sumエイリアスを削除
  • Categoryのセクションに、イテレータのスタッシュ、ムーブのみの型、scan_viewにおける無条件input_rangeと条件付きforward_rangeの選択に関する議論などを追加
  • scan_viewを条件付きborrowed_rangeとすることの実現可能性に関する議論を追加
  • Beman Projectにおける実装経験を追加
  • P2846R6が導入されたWDにリベース
    • LWG4189が採択されたため、freestandingセクションを削除
  • その他編集上の修正

などです。

P3385R6 Attributes reflection

リフレクションにおいて、エンティティに指定されている属性の情報を取得・付加できるようにする提案。

以前の記事を参照

このリビジョンでの変更は

  • P3678R0を統合
  • appertainを削除
  • has_attributeメタ関数を削除

などです。

P3401R1 Proxy Creation Facilities: Enriching Proxy Construction for Pointer-Semantics Polymorphism

Proxyを構築するユーティリティ関数の提案。

以前の記事を参照

このリビジョンでの変更は

  • オブザーバー型または所有型の監視を通して許容可能なターゲットとしてラップできる値型と、既にプロキシ可能なポインタlike型などを区別するために、proxiable_targetコンセプトを追加
  • テンプレートパラメータ制約を強化し、以前は単にclass Fだったところをfacade Fにした
  • 共有所有権の作成(allocate_proxy_shared, make_proxy_shared)と非所有ビューの作成(make_proxy_view)を追加
  • フリースタンディング対応の明確化
    • 割り当てが必要な関数以外はフリースタンディングで利用可能
  • ill-formed条件を、置換エラーではなくproxiable_target / inplace_proxiable_targetを使用して指定する

などです。

P3411R4 any_view

viewを型消去するためのviewany_viewの提案。

以前の記事を参照

このリビジョンでの変更は

  • デフォルト構築されムーブされたオブジェクトは空のビューに割り当てられるようにした

などです。

P3505R1 Fix the default floating-point representation in std::format

std::format()における浮動小数点数出力時のデフォルトの表現(gオプション)を修正する提案。

以前の記事を参照

このリビジョンでの変更は

  • 下限値と上限値は引数の型の値として表現可能な最も近い値であることを文言で明確化
  • 文言では絶対値を使用する
  • PythonとC++で同じ数値を使用することで、前者は簡潔性("shortness")の要件を満たし後者は満たさないことを明確化

などです。

LEWGのレビューにおいては、この提案の内容をDR(to_chars()関連はC++17、format関連はC++20)とすることで合意が取れているようです。

P3567R2 flat_meow Fixes

flat_xxxな連想コンテナに対するバグフィックス提案。

以前の記事を参照

このリビジョンでの変更は

  • 文言の修正

などです。

この提案は2025年11月の全体会議で承認され、C++26に採択されています。

P3578R1 What is "Safety"?

安全性(Safety)という言葉の定義を明確にし、その使用方法に注意を払う事を推奨する提案。

以前の記事を参照

このリビジョンでの変更は

  • “Grandma Safety”というフレーズを削除し、表現を簡素化

などです。

P3584R1 Proxy Facade Builder: Enriching Facade Construction for Pointer-Semantics Polymorphism

P3086R3からfacade_builderというクラステンプレートだけを導入する提案。

以前の記事を参照

このリビジョンでの変更は

  • basic_facade_builderの単一のテンプレート引数proxiable_ptr_constraints(集成体型NTTP)を明示的な個別のNTTPに分割(MaxSize, MaxAlign, Copyability, Relocatability, Destructibility
  • relocatabilityのデフォルトをtrivialに変更
    • nothrowを暗黙的に指定していた文言はそのまま
  • add_facadeのセマンティクス改訂
    • add_facadesubstitution_dispatch(オプトインのbool値)を介して置換のための直接的な規約を挿入するようになった
  • basic_facade_builder::add_skillを追加
    • ボイラープレートの削減とファサードのモジュール化の促進における役割を説明
  • 冪等性ルールを明確化
  • 後方互換性と最小限のバイナリサイズのバランスを取るために、明示的に置換をオプトインする根拠を説明

などです。

P3603R1 Consteval-only Values and Consteval Variables

consteval変数の提案。

以前の記事を参照

このリビジョンでの変更は

  • 実装経験の追加
  • not_fnの例を追加
  • 文章の拡張
  • リフレクションの採用に伴う文言の更新

などです。

P3606R1 On Overload Resolution, Exact Matches and Clever Implementations

関数テンプレートを含めた関数オーバーロードの解決において、GCCの実装戦略を標準化する提案。

以前の記事を参照

このリビジョンでの変更は

  • 提案する文言の追加

などです。

P3643R2 std::to_signed and std::to_unsigned

整数型を対応する符号付/符号なしの整数型に簡易に変換する関数の提案。

以前の記事を参照

このリビジョンでの変更は

  • §2.2. Dual purpose を追加
  • §4. Design と §5. Possible implementation の順番を変更
  • §4. Design に制約についての議論を追加

などです。

P3655R3 cstring_view

null終端文字列専用のstd::string_viewの提案。

以前の記事を参照

このリビジョンでの変更は

  • revision historyを追加
  • "discussion on type", "design rationale", "historical information", "wording"の順に並べ替え
  • 以前のアンケートセクションの削除
  • SG23/LEWGのフィードバックに従い、提案の範囲を絞り込み
  • Beman Projectにおける実装経験について追加
  • コンストラクタの説明を拡充
  • nullptrコンストラクタを削除
  • 空コンストラクタを追加
  • コンストラクタが変換演算子よりも優先されるため、string_viewcstring_viewの関係を書き換え
  • 文言を拡充

などです。

P3663R3 Future-proof submdspan-mapping

submdspan()submdspan_mappingを呼び出す場合について、将来的な拡張を考慮しておくようにする提案。

以前の記事を参照

このリビジョンでの変更は

  • 著者リストを更新
  • LWGレビューによる文言の改訂
    • 文言はより数学的な表現になり、実装の説明をあまり行わなくなった
    • 以前のリビジョンで提案されていた説明専用関数や既存の説明専用関数は、リネーム・置換または削除された
    • “Unit-stride slice”はレイアウトマッピングではなくIndexTypeにのみ依存し標準スライス型にのみ適用されるように再定義
  • 文言の変更を反映するために、それ以外のセクションを更新

などです。

この提案は2025年11月の全体会議で承認され、C++26に採択されています。

P3666R1 Bit-precise integers

C23の_BitIntをC++に導入する提案。

以前の記事を参照

このリビジョンでの変更は

  • N3699の公開に伴い、§4.5. _BitInt(1) を更新
  • §5.11. make_signed and make_unsigned を追加し、対応する文言を変更
  • 列挙型の基底型として_BitIntを許可
    • §4.3. Underlying type of enumerations を参照
  • 基底型として_BitIntを持つ列挙型の昇格を考慮
  • § [diff.lex] Annex C に0wbの型の違いに関するエントリを追加

などです。

P3669R3 Non-Blocking Support for std::execution

operation_stateの開始操作がブロッキングするかどうかを取得するインターフェースを追加する提案。

以前の記事を参照

このリビジョンでの変更は

  • 別の例を追加
  • 実装リンクを追加
  • 文言の修正
  • 命名に関する議論を追加
  • P0260の変更を削除

などです。

P3684R0 Fix erroneous behaviour termination semantics for C++26

erroneous behaviourにおける終了動作についての調整を行う提案。

EBについては以前の記事を参照

最終的なC++26仕様においてEBの動作としては、実行時にEBが起こる場合に実装は診断メッセージを発行したうえでその後(未規定の時間経過後)プログラムを終了する、という動作が許可されています。これにより、EBが発生するとその後プログラムはエラー状態になり、その後任意の時点で突然終了する可能性があります。

C++26のEB(未初期化変数読み取り)に関する現在の実装には2つの方向性があります

  1. 自動変数をゼロないし固定のパターンを持つ値に初期化することでその読み取り時(EB時)に安全な値を読み出すようにする
    • GCC/Clangが-ftrivial-auto-var-initを介して提供している
    • この実装では未定義動作は排除されるものの、推奨されるEBの診断動作を全く行わない
  2. EBとなる未初期化変数読み取りを実際に検出する
    • Memory Sanitizerによる実装
    • EB(未初期化変数読み取り)の発生後しばらく経つと診断メッセージを発行して終了する

C++コードが最適化後の機械語命令列にどのようにマッピングされるかは直観的に理解しにくい場合が多いため、未初期化変数読み取りの様なチェックは通常コンパイル後のコードへの検出機能の組み込みによって行われます(Sanitizerがまさにそうです)。

特に、未初期化変数読み取りが発生する場所はその後に続く計算処理に巻き込まれることで、ソースコード上で特定できる場所よりも後の計算処理内部であることがあり、診断とプログラム終了もその場所で行われることになります。このような実装戦略を反映しているのがC++26の規定にある遅延終了セマンティクスです。

ただし、Memory Sanitizerの実装はEB発生後その特定を無限に遅延するわけではありません。あくまで、実際のソースコード位置よりもかなり離れた場所で検出と診断・プログラム終了が行われる可能性がある、というだけです。Memory Sanitizerはその実装として、未初期化変数読み取りの結果未初期化である値が原因として異なる動作をした評価を捕捉することに重点を置いています。

そのため、Memory Sanitizerの動作を適合実装として維持するためにEB発生後のプログラムがいつでもそのEBを根拠として終了できる許可を与える必要はありません。現在の規定では、EB発生とその検出後にタイマーを設定し任意の時間経過後に突然プログラムを終了するような異常な実装は不必要に許可されています。このような動作はソフトウェアの予測可能性と信頼性の向上に対して逆行する行いであり、考慮する必要はありません。

また、P3100R4では未初期化変数読み取り以外のUBについて、その妥当なデフォルト動作をEBとして置換しようとしていますが、これらのUB(EB)の場合は遅延終了が意味を持たないものがあり、遅延終了を許可することでEBの結果として生じる誤った値(Erroneous Value)とP3100R4で提案されている暗黙の契約違反との間の意味の一貫性の確立が妨げられます。

この提案では、このような問題の解決のためにEB発生後の遅延終了を削除することを提案しています。その代わりに、EBによって生成されるErroneous Valueがそれを使用する演算等を介して伝播するようにしています。

結果がErroneous Valueに依存するような操作はすべてErroneous Valueを生成するため、そのような操作はすべてEBとなります。実装は最初にEBが起きた場所ではなく、このようにErroneous Valueの伝播とともに発生するEBのどこかでEBの検知とプログラム終了という動作を取ることができます。これはMemory Sanitizerの実装動作ですが、GCC/Clangの実装動作のように最初に読みだすErroneous Valueを特定の値に初期化し、あとはそのまま続行するという動作も引き続き許可されます。

現在の実装ではErroneous Valueを読み取るとその値はクリーンアップされて有害ではなくなるというアプローチをとっているものの、その代償にプログラム全体が有害な状態になってしまっています。Erroneous Valueがデータとともに伝播するようにすることで、問題のあるデータの範囲をプログラム全体からErroneous Valueを扱うプログラムの一部に限定し現実的な実装をカバーするのに十分な程度にErroneousな範囲を広げつつ、EB発生後任意のタイミングでの突然の終了の様な動作を拒否することができます。

すなわちこの提案によって、EBの発生したプログラムはエラー状態に陥り予期しない終了の脅威に晒され続ける、ということは無くなります。ただし、Memory Sanitizerの実装のように、最も都合の良いタイミングでEBを検知し終了するための最大限の柔軟性を提供しています。

この提案の後では、EBについて次のような性質が実現されます

  • Memory Sanitizerなど、未初期化の値の使用をトラップする既存の実装は適合実装のまま
    • 何らの変更を加えることなく現在の動作を維持できる
  • プログラムがEBによって終了した場合、プログラム開始から終了までの間でEBを発生させた可能性のある実行の全てを特定することなく、終了の理由を局所的に推論できるようになる
  • プログラムが実行状態にあり、EBを発生させるような操作を行っていない場合、それより以前のEBによって終了しないことが保証される

この提案については、対応するNBコメントが提出されているようです。

P3688R4 ASCII character utilities

ASCIIに関連する文字の判定・処理関数群を提供する提案。

以前の記事を参照

このリビジョンでの変更は

  • get_hex_digit_value()の例のバグを修正
  • §3.12. namespace ascii に設計に関する説明を追加

などです。

P3695R2 Deprecate implicit conversions between char8_t and char16_t, char32_t, or wchar_t

ユニコード文字型の間の暗黙変換を非推奨にする提案。

以前の記事を参照

このリビジョンでの変更は

  • char8_twchar_tの間の変換も非推奨にする
  • この方向性を反映してタイトル等を変更
  • §2.1. It's not hypothetical. This really happens のトートロジーに関する警告の注記を拡充
  • §3.6. Why not make these conversions narrowing? を追加
  • 文言の編集

などです。

P3702R3 Stricter requirements for document submissions (SD-7)

HTML形式の提案文書について一定のルールを設ける提案。

以前の記事を参照

このリビジョンでの変更は

  • 文書のアクセシビリティ要件を明確化(WCAG AA)
  • must を shall に変更
  • UTF-8エンコーディング要件を should から shall に変更
  • サポート対象フォーマットから、markdownとプレーンテキストを削除
  • その他の文言の修正

などです。

P3724R1 Integer division

商と剰余を様々な丸めモードで計算するライブラリ関数の提案。

以前の記事を参照

このリビジョンでの変更は

  • §4.6. SIMD overloads を追加
  • N5014にリベース

などです。

P3732R1 Numeric Range Algorithms

<numeric>ヘッダにある数値アルゴリズムのRange版を追加する提案。

以前の記事を参照

このリビジョンでの変更は

  • reduce_into, sum_into, product_intoアルゴリズムを追加
  • 文章の修正
    • reduce_into, transform_reduce_into, sum_into, product_into, dot_intoを特殊なケースとして説明
    • identity value(単位元に対応する値)を指定するための様々な設計の提示
    • reduce_firstは不要であると結論付ける

などです。

reduce_into, transform_reduce_intoranges::reduce/ranges::transform_reduceと同様に動作するものの、結果の値を返すのではなく出力範囲の最初の要素に書き出すものです。そして、sum_into, product_into, dot_intoは同名のranges::reduce/ranges::transform_reduceラッパに対するreduce_into, transform_reduce_intoのラッパです。

namespace std::ranges {

  // reduce_intoの宣言例
  template<sized-forward-range IR,
           sized-forward-range OR,
           class T = range_value_t<IR>,
           indirectly-binary-foldable<T, iterator_t<IR>> F>
  constexpr auto reduce_into(
    IR&& in_range,
    OR&& out_range,
    T init,
    F binary_op) -> /* see below */;
}

出力は範囲かつsized_rangeとして取っていますが、書き出すのはその最初の要素のみです。

P3733R1 More named universal character escapes

名前付きユニバーサル文字名のエイリアス名について、C++で使用可能ではないものを使用できるようにする提案。

以前の記事を参照

このリビジョンでの変更は

  • §3.3. Category stability guarantees を追加
  • この提案をDRとする根拠を追記

などです。

この提案は、SG16およびEWGにおいてDRとすることに合意が取れています、

P3737R1 std::array is a wrapper for an array!

std::arrayの実装自由度を制限する提案。

以前の記事を参照

このリビジョンでの変更は

  • §4. Design considerations で全体的な設計戦略を策定
  • array<T, 0>::front()/array<T, 0>::back()に関する変更を削除
    • LWG4276の採択を仮定している
  • ビットフィールドメンバをstd::arrayから削除
  • std::array<T,0>のトリビアルコピー可能性とトリビアルデフォルト構築可能性を無条件にした

などです。

P3739R3 Standard Library Hardening - using std::optional<T&>

std::inplace_vectorにおいて、std::optional<T&>を利用して堅牢化モードを導入する提案。

以前の記事を参照

このリビジョンでの変更は

  • 歴史的な議論に文脈を追加
  • Frequently Asked Questions セクションを追加

などです。

P3779R1 reserve() and capacity() for flat containers

std::flat_map等のflatなコンテナに、reserve()capacity()メンバ関数を追加する提案。

以前の記事を参照

このリビジョンでの変更は

  • 提案する文言を追加

などです。

P3786R1 Tuple protocol for fixed-size spans

固定サイズstd::spanにタプルプロトコルサポートを追加する提案。

以前の記事を参照

このリビジョンでの変更は

  • フィードバックにより設計上の問題を修正

などです。

P3800R0 2025-07 Library Evolution Poll Outcomes

2025年7月に行われた、LEWGにおける投票の結果。

次の提案が投票にかけられ、C++29に向けてLWGに転送されました。

また、投票の際に寄せられたコメントが記載されています。

P3804R0 Iterating on parallel_scheduler

parallel_schedulerの設計を洗練させるための提案。

P2079R10で提案されC++26にマージ済みのparallel_schedulerは、投入された処理の並行実行をサポートするschedulerです。提案段階でもかなり時間をかけて議論されていたのですが、WDへ入った後にも設計の懸念がいくつかあるようで、この提案はそれを改善することを目指すものです。

ここで提案されているのは次のことです

  • 軽微な設計の修正
    1. receiver_proxy::try_queryconst修飾できる
      • 現在されていないが、できない理由はない
    2. receiver_proxyには仮想デストラクタは必要ない
      • 多態的に破棄されることがないため
    3. receiver_proxy::try_queryはストップトークンとしてinplace_stop_tokenしか受け付けず、任意のストップトークンを使用できない
      • inplace_stop_tokenとその他の任意のストップトークンの間に実装定義のマッピングをサポートすることを推奨し、そのようなマッピングが存在する場合はクエリが成功するようにする
      • このようなメカニズムは他のプロパティにも役立つ可能性がある
    4. receiver_proxy::try_queryがサポートするプロパティのリストが実装定義となっていること
      • これは次のユースケースのサポートを意図したもの
        1. 実装者がサポートするプロパティを追加したい場合
        2. 実装者が特定のプロパティをサポートしたくない場合
      • プロパティのリストを厳密に規定してしまうとどちらもサポートできなくなってしまうため、この提案では現状維持を推奨している
  • より影響の大きい設計上の懸念事項
    1. receiver_proxy::try_queryでは、サポートされるクエリのリストを定義する必要がある
      • parallel_schedulerのバックエンドが個別のライブラリとして別にコンパイルされる場合に、receiverのプロパティ(環境とそのクエリ)を解決する方法がない
      • 現在のところそのような実装は確立していないため、現状維持
  • 命名について
    1. system_context_replaceabilityは置換可能性APIが含まれる名前空間に使用する適切な名前ではない
      • parallel_schedulerは以前はsystem_schedulerという名前で、system_context_replaceabilityはその時の名残が残っている
      • いくつか名前の候補を提示してLEWGに委ねる
  • 文言の不足
    1. parallel_schedulerにおけるbulk_unchunked/bulk_chunkedのカスタマイズに関する文言が十分に明確ではない
      • bulk_unchunked/bulk_chunkedのカスタマイズが必須と指定されているがその詳細は説明されていない
      • また、カスタマイズ部分では実行ポリシーが考慮されていない
      • P3862R0を基礎とするソリューションを構築する(後のリビジョン?

この提案はC++26に間に合わせようとはしていませんが、関連するNBコメントが提出されているようです。

P3810R1 hardened memory safety guarantees

標準ライブラリ内の基本的な操作について、未定義動作の余地がないことを規定しておく提案。

以前の記事を参照

このリビジョンでの変更は

  • コピペによるエラーを修正

などです。

P3824R1 Static storage for braced initializers NBC examples

初期化子リストの補助配列に対して、静的記憶域期間を義務付ける提案。

以前の記事を参照

このリビジョンでの変更は

  • インライン参照をより明確化
  • 提案する文言の追加

などです。

P3826R0 Defer Sender Algorithm Customization to C++29

senderアルゴリズムのカスタマイズをC++29まで延期する提案。

C++26では、std::executionと呼ばれる並行・非同期処理の構成や実行制御のためのフレームワークとなるライブラリが導入されています。std::executionでは、実際の非同期処理を構成するためにsenderアルゴリズムというものが用意されており、これはsenderという抽象によって表現される非同期処理のより柔軟な構成をサポートするための共通部品となるものです(STLにおけるイテレータに対するアルゴリズムとの対応から、senderアルゴリズムと呼ばれます)。

namespace ex = std::execution;

// scheduler: 処理の実行場所を指定する
ex::scheduler auto sc = ...;

// sender: 非同期処理を構成する
ex::sender auto async_work =
  ex::starts_on(sc, ex::just(10)) | 
  ex::then([](int n) {
    return n * n;
  }) |
  ex::then([](int n) {
    std::println("{}", n);
    return n;
  });

// receiver: 非同期処理の結果を受信するコールバック
ex::receiver auto re = ...;

// operation_state: 実行可能な状態の非同期処理
ex::operation_state auto os = ex::connect(async_work, re);

// 非同期処理の実行開始
ex::start(os);

// 終了の待機(receiver/operation_stateによって方法が異なる
re.wait();

// 結果の取得(receiverから取得する、receiverによって方法が異なる
auto n = re.result();

ここでのex::justex::thenなどがsenderアルゴリズムです。このほかにもいくつかのsenderアルゴリズムが用意されており、このように|によってチェーンすることで非同期処理の構成をサポートします。

これらのsenderアルゴリズムはそれぞれがCPOとして定義されており、ユーザーの用意したsenderによるカスタマイズをサポートしています。これはユーザーの実行環境や要件に対応したより効率的なsenderアルゴリズムの処理を可能にするための機構であり、ハードウェアベンダやその利用者が利用することを想定しています。

通常、senderアルゴリズムのカスタマイズはその目指す方向性からschedulerに対して行われますが、senderチェーンによる非同期操作ではschedulerは上から入れる(上記例のように)ことも、下から入れる(チェーンの下方、あるいはreceiver経由)こともできます。当初のsenderアルゴリズムは上からのカスタマイズしか考慮していなかったものの、下から入れる場合のカスタマイズ(Late customization)をサポートするために途中で変更されています(P2999R3)。

しかし、その設計はまだ安定しておらず、その後もsenderアルゴリズムのカスタマイズについては問題点がいくつか指定されています(P3718R0など)。これらの修正は可能ではあるもののその設計検討にはさらに時間をかける必要があり、C++26のタイムフレームの中で行うのは困難です(この提案の提出時点でC++26の機能追加フェーズは終わっています)。

そのため、この提案はsenderアルゴリズムのカスタマイズをC++26ではオフにしておいて、カスタマイズについてより堅牢な設計をC++29で再導入しようとするものです。

P3718R0(未適用)時点でのsenderアルゴリズムのカスタマイズではsenderアルゴリズムの呼び出し時(Early customization)とreceiverとの接続時(Late customization)の2か所でのカスタマイズの検出とディスパッチをサポートしています。前述のように、カスタマイズはschedulerに対して行われるため、senderアルゴリズムは自身がどこで(どのschedulerで)実行されて完了するかを知る必要があります。

しかし多くの場合、senderは自身がどこで開始されるかを知らない限り、どこで完了するかが分からない、という問題があります。例えば、ex::just()はどこで開始されてもその場所で完了するものの、どこで開始されるかは単にex::just(10)の様に呼び出された時点(Early customization時)には分からず、Late customizationを待つ必要があります。

また、ex::then(sndr, fn)の場合もそれは同様な場合があり(sndrによる、例えばex::just()の場合など)、その場合は呼び出し時点のカスタマイズは行われずLate customization時にreceiverの持つ情報に基づいて実行開始場所を取得し、カスタマイズが行われます。しかし、P3718R0で提案されているAPIではこの場合にこのような情報を取得することができない場合があります。

提案文書より、サンプル

// GPU実行scheduler
ex::scheduler auto gpu = ...;

// GPU上で実行したい処理
ex::sender auto sndr = ex::starts_on(gpu, ex::just()) | ex::then(fn);

// GPU上で実行し結果を待機
std::this_thread::sync_wait(std::move(sndr));

gpuはGPU上で処理を実行するschedulerであり、fnはGPU上で実行されます。そのため、ex::thenはGPU実装のためのカスタマイズを利用する必要があります。P3718R0のAPIではそれは次のように判定されます

  • Early customization時、starts_on(gpu, just()) | then(fn)式の実行中、thenCPOはstarts_on(gpu, just())senderに次のように完了する場所をクエリする
auto&& tmp1 = ex::starts_on(gpu, ex::just());
auto dom1 = ex::get_domain(ex::get_env(tmp1));
  • それを受けてstarts_onsenderは次のようにjustsenderに完了する場所をクエリする
auto&& tmp2 = ex::just();
auto dom2 = ex::get_domain(ex::get_env(tmp2));
  • justの完了場所は開始場所が分かるまで分からないため、ここではその情報は入手できず、dom2default_domainとなり、これがstarts_onのドメインとしてdom1に返される
    • しかしこれは誤りであり、starts_onはGPU上で完了する
  • thenCPOはdefault_domainを利用してカスタマイズの検索を行い、デフォルト実装を使用する
  • thensendersync_waitreceiverに接続されると、Late customizationが試みられる
  • ex::connectsync_waitreceiverthensenderの開始場所を問い合わせる
auto dom3 = ex::get_domain(ex::get_env(rcvr));
  • sync_wait自体は現在のスレッドで開始するため、ここでもdefault_domainが返されて、dom3default_domainとなる
  • このドメインを使用してカスタマイズを検索するため、Late customizationでもthenのGPU実装は検索されない

このように、この例ではGPU上でのthenのためにデフォルト実装(実質的にCPU用のもの)が使用されることになりますが。これは望ましい状況ではありません。

このような問題の解決のための設計はおおむね完了しているようですが(提案のappendixで説明されています)、まだ実環境で動作したものではなく、精査が完了していないようです。このためDRプロセスに委ねることには不安があるようで(C++20 rangeの経験からの様子)、かといってC++26からstd::execution全体あるいはsenderアルゴリズムを削除することはC++における非同機構の導入と採択を実質的に3年遅らせることになります。

そのためこの提案では、senderアルゴリズムのカスタマイズ機構だけをC++26から削除して、それ以外の部分は維持することを提案しています。

これによりカスタマイズに関連する

  • default_domain
  • transform_sender
  • transform_env
  • apply_sender
  • get_domain

などは削除されます。

C++29で復帰する予定のカスタマイズにおいては、Early customizationを削除しLate customizationのみにしたうえで、senderの完了スケジューラ(完了する場所)の問い合わせ時に開始場所についての情報を一緒に渡すようにするようです。

例えば、現在のex::get_completion_schedulerクエリを拡張して、senderreceiver両方の環境を渡せるようにします。

auto sch = get_completion_scheduler<set_value_t>(get_env(S), get_env(R));

Late customizationは実質的にex::connectによって呼び出されるconnect操作のカスタマイズによって行われ、C++29ではex::connectは次の2種類のドメインを計算します

  • 開始ドメイン(Starting domain): get_domain(get_env(rcvr))
  • 完了ドメイン(Completion domain): get_completion_domain<set_value_t>(get_env(sndr), get_env(rcvr))

これらの情報を利用して、connectフェーズにおいて正確なカスタマイズの検出とディスパッチを行えるようになります。

P3828R0 Rename the "to_input" view to "as_input"

views::to_inputviews::as_inputにリネームする提案。

views::to_inputは入力のviewのカテゴリをinput_rangeに落とすだけのビューで、要素の変換を伴わないものです。しかし、to_~という名前がranges::toのように各要素がアクティブな変換を受けているように見えるとして、リネームしようとしている提案です。

リネーム先はviews::as_inputを提案しています。

この提案には関連するNBコメントがあるようで、C++26をターゲットにしています。LEWGのレビューにおいては一定のコンセンサスを得ており、適用されそうです。

P3834R1 Defaulting the Compound Assignment Operators

複合代入演算子をデフォルト実装できるようにする提案。

以前の記事を参照

このリビジョンでの変更は

  • 2番目の引数がconst&で渡される場合、それをムーブしないように変更
  • § 4.2 = default is a function body の議論を拡張し、hidden friendsのステータスを明確化
  • § 6.1 Defining operator+ in terms of operator+= にパフォーマンスに関する議論を追加
  • § 8 Implementation を追加
  • 提案する文言の追加

などです。

この提案はEWGのレビューにおいてリジェクトされているようです。

P3836R1 Make optional<T&> trivially copyable (US NB comment 134)

std::optional<T&>がトリビアルコピー可能であることを規定する提案。

以前の記事を参照

このリビジョンでの変更は

  • 共著者の追加
  • US NB comment 134 の参照を追加
  • 文言の書き換え

などです。

P3841R0 Proposal for std::constructor Function Object

コンストラクタを関数オブジェクトとして扱うためのユーティリティの提案。

メンバ関数やフリー関数等通常の関数は、<functional>にあるようなユーティリティやstd::functionとそのファミリーを使用するなどして様々に活用することができます。しかし、これらのものに対してコンストラクタを関数と同じように扱わせることはできません。

コンストラクタを関数のように扱うためにはラッパが必要であり、この提案ではstd::constructor<T>()としてそれを提案しています。

提案文書より、std::size_tの範囲inputがあり、その要素の値によるサイズのstd::vectorstd::vectorを得るような処理を書く例

std::vector<std::vector<int>> result;
result.reserve(std::distance(input));
for (auto sz : input) {
  result.emplace_back(sz);
}

inputinput_rangeである場合に2回のイテレーションが可能ではない場合があり、emplace_back()のループは範囲から直接構築するよりも非効率となります。

rangeベースの現代的なソリューション

auto result = input
    | std::views::transform([] (size_t sz) {
        return std::vector<int>(sz);
      })
    | std::ranges::to<std::vector>();

まだラムダ式が簡潔ではありません。

この提案

auto result = input
    | std::views::transform(std::constructor<std::vector<int>>())
    | std::ranges::to<std::vector>();

std::constructor<T>()はコンストラクタオーバーロードの集合をCallableに変換します。これによって、コンストラクタを関数オブジェクトとして扱うことができ、既に関数オブジェクトを扱うことのできる場所で使用できるようになります。

std::constructor<T>()の宣言例

namespace std {

  template <typename T>
  struct constructor {
    template <typename... Args>
    static constexpr T operator()(Args&&... args) noexcept(std::is_nothrow_constructible_v<T, Args...>)
    {
      return T(std::forward<Args>(args)...);
    };
  };
}

P3842R0 A conservative fix for constexpr uncaught_exceptions() and current_exception()

C++26では、uncaught_exceptions()current_exception()を定数式で使用できないようにしておく提案。

P3818とP3820で、uncaught_exceptions()current_exception()を単純にconstexpr指定してしまうと破壊的変更となることが報告されており、それぞれ異なる解決策が提案されています。

LEWGでの協議の結果、C++26ではこれらの関数からconstexprを外しておくことで問題が起こらないようにする、ことが決定されました。この提案はそのための文言を提案するものです。

C++26の期限もあるため、代替案の追加や緩和策の導入などはいずれも行われず、C++26では定数式で使用できなくするのみです。

P3843R0 Reconsider R0 of P3774 (Rename std::nontype) for C++26

std::nontypestd::constant_argにリネームしないようにすることを推奨する提案

この提案は、P3774R0で提案されていた(R1で撤回された)std::fnの方向性に賛同し、std::nontypestd::constant_wrapperとは明確に異なるものとしてその方向に進化させていこうとするものです。

経緯等はP3774R0を参照

std::constant_wrapperstd::nontypeはどちらもNTTPを利用したconstexpr関数引数を実現するためのソリューションですが、std::nontypeはほぼstd::function_refにおけるメンバ関数の適切なサポートのためにのみ使用されるものであり、std::constant_wrapperのように汎用的なものではありません。

しかし、std::constant_argという名前はstd::constant_wrapperと非常に良く似ており、一見するとどちらを使用するべきかが分からない所があります。定義などはstd::constant_argの方がシンプルなのでそちらを好んで使用する人もいるかもしれません。これにより、constexpr関数引数という問題に対するソリューションとなる語彙型が2つ存在することになります。そして、それら2つの型の間には一切の互換性がありません。

現在の命名(std::constant_arg)はこの問題について真剣に検討された上でのものではありません。

P3774R0では、std::fnという名前にリネームするとともに、関数呼び出し演算子を備えることでstd::constant_wrapperとは異なる役割を与えようとしていました。

std::fnはコンパイル時に既知の関数のラッパとして、std::function_refstd::nontypeと同様に使用できるほか、次のように他の場所でも使用できます

  • 連想コンテナの比較関数やハッシュ関数の指定のために使用できる
  • アルゴリズムに渡す述語などのcallableとして使用できる

そしてどちらの場合でも、関数ポインタのサイズオーバーヘッドを削減しながらインライン化を可能にします。

これらのことから、この提案ではP3774R0の方向性を再検討することを目指しています。ただし、std::fnという名前には異論もあったことなどから、リネーム先をstd::fnではなくstd::function_wrapperにしています。

P3844R0 Restore simd::vec broadcast from int

std::simdの暗黙変換が可能なブロードキャストコンストラクタを復帰させる提案。

Parallelism TSだったころのstd::simdでは、vec<float>() + 1の様な構築(右オペランド)が可能であり、これはブロードキャストコンストラクタにおいて要素型と異なる型の値(特に整数型の値)を意図的に受け入れるようにしていることによります。

このようなAPIの意図は、浮動小数点演算コードでは* 2のような整数定数倍において* 2.0のように記述するよりもそのまま* 2とすることが多いため、これをそのままstd::simdでもサポートするためです。

float f(float x) { return x * 2; }    // converts 2 to float (at compile time)
float g(float x) { return x * 2.; }   // converts x to double (at run time)
float h(float x) { return x * 2.f; }  // no conversions

これと同等の表現をサポートするために、std::simdのブロードキャストコンストラクタは当初、int型を特別扱いして暗黙変換(縮小変換)を許可するようにしていました。

// 当初のstd::simd
using floatv = std::experimental::native_simd<float>;

floatv f(floatv x) { return x * 2; }    // converts 2 to float and broadcasts (at compile time)
floatv g(floatv x) { return x * 2.; }   // ill-formed
floatv h(floatv x) { return x * 2.f; }  // broadcasts 2.f to floatv

このコードは最終的なC++26 std::simdにおいてそのまま動作しません。少なくともfに対しては変更が必要です。

using floatv = std::simd::vec<float>;

floatv f(floatv x) { return x * std::cw<2>; }

std::simdではこのように、整数定数の様なものを扱う際にはstd::constant_wrapperを使用する必要があります。しかし、これにはコンパイルのコストがかかるため、それが望ましくない場合は2.fのようにfloat値を直接使用することが推奨されます。

ただし、8bitや16bitの整数型を要素としている場合など、対応するリテラルが無い場合は明示的変換を使用する必要があります。

template <simd_integral V>
V f(V x) {
  return x + 1; // ill-formed for V::value_type = (u)int8_t, (un)int16_t, and uint32_t
}

しかし、明示的変換を行うと値を変更してしまうような変換が診断されなくなり、バグの元となります。

template <simd_floating_point V>
V f(V x) {
  return x + V(0x5EAF00D);
}

f(vec<double>()); // OK

// compiles but
// adds 99282960 instead of 99282957
f(vec<float>());

// compiles but
// adds infinity instead of 99282957
f(vec<std::float16_t>());

ここでも、x + std::cw<0x5EAF00D>のように書いておけばこのような変換はエラーになりますが、std::cwは冗長でコンパイルコストも高いため、実際にはほぼ使用されないと思われます。

この提案は、当初のstd::simdが持っていたブロードキャストコンストラクタでの整数型の特別扱いを復帰させて、std::cwを介さなくても同じ結果が得られるようにするものです。

上記の例のこの提案後

template <simd_floating_point V>
V f(V x) {
  return x + 0x5EAF00D; // std::cwも明示的変換も不要
}

f(vec<double>()); // OK

// ill-formed: uncaught exception on
// value-changing conversion
f(vec<float>());

// ill-formed: uncaught exception on
// value-changing conversion
f(vec<std::float16_t>());

このようなチェックはstd::cwによるconstexpr引数が無いと行えないため、C++26のstd::simdでは整数型の特別扱いを行うコンストラクタは削除された上でstd::cwを使うように変更されていました。

この提案では、constevalコンストラクタとコンパイル時例外によってstd::cwを使用しなくても同等のチェックとエラー化を達成できるため、それを提案しています。

constevalコンストラクタの追加については2つの方法を提示したうえで、算術型からの変換コンストラクタをconstevalにする方法を提案しています(もう一つはexplicitコンストラクタとコンセプトの制約順序によってconstevalコンストラクタの呼び出し機会を制限したうえで現在のコンストラクタを維持する方法)。

再現コードで示すと次のようになります

template <class From, class To>
concept simd-broadcast-arg = constructible_from<To, From>;

template <class From, class To>
concept simd-consteval-broadcast-arg
  = simd-broadcast-arg<From, To>
      && convertible_to<From, To>
      && is_arithmetic_v<remove_cvref_t<From>>
      && !value-preserving-convertible-to<From, To>;

template <class T>
class basic_vec {
public:
  template <simd-broadcast-arg<T> U>
  constexpr explicit(see below) basic_vec(U&&); // #1

  template <simd-consteval-broadcast-arg<T> U>
  consteval basic_vec(U&&); // #2
};

simd-consteval-broadcast-argsimd-broadcast-argを包摂していることによって、オーバロード解決においてより優先されます。value-preserving-convertible-to<U, T>は算術型UからTへの変換が値を保持しない変換となる場合のみ選択されるためのコンセプトです。これにより、暗黙変換/明示的変換の両方において、要素型への変換が縮小変換となるような場合はconstevalコンストラクタ(#2)が選択されます。

#2のコンストラクタ内では変換に際して値が失われる(変換後に等しい値にならない)場合に例外を送出することでコンパイルエラーになります。

値を保持する変換については#1を選択し、算術型以外の型に対しても#1が選択されます。

この提案はC++26を目指していますが、専用の例外型の導入(というかどのような例外を投げるか)についてはC++26では行わないようにしています(ここでの目的はコンパイル時の診断の向上であって、例外をキャッチして処理することを目指していないため)。

P3845R0 Make std::execution's monadic operations naming scheme consistent

std::executionのモナド的操作に該当する操作の名前を標準ライブラリの既存のものと一貫させる提案。

std::executionsenderアルゴリズムの中には、senderをモナドと見た時にモナドに対する典型的な操作に該当するものがいくつかあります。標準ライブラリ中ではstd::optional/std::expectedがモナド的な型であり、それらはよく似たモナド的操作のAPIを備えています。しかし、同じモナド的操作に対応するAPIでもこの両者の間で名前が大きく異なっています。

この提案は、既存のモナド的操作(ほぼstd::optional/std::expectedのモナド的操作API)にあわせて、std::executionの一部のsenderアルゴリズムの名前を変更しようとするものです。

名前の変更しているものとその変更先候補は次のものです

  • std::execution::then -> std::execution::transform
  • std::execution::let_value -> std::execution::and_then
  • std::execution::let_error -> std::execution::or_else_error
  • std::execution::let_stopped -> std::execution::or_else_stopped

execution::thenTで値完了するsenderT -> Uの関数fを受け取って、senderが値完了した場合にその結果にfを適用した値で完了するsenderを返すものです。これは、views::transformTの範囲とT -> Uの関数fを受け取って、各要素をfで変換したUの範囲を返す)、std::optional<T>::transformoptional<T>T -> Uの関数fを受け取って、optionalが値を持っている場合にその値にfを適用した結果を保持するoptional<U>を返す)、std::expected<T, E>::transformexpected<T, E>T -> Uの関数fを受け取って、expectedが正常値を保持している場合にその値にfを適用した結果を保持するexpected<U, E>を返す)と対応しており、これはモナドに対するmapと呼ばれる操作です。

execution::let_valueTで値完了するsenderT -> senderの関数fを受け取って、senderが値完了した場合にその結果にfを適用したsenderを返すものです。これは、std::optional<T>::and_thenoptional<T>T -> optional<U>の関数fを受け取って、optionalが値を持っている場合にその値にfを適用した結果のoptional<U>を返す)、std::expected<T, E>::and_thenexpected<T, E>T -> expected<U, E>の関数fを受け取って、expectedが正常値を保持している場合にその値にfを適用した結果のexpected<U, E>を返す)と対応しており、これはモナドに対するbindと呼ばれる操作です。

execution::let_errorEでエラー完了するsenderE -> senderの関数fを受け取って、senderがエラー完了した場合にその結果にfを適用したsenderを返すものです。execution::let_stoppedsendervoid -> senderの関数fを受け取って、senderが停止した場合にfを呼びだした結果のsenderを返すものです。これらは、std::optional<T>::or_elseoptional<T>void -> optional<T>の関数fを受け取って、optionalが無効値を保持している場合にfを呼びだした結果のoptional<T>を返す)、std::expected<T, E>::or_elseexpected<T, E>E -> expected<T, F>の関数fを受け取って、expectedがエラー状態の場合にそのエラー値にfを適用した結果のexpected<T, F>を返す)と対応しており、これらはモナドの失敗チャネルにおけるbind操作です。

ただし、senderはエラーと停止の2つの失敗チャネルを持っており、execution::let_errorexecution::let_stoppedはそれに対応しています。この2つに同じor_elseという名前を使うことはできないため、or_elseを基本にしつつもこの2つのチャネルに対応した命名にする必要があります。

この提案ではその命名として、or_else_erroror_else_stoppedを採用しています。

提案文書より、サンプルコード

変更前(現在)

// The whole flow for transforming incoming requests into responses
sender auto snd =
    // get a sender when a new request comes
    schedule_request_start(the_read_requests_ctx)
    // make sure the request is valid; throw if not
    | let_value(validate_request)
    // process the request in a function that may be using a different execution resource
    | let_value(handle_request)
    // If there are errors transform them into proper responses
    | let_error(error_to_response)
    // If the flow is cancelled, send back a proper response
    | let_stopped(stopped_to_response)
    // write the result back to the client
    | let_value(send_response)
    // done
    ;

変更後

// The whole flow for transforming incoming requests into responses
sender auto snd =
    // get a sender when a new request comes
    schedule_request_start(the_read_requests_ctx)
    // make sure the request is valid; throw if not
    | and_then(validate_request)
    // process the request in a function that may be using a different execution resource
    | and_then(handle_request)
    // If there are errors transform them into proper responses
    | or_else_error(error_to_response)
    // If the flow is cancelled, send back a proper response
    | or_else_stopped(stopped_to_response)
    // write the result back to the client
    | and_then(send_response)
    // done
    ;

P3846R0 C++26 Contracts, reasserted

NBコメント等で表明されているContracts機能への懸念に答える文書。

C++26の機能凍結とNBコメントフェーズへの移行と前後して、C++26 Contracts機能への懸念の表明や削除を求める提案やNBコメントが多数提出されています。

ただ残念ながら、それらのほとんどの主張は新しいものではなく、P2900(C++26 Contracts提案)の議論期間中に何度か提示されすでに議論されていることについてのものがほとんどです。

この文書は改めてそれらに答えるもので、最近(この提案の提出時点)提起されたそれらの異議・反対意見を整理したうえで、これまでどのように議論され対処されたかを説明するものです。

ここでは、異議・反対意見は次の17個に整理されています

  1. P2900は安全ではない / C++を安全ではなくする
  2. P2900は翻訳単位間で一貫したセマンティクスを提供しない
  3. P2900が依存関係管理に与える影響は不透明
  4. P2900はODRの精神に反している
  5. P2900はモジュールではうまく動作しない
  6. P2900は実装定義が多すぎる
  7. P2900はコンパイラがチェックできないガイドラインに依存している
  8. const化には問題がある
  9. グローバルな契約違反ハンドラには問題がある
  10. 連続性のある複数の契約アサーションをobserveセマンティクスで評価することは危険
  11. 例外を契約違反として扱うことは実装不可能
  12. P2900は静的解析をサポートしない
  13. P2900は複雑すぎる
  14. P2900には重要な機能が欠けている
  15. P2900は将来の望ましい機能追加を困難にする
  16. P2900は他の機能から構成される必要がある
  17. P2900は導入経験が不十分

また、appendixとしてこの提案で対処されるNBコメントをリストアップしています。

P3849R0 SIS/TK611 considerations on Contract Assertions

C++26 Contractsをホワイトペーパー等を経由するようにする提案。

この提案は、スウェーデンの国内委員会における調査や議論の結論を報告するものです。次のような事を報告しています

  • 契約機能に対する強い支持はほとんどない
  • 現在の契約機能について強い抵抗がある
    • 既存の提案やコミュニティの著名人が、現状の機能への問題を指摘している
  • 契約アサーションを正しく使用するにはガイドラインや追加のディレクティブが必要であり、そのような機能を言語に追加すべきではない
  • 評価セマンティクスが異なるモードでビルドされたバイナリが混合するケースにおいて、契約機能とその設定がどのように影響するか十分に調査されていない

結果として

  • WG21は、他の各国機関からのフィードバックを収集し、文書化された懸念事項に対処すること
  • WG21は、未解決の議論を関連する作業範囲内において建設的に解決するとともに、必要に応じて関連情報が容易に発見できるようにすること
  • WG21は、Contracts機能をホワイトペーパーに移行するとともにコンパイラベンダ等と協力して、標準化前に実装経験を積むこと

を要請しています。

この提案はEWGのレビューにおいて賛同を得られず、リジェクトされています。

P3851R0 Position on contracts assertion for C++26

C++26からContractsを削除する提案。

  • 十分な導入経験の不足
    • P2900の仕様は実装と展開の経験がほとんどない
  • 混合モードビルドの問題
    • ヘッダファイルで記述された契約アサーションを含むinline関数(関数テンプレートなども同様)は、翻訳単位によってチェックされるかが変わる可能性がある
  • 依存関係を持つアサーション
    • 例えば、ポインタのnullチェックを行うアサーションと続いてそれをデリファレンスするアサーションがある場合、終了しない評価セマンティクスでは後者のアサーションが未定義動作に陥る
  • 安全でない結果
    • 重要なチェックが省略される可能性がある
  • const
    • ほぼ同じコンテキストにあっても、契約アサーションの内外で同じ関数呼び出しに対して異なるオーバロードを選択しうる
    • コードの理解を困難にする
  • 関数ポインタと仮想関数のサポートが無いこと
    • 関数ポインタも仮想関数も多くのコードベースで使用されており、それらをサポートしないことは機能を導入する上での障害となる

これらの懸念は現時点では解消されていないため、C++26にはContractsを含めずにホワイトペーパーまたはTSに移行することを強く推奨しています。

議論の過程は明らかではありませんが、この提案はリジェクトされています。

P3853R0 A thesis+antithesis=synthesis rumination on Contracts

Contracts機能に関して、P3640のアプローチを再考し、ホワイトペーパーとして両者の対照実験をしてから標準に導入する提案。

P3640R0では契約アサーションのデフォルトの評価セマンティクスをenforce固定にしておき評価セマンティクスを可変にしないようにすることと、可変セマンティクスはラベルによって可能にするようにすることを提案しています。

この提案では、P3640のアプローチの場合デフォルトの構文がその動作の不確実さや複雑さを伴うことなく、シンプルに使用することができ、異なる翻訳単位でセマンティクスのモードが異なる場合のABIに関する問題や最適化に関する問題と無縁になるとして、このアプローチの採用を推奨しています。

void f(int x) pre(x >= 0);  // C++26時点
// P2900: 評価セマンティクスが柔軟であり、コンパイラオプションや違反ハンドラによって動作を調整可能
// P3640: 評価セマンティクスが固定

void f(int x) pre<somelabel>(x >= 0); // 将来の拡張
// P2900: 評価セマンティクスの固定など、評価セマンティクス等の振る舞いをより明示的に指定できる
// P3640: 評価セマンティクスの柔軟性など、振る舞いをより細かく制御できる

すなわち、pre, post, contract_assertといった最短の構文に対して、P2900は汎用的な機能を持たせようとしており、P3640はシンプルな機能を持たせようとしています。それにより、P3640では最短の構文の意味も理解しやすくなります。

さらに、デフォルトのセマンティクスをquick enforceにしておけば違反ハンドラが必要なくなり、最短の構文の単純さはさらに高まります。

このアプローチは、Fedoraとその派生となるLinuxディストリビューションにおいて配布されているC++プログラム/ライブラリのデフォルトの設定でもあります(コンパイラが提供する堅牢化機能を有効化して配布するのがデフォルトになっている)。これは数年前から行われていることですが、それらのディストリビューションのユーザーには受け入れられており、パフォーマンスや突然のクラッシュが問題になるようなことは無かったようです。

したがって、P3640のアプローチはすでに実装経験があり、既存の慣行の一つでもあります。

また、後からP2900の上にP3640を導入することは、互換性を破壊せずにはできません。

そして、P2900のContracts機能には実装経験や展開の経験が皆無であり、C++26でこれを導入することは、本番環境で試験的な機能の実験を行うようなものです。

最終的に次のようなことを提案しています

  • C++26からContractsを削除する
  • P2900とP3640の両方をホワイトペーパーまたはTSとして出荷し、この両方を切り替えることのできるメカニズムを提供する
    • おそらく、コンパイラオプション
  • 実装者や教育者の力を借りてこの試験的Contracts機能の対照実験を行い、実装と展開の経験を得る

P3640とP2900のどちらがユーザーにとって好ましいのかの判断も含めて、Contracts機能の実装経験を得ることを重視しています。

P3855R0 New Reflection metafunction - is_destructurable_type

P3856R0 New Reflection metafunction - is_destructurable_type

ある型が構造化束縛によって分解可能であるかを取得する関数の提案。

構造化束縛宣言の右辺において分解できる型は、配列型・タプルプロトコルを実装した型・すべてのメンバ変数が公開されている構造体、の3種類があります。配列型とタプルプロトコルを実装した型は既存のメタ関数によって判別することができますが、3つめのすべてのメンバ変数が公開されている構造体については簡単に判別可能ではありません。

すべてのメンバ変数が公開されている構造体と言っても、必ずしも集成体である必要はありません。また、すべてのメンバが公開されていても、基底クラスを持っていると分解できなくなる場合があります。そもそもすべてのメンバが公開されているという状態そのものも従来のメタ関数では判定することが困難でした。

C++26では静的リフレクションが利用でき、このような判別を比較的簡易かつ確実に行うことができるようになります。この提案は、リフレクションを利用した実装による型の構造化束縛可能性を判定する関数を標準ライブラリに導入する提案です。

提案されている関数はstd::meta::is_destructurable_type()という名前の関数です。これは型特性ベースの関数ではなく、リフレクションベースの関数です。

namespace std::meta {
  consteval bool is_destructurable_type(info type, access_context ctx);
}

判定したい型のリフレクションとアクセスコンテキストを渡して呼び出すことで、その型が構造化束縛可能かどうかをbool値で取得できます(構造化束縛可能であればtrueを返す)。

struct S { 
  int a;
  int b;
  int c;
};

struct C1 : S {
};

struct C2 : S {
  int d;
};

constexpr auto ac = std::meta::access_context::unchecked();

static_assert(is_destructurable_type(^^int, ac) == false);
static_assert(is_destructurable_type(^^S, ac));
static_assert(is_destructurable_type(^^C1, ac));
static_assert(is_destructurable_type(^^C2, ac) == false);
static_assert(is_destructurable_type(^^std::tuple<int, bool>, ac));

提案文書に実装例がありますがそれほど簡単に書けるものではなく、必要とされる割に実装が単純ではないことも標準ライブラリに追加する動機のようです。

// 提案文書から構造体のケースの判定部分を抽出してきた例
bool is_data_member_case(std::meta::info r, std::meta::access_context ctx) {
  // アクセス制御を無視するアクセスコンテキスト
  constexpr auto unchecked_ctx = std::meta::access_context::unchecked();

  // r(型のリフレクション)の全ての非静的データメンバのリフレクション
  auto nsdms = std::meta::nonstatic_data_members_of(r, unchecked_ctx); 
  {
    std::meta::info class_with_members = nsdms.size() > 0 ? r 
                                                          : std::meta::info{};
    // rの基底クラスをチェックする
    // 基底クラスを持つ場合は次のどちらかでなければならない
    // 1. rにはデータメンバが無い
    // 2. 非静的データメンバはすべて1つの同じ基底クラスのメンバである
    for (auto base : std::meta::bases_of(r, unchecked_ctx)) { 
      // 基底クラスの非静的データメンバのリフレクション
      auto base_nsdms = std::meta::nonstatic_data_members_of(type_of(base), unchecked_ctx); 
      
      // メンバ変数を持つ基底クラスは1つでなければならないこと
      // rがメンバを持つ場合はメンバを持つ基底クラスはあってはならないこと
      // をチェック
      if (base_nsdms.size() > 0) { 
        if (class_with_members != std::meta::info{} && 
            class_with_members != base)
        {
          return false; 
        }

        class_with_members = base;
      }
    } 
  } 

  // ctxのコンテキストにおいて非静的データメンバが全てアクセス可能かをチェック
  // アクセス可能ではないメンバがある場合、構造化束縛できない
  for (auto mem : nsdms) { 
    if (!std::meta::is_accessible(mem, ctx)) { 
      return false; 
    }
    // 匿名共用体メンバを持つ場合、構造化束縛できない
    if (std::meta::is_union_type(type_of(mem)) && !std::meta::has_identifier(mem)) { 
      return false; 
    }
  }

  return true;
}

この判定処理を見るだけでも、ユーザー定義構造体に対する構造化束縛可能性がかなり複雑な条件によって決定されることが分かると思います。

この提案ではさらに、主に次のような利点からC++29以降の型クエリは可能な限り型特性ではなくリフレクションメタ関数として実装されるべき、としています。

  • 単一のstd::meta::infoを取るシンプルで固定的なインターフェース
  • ほとんどの場合、テンプレートのインスタンス化とSFINAE関連のメモリ消費の増大を回避できる
  • 型以外のものもクエリの対象にできる
    • 値、メンバ変数、関数、関数引数、名前空間など
  • コンパイル時例外とmeta::exceptionによってエラーメッセージが改善される
  • アクセスコンテキストを使用することでアクセス制御が行われる

また、型特性とリフレクションメタ関数は異なる副作用を持つためそれぞれ独立して実装されるべきであり、リフレクションメタ関数はその実装に型特性を使用すべきではなく、型特性はその実装にリフレクションメタ関数を使用すべきではないとも推奨しています。

なお、この提案はP3855R0と全く同一の内容の様で、こちらをメインとして扱うようです。

P3857R0 Policy: A function named get should return only on success

標準ライブラリにおいて、get()という名前のメンバ関数は値を直接返すべきとする提案。

P3091R4では、std::optional<T&>を使用することでより便利に連想コンテナからキーによって要素を引き当てるAPIが提案されています。そこでは、keyによって要素を引き当てstd::optional<T&>で返す関数に対して.get()という名前が当てられています。

// std::mapに対するget()のシグネチャ例
constexpr optional<mapped_type&>       get(const key_type& x) noexcept;
constexpr optional<const mapped_type&> get(const key_type& x) const noexcept;

しかし、このようなエラーチャネルを持つ戻り値を返すことは、標準ライブラリの既存のget()という名前の関数とは動作が異なっており一貫していません。

標準ライブラリにおける既存のget()は失敗の可能性のある値を返さず、契約内で呼ばれた場合は常に成功するか、他の方法で失敗が判定できるかのどちらかになっています。P3091R4のget()はそれらとは異なっています。

この提案ではこの問題への対処として2つの提案を行っています

  1. P3091R4のget()lookup()へ変更する
  2. ポリシーの変更
    • get()という名前の関数はその通常の戻り値が暗黙的にオブジェクトの取得に成功したことを示す関数である必要がある
      • エラーステータスを返す可能性のある関数は別の名前を使用するべき

この2つの事は同時に解決せずとも独立して投票可能としています。

P3858R0 A Lifetime-Management Primitive for Trivially Relocatable Types

指定された場所でオブジェクトの生存期間を再開するstd::restart_lifetimeの提案。

C++26ではトリビアルリロケーションの言語とライブラリのサポートによって、従来リロケーション(relocation)として知られつつもUBの上で行われていた操作が言語による保証とライブラリサポートの下で安全に利用できるようになります(ライブラリサポートは最終的に削除されましたが)。

リロケーションは、ある場所に配置されてそこで生存期間が開始されているオブジェクトを別の場所に移動させる操作であり、その際ムーブとは異なり元のオブジェクトの生存期間を終了させます。これは、オブジェクトのムーブ+元のオブジェクトの破棄+移動先でのオブジェクトの生存期間再開の複合操作ですが、トリビアルリロケーションの場合この移動はmemcpyによるビットコピーで行うことができ、より効率的になります。

トリビアルリロケーションがC++23までの言語で安全に行えるのはトリビアルコピー可能な型に対してのみでしたが、多くのクラス型(多態的なクラスであっても)でも実質動作することが知られていました(例えば、std::vectorstd::unique_ptrなどでも)。もちろん従来これはUBだったのですが、典型的にはstd::vector-likeなクラスにおける配列の伸長時など、リロケーション的な操作が必要になる箇所で最適化として使用されていました。

C++26では、トリビアルリロケーション可能な型というものがトリビアルコピー可能な型とは別に定義され、それらの型ではリロケーション操作(ライブラリ関数を通したもの)が安全に行えることが保証されるようになっています。

(この提案時点の)C++26のライブラリサポートではstd::trivially_relocate()という関数によってトリビアルリロケーションを実行することができます。

// Fooはトリビアルリロケーション可能だが、トリビアルコピー可能ではない
class Foo { /*...*/ };

static_assert(
     std::is_trivially_relocatable_v<Foo>()
 && !std::is_trivially_copyable_v<Foo>()
);

void f() {
  alignas(Foo) char x1_buffer[sizeof(Foo)],
                    x2_buffer[sizeof(Foo)],
                    y1_buffer[sizeof(Foo)],
                    y2_buffer[sizeof(Foo)];

  // memcpyによるリロケーション、UBとなる
  Foo* x1 = new (x1_buffer) Foo();
  std::memcpy(&y1_buffer, x1, sizeof(Foo));
    Foo* y1 = reinterpret_cast<Foo*>(y1_buffer);
  
  y1->bar(); // UB

  // std::trivially_relocate()によるリロケーション
  Foo* x2 = new (x2_buffer) Foo();
  Foo* y2 = std::trivially_relocate(
    x2,
    x2+1,
    reinterpret_cast<Foo*>(&y2_buffer));
  
  y2->bar(); // OK
}

std::trivially_relocate()という操作は前述のように、memcpy+リロケーション元オブジェクトの破棄+リロケーション先オブジェクトの生存期間再開の3つの操作が複合したものです。このうちmemcpyとリロケーション元オブジェクトの破棄についてはユーザーサイドでも行う方法があります。一方、リロケーション先オブジェクトの生存期間再開に関してはユーザーサイドで行う方法がありません。

リロケーション的な操作はC++23以前の環境でも様々な場所で必要となり、このような場所では必ずしもstd::trivially_relocate()が利用できるわけではありません。

例えば次のようなユースケースが挙げられています

  • realloc
    • reallocを典型として、メモリアロケータライブラリにおいては特定のメモリブロックのサイズを変更しようとする再割り当て機能が提供されている
    • これらの処理では、指定されたメモリをインプレースで伸長するか、新しいメモリ領域を確保して元のメモリの内容をコピーし、元のメモリを開放する
    • これらrealloc的な操作の後で、そのメモリ領域に配置されていたオブジェクトの生存期間を再開する方法がないため、このような場所ではトリビアルコピー可能な型のみで効率的なリロケーションが行える
      • realloc的な操作はあくまでメモリしか扱わないため、その領域のオブジェクトの管理は利用者が行う必要があるが、realloc的な操作内部でmemcpyが実行されていることによってstd::trivially_relocate()を利用できない
  • シリアライズ
    • インメモリデータベースや階層型キャッシュシステムにおいて、データをメモリとディスクの間で頻繁にリロケーションする必要がある
    • このユースケースのためのトリビアルリロケーションについてのライブラリ機能は十分ではなく、トリビアルコピー可能な型にのみ制限される
  • 特殊なmemcpy実装
    • std::memcpyよりも高速なmemcpy独自実装を利用する場合、std::trivially_relocate()内でのmemcpyを置き換える方法がない
    • cudaMemcpyでCPU-GPU間でオブジェクトを転送する際も同様の問題がある
  • Rustとの相互運用
    • Rustのムーブが実質的にトリビアルリロケーションであることで、RustとC++の双方からアクセス可能なオブジェクトを安全に扱うにはトリビアルリロケーション可能な型にのみ制限するか、ヒープに確保する必要がある
    • これは、パフォーマンスの劣化および使用する際の手続きの煩雑化の問題がある

これらにおいてはいずれも、memcpy部分を独自で行っていることによって複合操作であるstd::trivially_relocate()を使用することができません。そして複合操作のうちリロケーション先オブジェクトの生存期間再開の方法が無いことによって、トリビアルコピー可能な型のみでしかトリビアルリロケーションを利用できません。

この提案は、このリロケーション先オブジェクトの生存期間再開を行うライブラリ関数を追加することで、これらのユースケースにおいてより多くの型でトリビアルリロケーションを可能にしようとするものです。

提案されている宣言

namespace std {

  template<class T>
  T* restart_lifetime(void* p) noexcept;

  template<class T>
  volatile T* restart_lifetime(volatile void* p) noexcept;
}

std::trivially_relocate()そのものもこれを使用して記述することができます

template<class T>
  requires /* ... */
T* trivially_relocate(T* first, T* last, T* result)
{
  std::memcpy(result, first, (last-first)*sizeof(T));

  for (size_t i = 0; i < (last-first); ++i) {
    std::restart_lifetime(result[i]);
  }
}

このstd::restart_lifetimeによってstd::trivially_relocate()に複合されていた操作が単離され、ユーザーはオブジェクトの値表現のコピー方法によらずにトリビアルリロケーションを安全に利用できるようになります。

GPUメモリとの間でクラスオブジェクトをやり取りする例

void * host_buffer = ...;
void * device_buffer = ...;

// ホスト側で`Foo`オブジェクトを作成
Foo* x = new (host_buffer) Foo{};

// それをGPU側に転送
cudaMemcpy( device_buffer,
            host_buffer,
            sizeof(Foo),
            cudaMemcpyHostToDevice );

// host_bufferを他の目的に再利用する

// `Foo`オブジェクトをGPUからホスト側に戻す
cudaMemcpy( host_buffer,
            device_buffer,
            sizeof(Foo),
            cudaMemcpyDeviceToHost );

// ホスト側で生存期間を再開する
x = std::restart_lifetime<Foo>(host_buffer);

// ... continue using *x

std::restart_lifetimeの実装はほとんどのプラットフォームにおいてno-opとなるはずです。現在その例外として知られているのがARM64e環境で、Pointer Authentication Codeと呼ばれる機能によって多態的な型においてvtableのポインタ署名の修正が必要になることが知られています。std::trivially_relocate()はその関数内でそれを処理する機会を提供することができる点も利点でしたが、std::restart_lifetimeでも同様にポインタの再署名処理の機会を提供することができ、ARM64e環境ではそのための処理が必要になります。

このARM64e環境における安全な実装の検討のために提案の提出が遅れていたようですが、現在のところセキュリティリスクのない実装が可能であり実装上の問題は無いとしています。

この提案はC++26に対するバグ修正であるとして、C++26に導入することを推奨しています。とはいえ、この記事執筆時点では導入されていません。

P3859R0 Assertions are not necessarily for changing program behavior

契約アサーションの周辺ツールに対する情報提供手段としての側面を説明する提案。

この提案は、C++に契約アサーションを導入するモチベーションの一つは、実行時の契約チェックを行うことよりもコンパイラ以外のツールに対してプログラムの正確性を判断する情報を提供する標準的な方法を用意することにあるとして、P3835R0などの批判に回答するものです。

現在のC++には関数契約についての情報をコードで記述する標準的な方法は存在していません。Cのアサートはその立ち位置に近いところにありますが、事前条件と事後条件という追加の情報を適切に表明することができていません。これによって、関数契約に基づいた静的解析やプログラム検証を行う外部ツールはC++のコードを理解したうえでより詳細に解析をする必要があり、難易度が高くなるとともに誤検知の可能性が高くなります。結果として、C++に対するそのようなツールはあまりメジャーではありません。

Contracts機能では標準的に統一された方法によって、関数の事前条件と事後条件を記述する方法を提供します。これらのアサーションは、それが実行時にどのような振る舞いをするかとは無関係に、C++標準の外にあるツールにとってプログラム中の状態やその仮定に関する情報を取得する標準的な方法として有用となります。

C++26 Contractsの評価セマンティクスがignoreのみで出荷されていたとしても、この標準化された表記法が存在するだけで外部ツールやC++開発者への情報伝達の手段として有益なものとなります。

このような観点からは、契約アサーションは周辺のツールや人間が明確にそれと認識できるマーカーである必要があります。すなわち、P2900と同じように動作するアサーション相当のものが書かれていても、それがより汎用的な言語機能の応用の一つとして記述されている場合、そのアサーション相当のものが提示しているのが関数契約であるかどうかあいまいになる可能性があります。

P3829R0ではまさにそのように、より汎用的な言語機能による構成によってContracts機能を実現しようとしています。これは、ツールや人が明確に関数契約を認識することのできるマーカーとしてのアサーションの役割を軽視しており、ここで説明されているようなメリットが得られなくなります。

また、提案では改めて、契約アサーションの評価セマンティクスの選択に関して標準内で厳格に規定していないことについて、なぜそうしているのかの説明を行っています。

P3860R0 Proposed Resolution for NB Comment GB13-309 atomic_ref is not convertible to atomic_ref

std::atomic_ref<T>においてstd::atomic_ref<const T>への変換コンストラクタを追加する提案。

C++26では、P3323R1の採択によってstd::atomic_ref<T>Tconstを付加することができるようになっています。しかし、std::atomic_ref<T>からstd::atomic_ref<const T>への変換はできませんでした。

これはおそらく単に見落としであるため、そのような変換を許可する変換コンストラクタを追加する提案です。

int main() {
  int n = 0;

  std::atomic_ref<int> ar1{n};         // ok
  std::atomic_ref<const int> ar2{ar1}; // この提案以前はできなかった
  std::atomic_ref<int> ar3{ar2};       // ng、これは許可されない
}

これはC++26へのNBコメントを受けて、その解決のための提案です。そのため、C++26がターゲットです。

P3861R0 Pragmatic approach to standard structural types

std::string_viewstd::spanstd::tupleを構造的な型とする提案。

NTTP(定数テンプレートパラメータ)で使用可能な型は次の条件を満たす必要があります

  • 構造的な型である
  • その初期化が定数式であること
    • 一時的なメモリ割り当てを行わないこと

構造的な型の条件としてはまず、非publicメンバを持つ型が除外されます。これによって、通常のクラス型のほとんどはNTTPとして使用できません。これは標準ライブラリのクラス型でも同様のルールが適用されます。

構造的な型という条件が要求しているのは、そのクラスの構造表現が常に正規化されていることを保証するためです。これは、NTTPの構造表現がテンプレートの等価性に影響を与えるためです。

構造的な型の要件の緩和等は試みられてはいるものの、容易に解決できる問題ではないため多くのクラス型がNTTPとして使用できるようになるにはさらに時間がかかることが見込まれています。その一方で、静的リフレクション機能によるコンパイル時プログラミングでは、標準ライブラリの型が構造的な型になるとより便利になることが分かっています。

この提案は、構造的な型の要件について変更するのは避けて、一部の標準ライブラリクラス型を構造的な型であると指定することを提案しています。

対象は次のものです

  • std::string_view
  • std::span
  • std::tuple
    • 要素型が全て構造的な型である場合のみ

これらのクラス型における問題点は、いずれもプライベートメンバを持つという点です。ただし、標準ライブラリのクラス型でありその実装がほぼ特定されていることや標準ライブラリの実装を信頼することにより、構造的な等価性を定義できるようになります。

提案では、これらの型について構造的な型であると指定するとともに、template-argument-equivalent(2つのテンプレート引数が等価であること)の定義を指定しています。

  • std::string_view
    • size()data()の値がtemplate-argument-equivalentであるとき
  • std::span
    • size()data()の値がtemplate-argument-equivalentであるとき
  • std::tuple
    • 対応する要素同士が全てtemplate-argument-equivalentであるとき

この実装はおそらくコンパイラの特別扱いによって行われ、ユーザー定義型では利用できません(メンバに持ったときなどに間接的には利用できます)。

P3862R0 Postpone basic_string::subview and wait for cstring_view

cstring_view::subviewとの一貫性のために、basic_string::subviewを延期する提案。

C++26では、std::string/std::string_view.subview()メンバ関数が追加されています。これは.substr()の戻り値がstd::string_viewになるバージョンであり、std::string.substr()の戻り値(std::string)を変更できなかったため別の関数として追加されています(std::string_viewはインターフェース一貫性のために追加されている)。

P3655で提案中のstd::cstring_viewはnull終端保証のあるstd::string_viewですが、インターフェースはstd::string等と一貫しています。そこでも.subview()は用意されているのですが、std::cstring_viewが持つnull終端という情報を伝達するためにstd::string/std::string_viewとは異なる戻り値型を取っています。

// std::stringのsubview()
template<...>
class basic_string {

  ...

  constexpr auto subview(size_type pos = 0, size_type n = npos) const -> basic_string_view<...>;

  ...
};

// std::cstring_viewのsubview()
template<...>
class basic_cstring_view {
  ...

  constexpr auto subview(size_type pos = 0) const -> basic_cstring_view<...>; 
  constexpr auto subview(size_type pos, size_type n = npos) const -> basic_string_view<...>;
  
  ...
};

オーバロードは2つに分かれていますが提供する機能性は同様になります。ただし、std::cstring_view.subview()は終端を含むような部分文字列を取得する場合にstd::string_viewではなくstd::cstring_viewを返す点が異なっています。

std::cstring_viewはnull終端についての情報を型レベルで保持しているため、その文字列からの部分文字列の取得時にnull終端の情報をできるだけ保持しようとします。std::string_viewはnull終端についての情報を持たないので選択肢は無いですが、std::stringは同様にnull終端についての情報を持っているためstd::string.subview()std::cstring_viewと同様のアプローチを取ることが望ましいです。

しかし、C++26でstd::string.subview()を今のままにしておくと、あとからこのようなオーバロードを追加することはできません。なぜなら、戻り値型の変更が必要になり、それは破壊的変更となるからです。

この提案は、std::string.subview()std::cstring_viewと一貫させるために、std::string.subview()に後からこの変更を適用できるようにしておくことを提案するものです。

そのための方法として次の2つのアプローチを提示しています

  1. std::string.subview()を延期(C++26から削除)する
  2. std::string.subview()のデフォルト引数を削除する

どちらのアプローチをとっても、将来(おそらくC++29)std::cstring_viewを用いたnull終端情報を保持する.subview()を導入する事ができます。ただし、1の方法だとC++26ではstd::string.subview()が利用できなくなります。2の方法だと、C++26ではstr.subview(0, 10)のように必ず開始位置と長さを指定して使用しなければならないので、あとからstd::cstring_viewのように2つのオーバロードに分けた時でも動作も戻り値も同じオーバロードが選択されます。

しかし、C++26に対して何も対処をしなければ、std::string.subview()はnull終端情報を持たないstring_viewを返すことしかできず、情報の欠落が発生するとともにstd::cstring_viewとの一貫性が無くなります。.substr()と同様の理由により後からの修正は困難であり、やるとしたら新しい関数を追加するしかありません。

この提案ではどちらか一方を選択しているわけではなく、LEWGの決定に委ねています。

P3863R0 Minimal fix for CWG3003 (CTAD from template template parameters)

テンプレートテンプレートパラメータからのCTADを正式に許可する提案。

C++23では、ranges::toによって範囲空コンテナへの変換が簡単に行えるようになっています。

std::views::iota(0, 42) | std::ranges::to<std::vector<int>>();
std::views::iota(0, 42) | std::ranges::to<std::vector<double>>()

このとき、ranges::to<C>ではCのコンテナのテンプレートパラメータを省略することもできます。

std::views::iota(0, 42) | std::ranges::to<std::vector>();

このようにしている場合でも、ranges::to<C>は入力の値型からCのテンプレートパラメータを補ってくれます。

これは内部的にはCTADを利用することで実装されています。細部を省略すると、次のようなコードでこのコンテナのテンプレートパラメータの補完が行われています。

template <template <typename...> typename TT, typename R>
auto to(R&& r) {
  // ranges::to<C>の形式からranges::to<C<T>>の形式へ委譲
  return std::ranges::to<decltype(TT(std::from_range, std::declval<R&&>()))>(r);
}

ここのTT(std::from_range, std::declval<R&&>())ではテンプレートテンプレートパラメータTTに対してCTADが実行されることでTTのテンプレート引数を導出しています。

このテクニックは、rangeライブラリの元になったrange-v3ライブラリで発見され、主要なコンパイラではサポートされており、このように標準ライブラリ自身の実装にも使用されています。しかし、実際にはコア言語にはこのような機能は無く、テンプレートテンプレートパラメータに対してCTADを実行することは禁止されています。

LWG Issue 4381ではこのことを指摘して、ranges::toのこのオーバーロードを削除することで問題を解決しようとしています。また、CWG Issue 3003ではこの禁止事項(テンプレートテンプレートパラメータがCTADの対象ではない事)をより強調することを提案しています。

これらに対してこの提案は、広く使用されほぼすべての実装でサポートされているこの機能について、既存の慣行を反映して標準機能として許可することを提案するものです。これにより、ranges::toのこの推論を行うオーバーロードには非標準の機能に依存している現状が解消されます。

この提案では、これをDRとすることを提案しています。

EWGの議論では、この不完全な仕様の修正は後日行うこととして、ranges::toはとりあえず現状のまま維持することで合意したようです。それに伴ってこの提案は一旦クローズされています。

P3864R0 Correctly rounded floating-point maths functions

正しい丸め(correct rounding)を行う浮動小数点数演算関数の提案。

四則演算等の浮動小数点数演算においてはほとんどの場合に計算後に丸めが行われます。この時にどのように丸めが行われるのかは、丸めモードという浮動小数点環境の状態の一部としてスレッドごとにグローバルな状態によって設定されています。

したがって、ある浮動小数点数演算において丸めモードを指定しようとすると主に次の2つの問題が発生します

  1. 正しい丸めが必要な計算において、丸めモードを変更する方法が人間工学的に適切ではない
  2. ライブラリ内部実装で丸めモードを変更する場合、その時点の浮動小数点環境を保存し復帰する必要がある

丸めモードについてのこのような問題はC++だけの問題ではなくCでもよく知られた問題であり、C規格のAnnex FではIEC 60559で指定されている数学演算に完全に準拠した関数に対するプリフィックスとしてcr_を予約しています。この関数は丸めに関して正しい丸めを行うことが保証された関数であり、それが必要な場合に上記の問題を気にせずに使用できるものです。

この提案は、加減乗除と平方根計算のcr_付き関数を標準ライブラリに追加する提案です。

// 提案する関数の宣言
namespace std {
  constexpr floating-point-type cr_add(floating-point-type x, floating-point-type y);
  constexpr floating-point-type cr_sub(floating-point-type x, floating-point-type y);
  constexpr floating-point-type cr_mul(floating-point-type x, floating-point-type y);
  constexpr floating-point-type cr_div(floating-point-type x, floating-point-type y);
  constexpr floating-point-type cr_sqrt(floating-point-type x);
}

これらの関数は無限精度であるかのように演算が実行された後、その結果を最近傍偶数丸め(round-nearest-to-even)によって丸めて返します。floating-point-typeはプレースホルダ型であり、std::numeric_limits<F>::is_iec559trueとなるような浮動小数点数型Fである必要があります。

これらの関数によっても2の問題は完全には解決できないですが(実装によっては浮動小数点環境に影響を与える可能性があるため)、この関数の利用に当たって丸めモードを変更する必要は無いため1の問題は解決されます。

P3865R0 Class template argument deduction (CTAD) for type template template parameters

型テンプレートテンプレートパラメータに対するCTADを許可する提案。

モチベーションなどは少し上のP3863R0と共通しているのでそちらを参照してください。

この提案でもP3863R0と同様に型テンプレートテンプレートパラメータに対するCTADを明示的に許可し、それをC++17へのDRとすることを提案していますが、ここではさらにそのセマンティクスについて詳細に検討されています。

この提案では、テンプレートテンプレートパラメータに指定されたデフォルト引数やテンプレートテンプレートパラメータがそれに指定されたテンプレートよりも特化している場合に生じている制約を尊重するようにCTADが実行されるようにすることを提案しています。

これはエイリアステンプレートのCTADの動作と一貫しており、この提案でのテンプレートテンプレートパラメータに対するCTADでは対応するエイリアステンプレートを仮設してそれを使用してCTADを行うようにしています。

すなわち、型テンプレートテンプレートパラメータに対するCTADにおいては、テンプレートテンプレートパラメータに指定されたテンプレート実引数を表すエイリアステンプレート(そのテンプレートテンプレートパラメータ(not実引数)と同じテンプレートパラメータを持つ)によって置き換えて、そのエイリアステンプレートに対して実行されたかのようにCTADが実行されます。

提案文書より、単純な例

template<typename T>
struct C {
  C(T);
};

template<template<typename> class X>
void f() {
  X x(1); // ok、XにはC<int>が推論される
}

template void f<C>();

テンプレート実引数よりもテンプレートテンプレートパラメータの方が特化しているケース

template<typename ... T>
struct C {
  C(T ...);
};

template<template<typename> class X>
void f() {
  X x1{1};    // ok、XにはC<int>が推論される
  X x2{1, 2}; // ng
}

template void f<C>();

このようなCTADは、次のようなエイリアステンプレートを用いた推論によって行われます

// 仮設のエイリアステンプレート
// テンプレートテンプレートパラメータXと同じテンプレートパラメータを持ち、テンプレート実引数Cを表すもの
template<typename T>
using XC = C<T>;

void g() {
  XC x1{1};     // ok、XCにはC<int>が推論される
  XC x2{1, 2};  // ng
}

デフォルト引数のケース

template<typename T = int>
struct C {
  C(int);
};

template<template<typename = long> class X>
void f() {
  X x{1}; // ok、XにはC<long>が推論される
}

template void f<C>();

このようなCTADは、次のようなエイリアステンプレートを用いた推論によって行われます

// 仮設のエイリアステンプレート
// テンプレートテンプレートパラメータXと同じテンプレートパラメータを持ち、テンプレート実引数Cを表すもの
template<typename T = long>
using XC = C<T>;

void g() {
  XC x{1}; // ok、XCにはC<long>が推論される
}

P3866R0 V2: An Evolution Path for the Standard Library

ライブラリ機能を将来にわたって改善していくための標準ライブラリ中でのバージョン分離ルールの提案。

C++は言語もライブラリも後方互換性を維持することを強く志向しています。それは利用者にとってはバージョンアップのコストが低いというメリットをもたらしています。しかしその一方で標準化、特に標準ライブラリの進化はこのことに強く制約を受けており、既存のものの改善が必要な場合は別の名前を付けたり、別の名前空間に配置したり、一貫性が無く場当たり的な対応が行われています。

// old
std::lock_guard x(m);

// new
std::scoped_lock x(m);
// old
std::sort(v.begin(), v.end());

// new
std::ranges::sort(v.begin(), v.end());
// old
std::function f = ...;

// new
std::copyable_function f = ...;
// old
std::thread t = ...;

// new
std::jthread t = ...;

いずれの方法を取ったとしても、改善後のバージョンが分かりづらく、両者が混在していることで経緯を知らない利用者に混乱と学習の困難さをもたらしています。さらに、このような適切な名前を見つけなければならないことは機能の改善そのものの障害になっています。

この提案は、標準ライブラリ機能を将来にわたって一貫して改善していくための構造化フレームワークV2を提案しています。

V2は、C++標準ライブラリの破壊的変更を管理するためのバージョン管理戦略であり、主に次の2つの指針を立てています

  • ライブラリコンポーネントにインターフェースを変更するような変更が行われた場合、数値サフィックスを付加した新しいバージョンを導入する
    • 例えば、std::foo -> std::foo2 (-> std::foo3)
  • std名前空間内のバージョン管理されたエンティティを補完するために、標準(バージョン)固有の名前空間を導入する
    • std::cpp29std::cpp32など
    • これらの名前空間には、そのC++標準で利用可能なコンポーネントのその時点での最新版へのエイリアスが含まれる
    • 推奨される運用方法として、開発者はこれらのバージョン管理付き名前空間をデフォルトで使用することが望ましい
      • その後、std名前空間は古いバージョンの廃止されたAPIに明示的にアクセスする必要がある場合にのみ使用される
namespace cpp = std::cpp29;

void f() {
  // std::cpp29::stringはstd::stringのエイリアス
  cpp::string s;
  
  // std::cpp29::unordered_mapはstd::unordered_map2のエイリアス
  cpp::unordered_map<int,int> map;
}

また、これに関連するポリシーもいくつか提案しています。

まず、バージョンインクリメントは次の変更に適用されます

  • ソース互換性が損なわれる
    • ユーザーコードでコンパイルエラーが発生する
  • 主要プラットフォームにおいてABI互換性が損なわれる
  • 既存の機能を変更する動作のレグレッションや意味的変更を導入する

バージョンインクリメントはそうそう気軽に行われるものではなく、実行するには強力な正当性が必要になります。破壊的変更を導入する前に次のことを考慮すべきです

  • 移行パス
    • ユーザーがコードベースを半自動/自動で更新できるパスは用意できるか?
  • 概念の一貫性
    • 変更後のコンポーネントは、変更元の概念的なアイデンティティを維持しているか?
      • 変更は、ユーザーがその目的と使用法についてのメンタルモデルを適応させることが困難になるほど劇的であってはならない
  • 費用対効果の分析
    • 変更によるメリットは関連するコストを明確に上回っているか
      • コストにはたとえば、ユーザーがコードベースを更新するためのエンジニアリング労力、新しいインターフェースを習得するための学習コスト、標準ライブラリに別バージョンを導入することによる複雑さの増大、などが含まれる

バグ修正等の非破壊的な変更はコンポーネントの最新バージョンに適用する必要があり、以前のバージョンにバックポートするかはケースバイケースで判断できるものの、可能な限り広いユーザーにメリットをもたらすために、そのような改善は基本的にバックポートすることを推奨しています。

V2の下では、冒頭に示した例は次のように改善されます

// old
std::lock_guard x(m);
std::cpp11::lock_guard y(m);

// new
std::lock_guard2 x(m);
std::cpp17::lock_guard y(m);
// old
std::sort(v.begin(), v.end());
std::cpp17::sort(v.begin(), v.end());

// new
std::sort2(v.begin(), v.end());
std::cpp20::sort(v.begin(), v.end());
// old
std::function f = ...;
std::cpp11::function g = ...;

// new
std::function2 f = ...;
std::cpp26::function g = ...;
// old
std::thread t = ...;
std::cpp11::thread u =...;

// new
std::thread2 t = ...;
std::cpp20::thread u = ...;

V2フレームワークの利点は、ユーザーがバージョンアップをしようと考えた時でもすべてのものを一度に更新する必要はない点です。一度に全部更新することも、一部のものから順にアップデートしていくこともでき、ユーザーはそのペースを選択することができます。

P3867R0 define_static_string as a STATICALLY_WIDEN replacement

文字列リテラルを型に合わせた文字列型に変換する関数の提案。

標準規格文書中では、型Tcharであるかwchar_tであるかに合わせて自動的に文字列リテラルを変換するユーティリティ、STATICALLY-WIDENが定義されています。

Let STATICALLY-WIDEN<charT>("...") be "..." if charT is char and L"..." if charT is wchar_t.

これはほぼ同等なものをマクロを活用することで実装することができます

template <typename _CharT>
constexpr const _CharT* __statically_widen(const char* __str, const wchar_t* __wstr) {
  if constexpr (same_as<_CharT, char>)
    return __str;
  else
    return __wstr;
}

#define STATICALLY_WIDEN(_CharT, str) statically_widen<_CharT>(str, L##str)

このような実装はプリプロセスに依存するため文字列リテラルでしか使用できません。一方、このようなユーティリティはコンパイル時の文字列全般およびそのほかの文字列型(char8_t文字列など)において有用である可能性があります。

この提案は、STATICALLY-WIDENをコンパイル時リフレクションを利用してあらゆるコンパイル時の文字列と任意の文字列型で動作するもので置き換えて、なおかつそれを標準ライブラリ機能にしようとする提案です。

この提案では、std::meta::define_static_string()を参考に、char8_t文字列を受け取りそれを指定されたテンプレートパラメータが表す文字型のエンコーディングで文字列リテラルを生成するdefine_encoded_static_string()を提案しています。

namespace std::meta {
  template<typename CharT, ranges::input_range R>
    requires same_as<char8_t, ranges::range_value_t<R>>
  consteval const CharT* define_encoded_static_string(R&& r);
}

使用例

const char* hello = define_encoded_static_string<char>(u8"Hello");
const wchar_t* wello = define_encoded_static_string<wchar_t>(u8string_view(u8"Hello"));

入力として使用可能なのはchar8_t文字型によるUTF-8文字列ですが、その形式は文字列リテラルでなくてもokです。戻り値として、渡された文字列を指定された文字型の文字列に変換した文字列リテラルを返します。

提案では特に実装は示されていませんが、リフレクションに加えてコンパイラのサポートも必要なようです。コンパイラは規格に準拠したコンパイルのためにほぼ同等の処理を内部的に利用しているはずであり、これはそれを標準ライブラリ経由で公開するだけであるはず、とされています。

P3868R0 Allow #line before module declarations

モジュール宣言の前に#lineディレクティブを置けるようにする提案。

C++20のモジュール仕様では、モジュール宣言の前にプリプロセッシングディレクティブを置くことができません。Clangでこのことを実装したところ、次のようなコードがコンパイルエラーになることが発見されました。

#line 1 "A.cppm"
export module a;

これはオリジナルのソースコードの行数を参照するプログラムなどが良く行うことでであり、そのようなプログラム(build2, ccache, distccなど)において問題となります。

この提案は、この制限を緩和しようとするものです。

ただし、置くことを許可するのはこの#lineディレクティブのみです。

この提案には対応するNBコメントがあるようで、C++26をターゲットにしています。

P3870R0 Renaming std::nontype to std::tag

std::nontypestd::tagにリネームする提案。

std::nontypeに関してはこちらのスライドを見てもらうか、以前の記事を参照

この提案では、std::nontypeが実質的にコンストラクタタグ型であることから、std::tagとすることを提案しています。

この提案の名前は支持を得られず、結局std::constant_argになったようです。

P3872R0 2025-10 Library Evolution Polls

2025年10月に行われるLEWGにおける投票の予定。

次の提案が投票にかけられる予定です

P3774R1はC++26のDR、P3798R1はC++29を目指してLWGに転送するための投票です。

P3874R0 Safety Strategy Requirements for C++

C++の安全性についての戦略として、構成による安全性(Safety by Construction)を採用すべきとする提案。

この提案では、C++が将来にわたって新規開発のための言語としての選択肢であり続けるための安全性の保証として、構成による安全性(Safety by Construction)を採用し、それをC++の安全性戦略ビジョンとしてコミットメントすることを推奨するものです。

構成による安全性とは、RustやSwift等モダンな言語(Rustを除けば非システムプログラミング言語)のデファクトとなっている、デフォルトが安全なモードである状態の事です。C++の現状は逆で、デフォルトがアンセーフになっています。

また同時に、既存のC++コードへの直接アクセス(後方互換性)も重要視しています。

この提案における安全性とはUBが起こらない事を指しており、それを達成するために最も障害になるのがメモリ安全性であるとしています。また、この提案の目標としてはUBをゼロにすることで、UBを減らすことではありません。

かなり要約すると次のようなことを主張しています

  • UBによる欠陥(脆弱性)のほとんどは、新しいコードで発生する
    • 脆弱性の存在確率は、コードの寿命とともに指数関数的に減少する
  • 古いコードを安全に書き換えることよりも、新しく書かれるコードを安全にすることが重要
  • 現在のC++のビジョンには、この観点が欠けている
    • プロファイル機能は構成による安全性を補完するもの、レガシーコードの改善には役立つがこれによって構成による安全性を実現するのは困難
    • 暗黙の契約アサーションはUBフリーの良い一歩となる可能性がある
      • ただし、構成による安全性とは異なるアプローチ
  • 現状のWG21のコミットメントでは、メモリ安全性(ひいてはUB安全性)をセキュリティの一部として捉えており、直近それに対処されることが期待できない
    • 安全性の向上やUBの削減に向けた取り組みは行われており、C++の実績を考えるといずれ達成はされると思われる
  • 欠けているのはビジョンへのコミットメントにある
    • 言語の明確なビジョンを打ち出すことは、複数のシステム言語の中で実績のある選択肢として残るか、レガシーツールとして残るかの違いを生む可能性がある

このような観点から、構成による安全性(+現状と同等の後方互換性)によるUBフリー(結果的にメモリ安全)をC++の安全性戦略の到達目標として、そのコミットメントをより明確に打ち出すことを提案しています。

特筆すべき点として、この提案はRust Foundationの人の手によって書かれています。

P3875R0 Defining -ffast-math is hard!

-ffast-mathのような浮動小数点演算最適化を許可するために標準仕様として正確に定義するのは実現不可能だとする文書。

SG6では-ffast-mathによって有効になる最適化と同様に、正確な値を保持しない浮動小数点演算の最適化を明示的に許可することについて議論が行われていたようです。この文書は、それに対してそのような最適化の緩和条件のほとんどは正確に記述することが困難であることを指摘するものです。

ここでの正確とは、ユーザーがコンパイラの最適化による予期しない挙動を回避したり、プログラムの正しさについて厳密に推論したりできるほど十分明確に記述できること、を言います。

例えば浮動小数点演算において、再結合(reassociation)を許可することを考えます。再結合においては、浮動小数点数演算の式を場所によって異なる順序で計算することが許可されます。

例えば次のような関数

inline float f(float x, float y, float z) { 
  return x + y + z; 
}

がインライン展開され2か所で使用されている場合、異なる結合方法によって計算することでそれら一見同じであるはずの値によって呼ばれる純粋関数が異なる値を返すことはあり得るでしょうか?

仮に次のように使用されている時

w = f(a, b, c); 
use w; 

... 

use w again;

コンパイラが2回の使用の間(...)でwをレジスタスピルする必要がある場合、代わりに異なる結合方法でwを再計算(...中にある計算結果を再利用するなど)することで、wが僅かに異なる値を取ることは可能でしょうか?

今日の最適化においては、このようなことは実際に行われているようです。

異なる結合方法で同じ値を異なる時点で計算した場合、コンパイラはその結果が必ず同じであると仮定し、そうでない場合には完全に誤った結果を生成しても良いのでしょうか?明らかにそうではないのですが、このことをどのように仕様として規定するかは不透明です。

別の例として、Flush-to-zeroでも同様の問題が起こります。Flush-to-zeroでは非正規化数が0にされることを許可する最適化ですが、これは必ずしもすべての場合に適用されません。また、これは多くの場合別の翻訳単位で指定されることで、ある翻訳単位のコンパイラからはそれが設定されていることが観測できない場合が良くあります。

筆者の方による特に直観に反する例

int main() { 
  double smallest = nextafter(0.0, 1.0);

  if (smallest > 0.0) { 
    printf("Positive: %g\n", smallest); 
  } else { 
    printf("Wrong: %g\n", smallest); 
  }

  return 0; 
}

これをclang 16で-ffast-mathオプションを付けてコンパイルし実行すると

Wrong: 4.94066e-324

これは、smallestにはnextafter()の結果が正しく格納されているものの、Flush-to-zeroによってsmallest > 0.0の比較結果が0にフラッシュされているようです。

このような動作は正しく定義したり、実装詳細を理解していないユーザーに説明することが困難です。非正規化数は一部のコンテキストでは生成されうるものの、他のコンテキストでは生成されず、もはや表現可能な値(representable values)のwell-definedな集合は定義できません(このため、nextafter()の効果を正しく規定することができません)。

提案にはもう一例挙げられていますが、このように-ffast-mathが行っているような最適化は時に予測不可能なものであり、標準規格として妥当な形で規定することは無理ではないか?というのがこの文書の主張するところです。だからやめようとまでは提案しておらず、議論の一環としての文書のようです。

おわり

この記事のMarkdownソース




以上の内容はhttps://onihusube.hatenablog.com/entry/2026/03/01/204210より取得しました。
このページはhttp://font.textar.tv/のウェブフォントを使用してます

不具合報告/要望等はこちらへお願いします。
モバイルやる夫Viewer Ver0.14