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


[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ソース

[C++]Eigen::Transformの分かりづらいところ

はまったことのメモです。

Eigen::Transform

Eigen::Transformはアフィン変換を表現するクラス型です。

#include <Eigen/Core>
#include <Eigen/Geometry>

int main() {
  // 平行移動を表すアフィン変換を構成
  Eigen::Translation3d tr{10.0, 5.0, 0.0};
  Eigen::Transform3d transform{tr};

  Eigen::Vector3d vec{1.0, 2.0, 3.0};
  // 平行移動を適用
  Eigen::Vector3d res = transform * vec;
}

その他回転や拡大縮小などを行えます。ベクトルとの*での積の際も、同次行列を考慮して次元を足して1入れてみたいなことを内部で自動でやってくれます。

ただ、これだけならまだ普通にEigen::Matrixを用意してやった方が良い気持ちが大きいですが、このクラスの良いところは多段のアフィン変換の合成をサポートしているところです。

合成は二項*あるいは*=によって行えます。

#include <numbers>

#include <Eigen/Core>
#include <Eigen/Geometry>

int main() {
  // 3次元座標変換を表すアフィン変換を構成
  // 例えば、ローカル座標系からグローバル座標系への変換
  Eigen::Translation3d tr{10.0, 5.0, 0.0};
  Eigen::AngleAxisd rot{std::numbers::pi, Eigen::Vector3d::UnitZ()};
  Eigen::Transform3d transform{tr};
  transform *= rot;

  Eigen::Vector3d vec{1.0, 2.0, 3.0};
  // 座標変換を適用
  Eigen::Vector3d res = transform * vec;
}

*による合成はそのままアフィン変換行列の積と思えば良く、アフィン変換行列T1, T2, T3がありT1 -> T2 -> T3の順で変換を適用したい場合、そのままT3 * T2 * T1のように書くことができます。つまり*の右側にあるものほど先に適用されます。

ただし、Eigenの場合*の両辺はかなり柔軟で、アフィン変換行列そのものではなくアフィン変換を表すものを直接指定することができます(上記例だとEigen::Translation3dEigen::AngleAxisd)。Eigen::Transformおよびこれらの型でオーバロードされた*演算子内で行列に変換して積を取った時と同等の事が行われています。

Eigen::Transformそのものへの変換の渡し方/適用方法も柔軟になっています。

// *してから渡す
Eigen::Transform3d transform{tr * rot};

// *したものを代入
Eigen::Transform3d transform{};
transform = tr * rot;

// 2つのTransformの合成
Eigen::Transform3d t_rot{rot};
Eigen::Transform3d t_translate{tr};
Eigen::Transform3d transform = t_translate * t_rot;

この例はすべて一個前の例のtransformと同じ変換を表すはずです。

メンバ関数によるアフィン変換のセット・合成

Eigen::Transformへのアフィン変換の適用方法にはメンバ関数も利用できます。

Eigen::Transform3d transform = Eigen::Affine3d::Identity();
transform.translate(tr);
transform.rotate(rot);

こちらは*よりも起きていることが分かりやすいのでより説明的かもしれません。

ただ、ここで問題になるのはこのように1つのEigen::Transformメンバ関数で変換を適用していった際の変換の適用順が分かりづらくなることです。この例のtransformの場合、transform * vecという風に変換を適用した時、tr * rot * vecになるのかrot * tr * vecになるのかわかりづらくなります。別の言い方をすると、メンバ関数で変換をセットしていくと、セットした変換がする前の変換の左右どちらに適用されるか分かりづらくなります。

答えは、後にセットしたものがより右側に適用される(メンバ関数の呼び出し順に*で合成した時と同じ)です。上記のメンバ関数の例は前節での*=の例と同じ変換を表し、rot -> trの順に変換が適用されます(tr * rot * vec)。

座標変換が典型ですが、複数の変換を合成したアフィン変換ではその順番が重要になるため、注意が必要になります。

// T1 -> T2 -> T3 -> T4の順で変換を行うアフィン変換を構成したい
Eigen::Translation3d T1 = ...;
Eigen::Matrix3d T2 = ...;
auto T3 = Eigen::Scaling(...);
Eigen::Translation3d T4 = ...;

// *による合成
Eigen::Transform3d transform1{};
transform = T4 * T3 * T2 * T1;

// メンバ関数による合成
Eigen::Transform3d transform2 = Eigen::Affine3d::Identity();
transform.translate(T4);
transform.scale(T3);
transform.rotate(T2);
transform.translate(T1);

このtransform1transform2は同じ変換を表します。

私はこれにはまって時間を溶かし同僚に迷惑をかけたわけですが、なんかこうして書き起こしてみると全然罠でもなく自明ですね・・・

メンバ関数で適用順を制御する

メンバ関数による変換の合成時にも何らかの都合で右に追加するのではなく左に追加したいことがあるかもしれません。その場合は、メンバ関数名の先頭にpreを付けたメンバ関数によってそれを行えます。

// T1 -> T2 -> T3 -> T4の順で変換を行うアフィン変換を構成
Eigen::Transform3d transform3{T2};
transform.translate(T1);    // 右に適用
transform.prescale(T3);     // 左に適用
transform.pretranslate(T4); // 左に適用

.rotate()に対応するのは.prerotate()です。

このtransform3transform1transform2と同じ変換を表します。が、余計にわかりづらくなるのであまりやらない方が良さそうに思えます。

デフォルト構築

これは書いてて気づいたおまけです。

ここまでの例では黙って回避していましたがEigen::Transformをデフォルト構築すると恒等変換ではなく無の変換が構成されます。これはおそらく内部の変換行列の係数が不定値を取るため、これに対して変換を適用してもおかしな結果を招くでしょう。

Eigen::Transform3d transform{};
transform *= T4;  // 意図通りの変換が構成されない

デフォルト構築はクラスメンバにしたときにそのクラスのデフォルトコンストラクタを無効化しないために用意されているだけだと思われます。デフォルト構築した後は代入演算子=)によって上書きするか、そもそもEigen::Affine3d::Identity()などによって明示的に恒等変換で初期化する必要があります。

Eigen::Transform3d transform{};
transform = T4;  // T4の変換と同等の変換を構成
// 恒等変換を構成
Eigen::Transform3d transform = Eigen::Affine3d::Identity();

参考文献

この記事のMarkdownソース

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

文書の一覧

全部で37本あります。

もくじ

N5020 2026-11 Búzios Meeting Information

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

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

P2953R2 Forbid defaulting operator=(X&&) &&

右辺値修飾されたdefault代入演算子を禁止する提案。

以前の記事を参照

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

  • EWGの投票結果を追加
  • コンパイラ毎のdefault代入演算子に対する挙動の違いの表を更新
  • その他提案と文言の修正

などです。

P3347R5 Invalid/Prospective Pointer Operations

無効化されたポインタに対する一部の演算を明示的に許可する提案。

以前の記事を参照

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

  • R4の改訂履歴を追加
  • 著者のメールアドレス集成

などです。

P3567R1 flat_meow Fixes

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

以前の記事を参照

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

  • 文言の修正

などです。

P3579R2 Fix matching of constant template parameters when matching template template parameters

テンプレートテンプレートの特殊化のマッチング時に、NTTPの縮小変換を許可しないようにする提案。

以前の記事を参照

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

  • 文言の改善
  • 最新のWDにリベース

などです。

P3612R0 Harmonize proxy-reference operations (LWG 3638 and 4187)

vector<bool>::referencebitset<N>::referenceの間で一貫したIssue解決を行う提案。

この提案はvector<bool>::referencebitset<N>::referenceに関する次の2つのイシュー解決の際に、追加するswap()/operator=シグネチャを共通化しようとするものです。

P2321(views::zipの提案)ではviews::zipの動作のためにvector<bool>::referenceに代入演算子が追加されており、これは

namespace std {
  template<class Allocator>
  class vector<bool, Allocator> {
    public:  
    ...
     
    class reference {
      friend class vector;
      constexpr reference() noexcept;
    public:
      constexpr reference(const reference&) = default;
      constexpr ~reference();

      // bool型への暗黙変換
      constexpr operator bool() const noexcept;

      // 既存の代入演算子
      constexpr reference& operator=(const bool x) noexcept;
      constexpr reference& operator=(const reference& x) noexcept;
      
      // P2321で追加された代入演算子
      constexpr const reference& operator=(bool x) const noexcept;

      ...
    };
   };
}

プロクシ参照型はconstでも代入可能である必要があったもののvector<bool>::referenceconst修飾された代入演算子を持っていなかったため、後方互換性を維持しつつそれを可能にするためにconst修飾された代入演算子が追加されました。この時、その戻り値型はreferenceのprvalueではなく、const lvalueとされました(なぜかはよくわかりません)。

このvector<bool>::referencestd::swapが意図通りに動作しないためswap()のカスタマイズが必要です。例えば、vector<bool> vに対してstd::swap(v[1], v[2])が機能する必要があるものの、std::swapは非const参照しか取らないため呼び出せません。また、呼び出せたとしても、std::swapのデフォルト実装であるReference t = r1; r1 = r2; r2 = t;の様な交換操作はvector<bool>::referenceのコピーは参照のコピーになるため、参照先のbool値を交換しません。

LWG Issue 3638はこれについてのイシュー報告です。

一方、vector<bool>::referenceとほぼ同じ動作をするものに、bitset<N>::referenceがあります。

namespace std {
  template<size_t N>
  class bitset {
  public:
    ...

    class reference {
    public:
      constexpr reference(const reference&) = default;
      constexpr ~reference();
      
      // bool型への暗黙変換
      constexpr operator bool() const noexcept;                  // for x = b[i];
      
      // 代入演算子
      constexpr reference& operator=(bool x) noexcept;           // for b[i] = x;
      constexpr reference& operator=(const reference&) noexcept; // for b[i] = b[j];

      ...
    };
  };
}

こちらはP2321で変更されなかった(bitsetrangeではないためと思われる)ため、const修飾された代入演算子を持たず、swapについて同様の問題があります。LWG Issue 4187は前者(const代入演算子)についてのイシュー報告です。

これら2つの型には共通点が多くあるものの、両方のイシューの解決後も型のインターフェースは一貫していません。この提案は、この両方のイシューを合わせた形の一貫した解決を両方の型に適用しようとするものです。

すなわち次の変更を適用します

  • vector<bool>::reference
    • ADL swap()を追加
  • bitset<N>::reference
    • const修飾された代入演算子を追加
    • ADL swap()を追加

提案後の両クラスのインターフェース

namespace std {
  template<class Allocator>
  class vector<bool, Allocator> {
    public:  
    ...
     
    class reference {
      friend class vector;
      constexpr reference() noexcept;
    public:
      constexpr reference(const reference&) noexcept; // 👈
      constexpr ~reference();

      // bool型への暗黙変換
      constexpr operator bool() const noexcept;

      // 既存の代入演算子
      constexpr reference& operator=(const bool x) noexcept;
      constexpr reference& operator=(const reference& x) noexcept;
      
      // P2321で追加された代入演算子
      constexpr const reference& operator=(bool x) const noexcept;
      
      // この提案によるADL swap()
      friend constexpr void swap(reference x, reference y) noexcept;  // 👈
      friend constexpr void swap(reference x, bool& y) noexcept;      // 👈
      friend constexpr void swap(bool& x, reference y) noexcept;      // 👈

      ...
    };
  };
}
namespace std {
  template<size_t N>
  class bitset {
  public:
    ...

    class reference {
    public:
      constexpr reference(const reference&) noexcept; // 👈
      constexpr ~reference();
      
      // bool型への暗黙変換
      constexpr operator bool() const noexcept;                  // for x = b[i];
      
      // 代入演算子
      constexpr reference& operator=(bool x) noexcept;           // for b[i] = x;
      constexpr reference& operator=(const reference&) noexcept; // for b[i] = b[j];
      
      // この提案によるconst 代入演算子
      constexpr const reference& operator=(bool x) const noexcept;  // 👈
      
      // この提案によるADL swap()
      friend constexpr void swap(reference x, reference y) noexcept;  // 👈
      friend constexpr void swap(reference x, bool& y) noexcept;      // 👈
      friend constexpr void swap(bool& x, reference y) noexcept;      // 👈

      ...
    };
  };
}

bool&を取る2つのswap()オーバーロードは、bool型の左辺値breferenceの左辺値rに対してswap(r, b)swap(b, r)を機能させるためのオーバーロードです。どちらのreferencebool&から暗黙変換可能ではないため、このswapを機能させるためにこの2つのオーバーロードが必要になります。

コピーコンストラクタのdefaultが削除されているのは、実装によって両クラスのデストラクタのトリビアル性に差異があったことから、標準としてそれを強制することを回避することを意図したものです。コピーコンストラクタは実際には主要3実装全てでトリビアルでしたが、ここでのトリビアル性が重要ではないことからデストラクタに合わせたようです。

P3666R0 Bit-precise integers

C23の_BitIntC++に導入する提案。

幅の大きな整数型の必要性はP3140R0で、_BitIntの性質と基本型として実装するメリットはP3639R0で、それぞれ説明されています(これらは同じ著者の方によるものです)。

これらの経験を踏まえて、C++にC23互換の_BitIntを導入しようとする提案です。

この提案の_BitIntはキーワードかつ基本型として提案されており、特にCとの互換性を重視したものになっています。また、ここでは_BitIntの最小限のもの(MVP: Minimal Viable Product)をまず導入することを目指しており、最初から完全なものを入れることを目指していません。

言語機能としてはCの_BitIntをほぼそのまま導入します

  • 型名は_BitInt(N)もしくはunsigned _BitInt(N)
    • _BitIntはキーワード
  • Nの値は少なくとも64までをサポートする
    • BITINT_MAXWIDTHで最大値を取得できる
  • wb, uwbなどのリテラルサフィックスをサポート
  • 整数昇格の対象外
  • 標準整数型と同じ幅であれば、標準整数型の方が変換ランクが低くなる
    • _BitInt(32)int(32ビット幅とする)はintの方が変換ランクが高い
    • 幅が大きい場合にのみ変換ランクが高くなる
      • _BitInt(33)int(32ビット幅とする)は_BitInt(33)の方が変換ランクが高い
  • 符号が混在した比較や暗黙変換などの寛容さがある
  • 符号付_BitIntオーバーフローは未定義動作

ただし一部C++側で拡張している部分があります

  • 浮動小数点数型、bool型や文字型への暗黙変換を禁止
  • _BitInt(N)Nを関数テンプレートにおける引数推論の対象にする
    • template <size_t N> void f(std::bit_int<N> x);のような関数をf(100wb)のように呼ぶと、N = 8が推論される
  • _BitInt(1)をサポート
    • C23では符号なし_Bitint(1)をサポートしていない

標準ライブラリでも_BitInt対応が行われます

  • std::bit_int<N>std::bit_uint<N>エイリアステンプレートの追加
  • 文字列変換サポート
    • std::format
    • std::to_chars/std::from_chars
    • std::to_string/std::to_wstring
  • <cmath>の数学関数のサポート
  • std::is_integral/std::integralのサポート
  • std::atomic_BitInt特殊化サポート
  • iota_viewの内部整数型IOTA-DIFF-Tの定義を調整
    • 128ビットを超える幅の整数型が使用された際にもABI破壊が起こらないように考慮する
  • <simd>
    • ここではまず、標準整数型と一致する幅の_BitIntについてのみサポートする

ライブラリの規定では多くの場所で既に包括的な整数型の指定によって暗黙的に任意の整数型をサポートするようになっているため、文言の変更が無くても多くの場所で自動的に対応が行われます(実装が不要になるわけではありませんが)。

#include <cstdint>

void large_integer_example() {
  // 128ビットの符号なし整数を定義
  std::bit_uint<128> a = 0xFFFF'FFFF'FFFF'FFFF'FFFF'FFFF'FFFF'FFFFuwb;

  std::bit_uint<128> b = 2uwb;
  std::bit_uint<128> result = b - a;
  // オーバーフローはUBになるのでより広い幅で行う必要がある
  std::bit_uint<256> prod = std::bit_uint<256>(a) * std::bit_uint<256>(b);
}

ただし、64を超えるNのサポートは実装定義です。

P3688R3 ASCII character utilities

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

以前の記事を参照

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

  • §3.6. Why no function objects? セクションを追加
  • N5014にリベース

などです。

P3695R1 Deprecate implicit conversions between Unicode character types

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

以前の記事を参照

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

  • char8_tを含む変換のみを非推奨とする
    • char16_tchar32_tの間の変換については手を付けない
  • N5014にリベース

などです。

char16_tUTF-16)はサロゲートペアを利用して一部のコードポイントの文字をエンコードすることにより、char32_tとの比較で誤検出(異なるコードポイントの文字に対応する値の比較がtrueになる)が発生することは基本的に無いため、char16_tchar32_tの間の暗黙変換はそのままにすることにしたようです。

P3702R2 Stricter requirements for document submissions (SD-7)

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

以前の記事を参照

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

  • プレゼンテーションスライドに関する例外を追加

などです。

P3754R1 Slides for P3100R2 presentation to EWG

P3100R2の紹介スライド。

以前の記事を参照

このリビジョンでの変更はよくわかりませんが、これは提案ではありません。

P3776R0 More trailing commas

P3776R1 More trailing commas

末尾カンマを許可する場所を広げる提案。

関数の引数リストや初期化子リスト、列挙値の宣言など、C++コードの中でカンマ区切りリストを記述する必要のある場所は多数あります。そのような場所では場合によってはリスト末尾のカンマを付けたままにすることができることがありますが、別の場所ではできない場合もあります。

この提案は、なるべく多くの場所で末尾カンマを許容するようにすることを提案しています。

同様の提案は以前にもP0562R2で提案されていました。P0562R2では基底クラスリストとコンストラクタ初期化子リストでの末尾カンマを許容することを提案していましたが、コンストラクタ初期化子リストに関してはコンストラクタ本体のパースにおける曖昧性の問題が指摘されており、それを受けて提案の追及は止まってしまっているようです。

この提案もP0562R2とモチベーションを同じくしており、次の事をモチベーションとして挙げています

  • エディタのテキスト編集機能への対応
    • 例えばVSCodeのAlt+↑などのようにテキストの行を入れ替える機能において、カンマ有無のコンパイルエラーを誘発するのを防止できる
  • Gitなどのバージョン管理における差分の改善
    • カンマ区切りリストに要素を追加する場合、元のリストの最後の要素にカンマを追加してから要素を追加する
    • この時、1行1要素のようにフォーマットされていると、差分が2行に及ぶ
    • 末尾カンマを許容するとこの場合を1行だけ(追加した分だけ)にでき、レビューの容易化・git blameを汚染しない・コンフリクト発生の最小化、などが期待できる
  • コード生成機能の簡素化
    • 可変長マクロにおける__VA_OPT__のように、末尾カンマを許容しないコンテキストに対する特殊対応が必要になることが良くある
    • C++26のリフレクションを用いたコード生成機能(P3294R2)においても末尾カンマに対する特殊対応が必要になることが予想されている
    • 末尾カンマを許容することで、これらの対応や考慮が不要になり、コード生成するコードが簡素化される
  • フォーマッタの制御
    • clang-formatにおいては末尾カンマを利用してリストのフォーマットを制御できる機能があるが、末尾カンマを許容しない場所ではこれを使えない
    • 末尾カンマを許容することでこれを使用できる場所が増える
  • 言語の一貫性の向上
    • 末尾カンマを許容している場所としていない場所が混在しており、どこで許容されてどこで許容されないかわかりづらい
    • 単純なリファクタリングなどの際に問題になることがある
  • 文字列リテラルのカンマ区切りリストにおけるバグの防止
    • 関数引数に複数の文字列リテラルを渡すとき、カンマを忘れても(関数側の引数の期待する数と合っていれば)エラーにはならないが、文字列リテラルの結合によって意図通りに動作しなくなる
    • 末尾カンマを許容しておけば、あとから末尾に文字列リテラルを追加する際にも忘れが無くなる

この提案ではセミコロンで終了するリストを除いて可能な限りのリストにおいて末尾カンマを許容するようにすることを提案しており、おおむね{}, (), [], <>のいずれかによるかっこで囲まれたリストにおいて末尾カンマを許容するようにしています。

提案している末尾カンマを許容するリストの例

[: ... :]<A, B,>                    // template-argument-list in splice-specialization-specifier
[]<A, B,>{}                         // template-parameter-list in lambda-expression
[a, b,]{}                           // capture-list in lambda-introducer
d[a, b,]                            // expression-list in subscript operator
f(a, b,)                            // expression-list in call operator
T(0,)                               // expression-list in function-style cast
typename T(0,)                      // expression-list in function-style cast with typename
new (a, b,) T                       // expression-list in new-placement
new T(a, b,)                        // expression-list in new-initializer
template for (int _ : { a, b, })    // expression-list in expansion-init-list
auto [a, b,]                        // sb-identifier-list in structured-binding-declaration
T f(a, b,)                          // parameter-declaration-list in parameter-declaration-clause
T x(a, b,);                         // expression-list in initializer
[[=a, =b,]]                         // annotation-list in attribute-specifier
S() : m(a, b,)                      // expression-list in mem-initializer
template<a, b,>                     // template-parameter-list in template-head
template<C<a, b,> T>                // template-argument-list in type-constraint
template<template<A, B,> concept>   // template-parameter-list in concept-tt-parameter
T<A, B,>                            // template-argument-list in simple-template-id
operator()<A, B,>                   // template-argument-list in template-id

ちなみに、次のリストでは既に末尾カンマが許容されています

{ a, b, }                 // initializer-list in braced-init-list
{ .a=0, .b=0, }           // designated-initializer-list in braced-init-list
enum { a, b, }            // enumerator-list in enum-specifier
[[a,b,,,,]]               // attribute-list in attribute-specifier

なお、P0562R2で提案されていた末尾カンマの許容については、構文解析を曖昧にする問題が(どちらにも)ありそうなため、ここでは提案していません。ここで提案しているリストは末尾カンマの後には閉じかっこしか来ないため構文解析を曖昧にはしないはずです。

この提案はEWGのレビューにおいてC++29に向けてCWGに転送するためのコンセンサスを集めることができずにリジェクトされています(その理由は特に書かれていません)。

P3784R1 range-if

範囲if文の提案。

以前の記事を参照

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

  • ライブラリベースの代替案についての詳細な議論を追加

などです。

ライブラリベースの解決策は次の2つが挙げられています

  • アルゴリズム
    • break, continueなどの制御構文がサポートされない
  • ranges::nonempty_subrange
    • Redditで提案されたもの
    • 構文が冗長
    • ダングリングを防止するためには、borrowed_rangeに制限するかowning_subrange(P2644/P2718のライブラリソリューション)を使用する必要がある

いずれも言語機能よりも優れてはいないとしています。

P3786R0 Tuple protocol for fixed-size spans

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

タプルプロトコルは固定サイズの多くのライブラリ型ですでにサポートされており、構造化束縛のカスタマイズポイントとなります。std::spanはまだサポートしていないため、追加しようとする提案です。

std::span<int, 3> s{...}; 
auto & [x, y, z] = s; // ok、この提案後

std::vector<std::span<int, 3>> ss{...}; 
auto firsts{ss | std::views::elements<0> 
               | std::ranges::to<std::vector>()};  // ok、この提案後

実は以前にもP1024でこれは提案されており、C++20で採択されていたのですが、std::tuple_element_t<const std::span<T, 3>>const Tになる(Tになってほしい)という問題が報告された結果、P2116で削除されています。ここでの設計はこの問題をstd::spanが参照セマンティクスを持つ型として扱うことで解決しています。

すなわち、std::spanに対するトップレベルの修飾子はすべて無視されます。

  • tuple_size<cv1 span<cv2 T, N>>::value == N
  • tuple_element<I, cv1 span<cv2 T, N>>::type == cv2 T
  • decltype(get(span<cv T, N>)) == cv T

当然ですが、タプルプロトコルサポートは固定サイズ(N != dynamic_extent)の場合のみです。

P3811R0 default comparison memory safety

デフォルト比較演算子の実装に対してメモリ安全であることを要求するようにする提案。

C++20で追加された比較演算子のデフォルト実装において生成されるコードはかなり単純かつボイラープレート的なコードであり、UBフリーで実装できるはずです。この提案は、標準としてそのような実装を行うことを要求するようにする提案です。

デフォルト比較演算子の実装は例えば次のようになります

class TotallyOrdered : Base {
    string tax_id;
    string first_name;
    string last_name;
public:
  // auto operator<=>(const TotallyOrdered&) const = default; の実装イメージ
  std::strong_ordering operator<=>(const TotallyOrdered& that) const {
    if (auto cmp = (Base&)(*this) <=> (Base&)that; cmp != 0) return cmp;
    if (auto cmp = last_name <=> that.last_name; cmp != 0) return cmp;
    if (auto cmp = first_name <=> that.first_name; cmp != 0) return cmp;

    return tax_id <=> that.tax_id;
  }

  // bool operator==(const TotallyOrdered&) const = default; の実装イメージ
  bool operator==(const TotallyOrdered& that) const {
    if (!((Base&)(*this) == (Base&)that)) return false;
    if (!(last_name == that.last_name)) return false;
    if (!(first_name == that.first_name)) return false;

    return tax_id == that.tax_id;

  }
};

実際にコンパイラがこのようなコードを生成するわけではないものの、これと大きく異なるコードが生成されることもないはずです。このようなコードにおいては、使用する各サブオブジェクトの比較演算子== <=>)を除いて未定義動作を混入させることなく実装ができるはずです。また、<=> ==がそのようになっていれば、そこから生成される他の比較演算子でも同様の保証を提供できます。

P3812R0 const and & in default member functions

const/参照メンバを持つクラスの代入演算子のデフォルト実装を可能にする提案。

クラスの非静的メンバ変数としてconstあるいは参照メンバを持つと、そのクラスの代入演算子はデフォルト実装できなくなります。

// least privilege
class leastp final {
public:
  leastp(std::vector<int>& v, int i)
      : v{v}, i{i} {}

  // コンストラクタはデフォルト実装できる
  leastp(const leastp&) = default;
  leastp(leastp&&) = default;

  // 代入演算子はデフォルト実装できない(deleteされる
  leastp& operator=(const leastp& other) = default;
  leastp& operator=(leastp&& other) = default;

  // least privilege
  constexpr size_t size() const {
      return v.size();
  }

private:
  std::vector<int>& v;
  const int i;
};

これは代入演算子を手書きすることを考えると分かりやすいと思われますが、参照やconstメンバは基本的に置換可能ではないためです。

これを実装しようとすると*thisを配置しなおす必要があります。例えば次のような実装になります

class leastp final {
  ...

  leastp& operator=(const leastp& other) {
    if (this != &other) {
      // *thisを一旦破棄
      this->~leastp();

      // コピーコンストラクタを用いてthisの場所に新しいオブジェクトを構築
      new (this) leastp(other);
    }

    return *this;
  }

  leastp& operator=(leastp&& other) {
    if (this != &other) {
      // *thisを一旦破棄
      this->~leastp();

      // ムーブコンストラクタを用いてthisの場所に新しいオブジェクトを構築
      new (this) leastp(std::move(other));
    }
    
    return *this;
  }

  ...
};

このような実装は複雑であり、正しく記述することが困難です(少し間違えるとUBに突入する)。C++コアガイドラインにおいてもコピー/ムーブ可能なクラスにconstメンバや参照メンバを含めないことが推奨されています。

この提案は、const/参照メンバを非静的メンバに持つクラスにおいて、コピー/ムーブコンストラクタがdefault指定されているならば、透過的に置換可能(transparently replaceable)として、対応する代入演算子default実装できるようにするものです。

これによって、const/参照メンバを保持したい場合に諦めたり代わりにポインタを保持するなどの必要がなくなり、上記のような実装を取る場合のコードの必要性もなくなり、コアガイドラインも不要になります。

P3813R0 execution::task::valueless()

execution::taskvalueless()メンバ関数を追加する提案。

C++の標準ライブラリの型には空の状態を取るものは珍しくありません。その中には空の状態が回復不可能である型があります。それは、indirect, polymorphic, generator, taskの4つです。これらの型ではほかの全てのメンバ関数が事前条件として空ではない事を要求するため、代入と破棄くらいしかできることがありません。

これらの型の空の状態は、通常のプログラムではほぼ発生しないと思われるものの場合によっては出会うかもしれません。そのために、indirect, polymorphicでは.valueless_after_move()によってこの空の状態をチェックできるようにしています。しかし、generator, taskにはそのような関数がありません。

generatorについては既存の実装を考慮すると複雑になるため別の提案で行うことにして、ここではtaskに対して空の状態チェック関数を追加することを提案しています。

taskが回復不可能な空になるのは、ムーブの前後とconnect呼び出し後です。この2つの状態は区別する必要がなさそうなので、ここでは状態が空かどうかをチェックする関数.valueless()のみを追加しています。

namespace std::execution{
  template <class T = void, class Environment = env<>>
  class task {
    ...

    bool valueless() const noexcept;

    ...
  }
}

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

task<int> t = ...;

// 状態チェックを行えるようになる
contract_assert(not t.valueless()); 

auto res = sync_wait(t);

P3815R0 Add scope_association concept to P3149

P3149の非同期スコープ機能に、scope_associationコンセプトを追加する提案。

P3149の非同期スコープ機能は、2つのスコープ型(simple_counting_scopecounting_scope)とassociatespawnspawn_futureなどの基礎的な操作によって構成されており、これらのものはscope_tokenというコンセプトを中心として設計されています。

namespace ex = std::execution;

// ウィンドウを表現する簡単な型
struct my_window {
  class close_message {};

  ex::sender auto some_work(int message);

  ex::sender auto some_work(close_message message);

  void onMessage(int i) {
    ++count;
    // onによってschのコンテキストでsome_workを実行し
    // その操作はscopeに対応するスコープに関連付けられる
    ex::spawn(ex::on(sch, some_work(i)), scope);
  }

  void onClickClose() {
    ++count;
    ex::spawn(ex::on(sch, some_work(close_message{})), scope);
  }

  my_window(ex::system_scheduler sch, ex::counting_scope::token scope)
    : sch(sch)
    , scope(scope) {
    // このクラスを何らかの方法でWindowフレームワークに登録し
    // `onMessage()`と`onClickClose()`の呼び出しを受け付けられるようにする
  }

  ex::system_scheduler sch;
  ex::counting_scope::token scope;
  int count{0};
};

int main() {
  // keep track of all spawned work
  ex::counting_scope scope;
  ex::system_context ctx;
  
  my_window window{ctx.get_scheduler(), scope.get_token()};

  // scopeに関連付けられた全ての操作の完了を待機する
  std::this_thread::sync_wait(scope.join());
  
  // すべてのリソースは安全に破棄できる
  // =スコープに関連付けられた操作はすべて完了している
  return window.count;
}

スコープ型は、そのスコープに関連付けられた非同期処理(sender)による処理が全て終わるまで、そこで使用されうるリソースを保護するためのものです。スコープへの非同期操作の関連付けはexecution::spawnによって行われており、ここに処理を表すsenderとスコープから取得したトークン(上記例だとcounting_scope::token)を渡すことでスコープと非同期処理を関連付けています。

スコープは.join()によってスコープ自身のsenderを取得でき、そのsenderは関連付けられた処理が全て終わるまで完了しないものです。スコープオブジェクトを適切に管理することで、スコープ内で実行される非同期処理で使用されるリソース(上記例だとsystem_contextwindow自身)をその完了まで適切に保護することができます。

この提案はscope_associationというコンセプトを導入し、これを用いてP3149で提案されているこれらの機能の内部設計を行うことで、現在の実装より良い実装を取ることができるとして、scope_associationコンセプトとそれによる再設計を提案するものです。

とはいえ、ここでの再設計は内部に閉じたものでインターフェースを変更するものではありません。これにより次の利点が得られるとしています

  • スコープ型と基礎操作のバイナリサイズの削減
  • ムーブ/コピーの回数を減らせる
  • 実装モデルが簡素化され、カスタマイズしやすくなる

提案されているscope_associationコンセプトは次のようなものです

template <class Assoc>
concept scope_association =
  movable<Assoc> &&
  default_initializable<Assoc> &&
  requires(Assoc assoc) {
    { static_cast<bool>(assoc) } noexcept;
    { assoc.try_associate() } -> same_as<Assoc>;
  };

scope_associationのモデルとなる型は、senderと非同期スコープ間の関連付けを表すRAIIハンドル型となります。コンセプトから読み取れる性質は次のようになります

  • ムーブ可能
    • コピー不可能であることを意図
  • デフォルト構築可能
  • boolへの変換が可能
    • 文脈的bool変換可能であることを意図
  • .try_associate()を持つ

scope_associationな型のオブジェクトassocは、bool変換されてtrueを返した場合(“engaged”な状態)は何らかのsenderとスコープの間で関連付けが成立していることを表し、falseを返した場合(“disengaged”な状態)は関連付けが成立していないことを表します。

.try_associate()についてはP3149のscope_token(のモデルとなる)型のそれと全く同じ意味論となります。

scope_association型はRAIIハンドルであり、そのデストラクタで関連付けの開放処理(scope_token.disassociate()相当)を行うことで関連付け解除とそのクリーンアップ処理を自動化します。

これを用いて、P3149のscope_token, associate, spawn, spawn_future, simple_counting_scope, counting_scopeを変更します。

execution::scope_token

P3149の中心概念であるscope_tokenは次のように定義が変わります

P3149R11 この提案
template <class Token>
concept scope_token =
  copyable<Token> &&
  requires(Token token) {
      { token.try_associate() } -> same_as<bool>;
      { token.disassociate() } noexcept -> same_as<void>;
      { token.wrap(declval<test-sender>()) } -> sender_in<test-env>;
  };
template <class Token>
concept scope_token =
  copyable<Token> &&
  requires(Token token) {
    { token.try_associate() } -> scope_association;
    { token.wrap(declval<test-sender>()) } -> sender_in<test-env>;
  };

.try_associate()scope_association型を返すようになります。これまでは関連付けの成否を表すbool値を返していましたが、scope_association型を返すことで関連付けそのものを表現するものを返すようになります。

scope_association型はデストラクタで開放処理を自動化するため、.disassociate()は不要になります。

execution::associate

associateはCPOであり、sender auto associate(sender auto&&, scope_token auto) noexcept(...);の様なシグネチャを持つものです。これは、senderとスコープをscope_tokenを介して関連付けを行おうとするものです。

インタフェースや役割は変化しませんが、返されるsenderのコピー動作が変化します。

associate()に渡されるsenderがコピー可能な場合、結果のassociate-senderもコピー可能となり

  • 関連付けられていないassociate-senderをコピーすると、必ず新しい関連付けられていないassociate-senderが生成される
  • 関連付けられているassociate-senderをコピーするには、それに含まれるassociate-dataをコピーする必要があり、そのコピーコンストラクタは次のように動作する
    1. ソースのassociation.try_associate()を呼び出した結果が宛先のassociate-dataに渡される
    2. 結果の関連付け(scope_associationオブジェクト)がengaged状態ならば、ラップされたsenderをソースから宛先のassociate-dataにコピーする
      • 宛先のassociate-senderは関連付けられている
    3. そうでない場合、宛先のassociate-senderは関連付けられていない

さらに、operation-stateのデストラクタは次の事を保証します

  • 独自の関連付け(scope_associationオブジェクト)を持つoperation-stateoperation-stateのデストラクタの最後のステップとして、関連付けのデストラクタを呼び出す必要がある

execution::spawn/execution::spawn_future

内部状態でscope_tokenオブジェクトを保持する代わりにscope_associationオブジェクトを保持するようになり、関連付けの解除がデストラクタによって行われるようになります。

execution::simple_counting_scope/execution::counting_scope

関連付けの解除がtoken.try_associate()から返されるscope_associationオブジェクトのデストラクタによって行われるようになります。

これらの変更は前述のようにユーザー向きのAPIにはほとんど影響がありません。そのうえで、内部実装を改善しようとするものです。

P3816R0 Hashing meta::info

meta::infoのハッシュサポートを追加する提案。

meta::infoはリフレクション機能において^^(リフレクション操作)の戻り値となるリフレクションオブジェクトの型であり、コンパイル時にのみ使用可能な不透明型です。コンパイル時のハッシュサポートはリフレクションにおいても考慮されていなかったため、meta::infoはハッシュ化可能ではありません。

一方、P3372R3では連想コンテナを含むほぼすべてのコンテナがconstexpr対応しており、そのためにハッシュサポートが重要となります。

そのために、この提案はmeta::infoコンパイル時ハッシュサポートを追加しようとするものです。

しかしmeta::infoコンパイル時にのみ使用可能な型であり、std::hashの特殊化による対応は実行時要件が多く課せられるため適していません。そのほかのコンパイル時にのみ使用可能な型のコンパイル時ハッシュサポートのために、std::consteval_hash<T>を追加し、そのmeta::info特殊化としてコンパイル時ハッシュサポートを追加することを提案しています。

リフレクションによるmp_unique実装の、コンパイルunordered_setを使用する例

template <typename... Types>
struct type_list {};

template <typename TypeList>
consteval auto mp_unique_reflected() {
  static_assert(std::meta::has_template_arguments(^^TypeList), "mp_unique requires a type_list");
  static_assert(std::meta::template_of(^^TypeList) == ^^type_list, "mp_unique requires a type_list");

  std::unordered_set<std::meta::info, std::consteval_hash<std::meta::info>> seen;
  std::vector<std::meta::info> unique_types;

  for (auto type_info : std::meta::template_arguments_of(^^TypeList)) {
    if (const bool is_unique = seen.insert(type_info).second; is_unique) {
      unique_types.push_back(type_info);
    }
  }

  return std::meta::substitute(^^type_list, unique_types);
}

template <class TypeList>
using mp_unique = [:mp_unique_reflected<TypeList>():];

using input = type_list<int, char, int, string, double, char>;
using filtered = mp_unique<input>;
using expected = type_list<int, char, string, double>;

static_assert(is_same_v<expected, filtered>);

std::hashコンパイル時に利用可能なようになっておらず、その予定もないため、非順序連想コンテナをコンパイル時に使用するためにはハッシュのカスタマイズがどのみち必要になります。そのため新しいコンパイル時専用ハッシュ型を追加することはコンパイル時の非順序連想コンテナの使用感を今より悪くすることはありません。このことは問題ではあるものの、この提案はその解決を行うことを意図していません。

ConstevalHash要件は次のように指定されています

  • 関数オブジェクト型
  • コピー構築可能かつ破棄可能
    • Cpp17CopyConstructibleかつCpp17Destructible
  • constevalオンリー型
  • Hの2つの実体は、同じ引数に対して同じ値を生成することを保証しない
    • HConstevalHash
    • 特に、翻訳単位間で値が異なることがある
  • Key型をHの関数呼び出し引数型、hHのオブジェクト、uKeyの左辺値、kKeyに変換可能な型の値として、次の操作が有効かつ指定されたセマンティクスを持つ
    • h(k) -> std::size_t
      • 返される値はkのみに依存する
      • 値はコンパイルを繰り返し実行しても安定している
      • 2つの異なる値t1, t2について、h(t1) == h(t2)となる確率は非常に小さく、1.0 / std::numeric_limits<size_t>::max()に近くなる
    • h(u) -> std::size_t
      • uを変更しない

提案文書より、実装例

template<>
struct consteval_hash<meta::info>
{
  consteval consteval_hash() = default;
  consteval consteval_hash(const consteval_hash<meta::info>&) = default;
  consteval consteval_hash(consteval_hash<meta::info>&&) = default;

  consteval auto operator()(meta::info r) const noexcept -> size_t {
    return __metafunction(meta::detail::__metafn_reflection_hash, r);
  }

private:

  // This unused variable is here to make consteval_hash<> a
  // consteval-only type.
  [[maybe_unused]] const meta::info unused = ^^::;
};

P3818R0 constexpr exception fix for potentially constant initialization

P3818R1 constexpr exception fix for potentially constant initialization

現在の例外をチェックする関数を特定の定数評価コンテキストで評価しないようにする提案。

C++26では定数式での例外送出が可能になっており、それに伴ってstd::exceptionとその派生クラスなどの例外関連のユーティリティが定数式で実行可能になっています。例えば、std::current_exception()によってstd::exception_ptrを定数式で取得して処理することができます。

constexpr auto maybe_throw(int n);

consteval void f() {
  try {
    maybe_throw(1);
  } catch (...) {
    auto exptr = std::current_exception();  // ok
  }
}

std::current_exception()は定数式で呼ばれる場合、その定数評価コンテキスト内で送出されている例外オブジェクトを取得します。

ところで、定数評価はconstexpr変数の初期化やconsteval関数呼び出しなどの明示的な評価の開始以外でも行われることがあります。その一つはconstな整数型の変数の初期化式が定数評価可能な場合で、このような変数をpotentially constantな変数(定数かもしれない変数)と呼びます。

constexpr int f() {
  return 1;
}

int main() {
  const int n = f();  // nはpotentially constantな変数
}

定数かもしれない変数には正確にはconstexpr変数も含みますがここではそれは除外するとします。

この定数かもしれない変数はその初期化子が定数評価可能であれば、その結果を用いてコンパイル時に初期化され、定数式のコンテキストで使用可能になります。定数かもしれない変数であっても初期化式が定数評価可能でなければ、その初期化及び初期化式の評価は実行時に行われます。

std::current_exception()などの呼び出しはC++26から定数評価可能な式です。以前は定数評価可能ではなかったためこの変更が下位互換性を損ねることは無いはずでしたが、これらの式が定数かもしれない変数の初期化式に使用されている場合に動作が変化します。

try {
  // 例外を投げうるコード
    ...
} catch (const std::exception & exc) {
  // has_exceptionは定数かもしれない変数だが、C++23まではその初期化式は定数評価可能ではなかった
    const bool has_exception = (std::current_exception() != nullptr); // 定数評価された時にはそのコンテキストに現在の例外は存在しない
    static_assert(has_exception == false); // ✅、has_exceptionは定数式で使用可能
}

constな整数型(boolを含む)は定数かもしれない変数ですが、std::current_exception()の呼び出しはC++23までは定数評価不可能だったため定数評価されることはありませんでした。しかしC++26では定数評価可能になることによってこのhas_exceptionの初期化式は定数式としてコンパイル時に実行されます。この変数の初期化によって開始する定数評価コンテキストでは例外は送出されていないため、定数式ではcatchの内部であるにもかかわらずstd::current_exception()nullptrを返します。

これによって、has_exceptionは実行時の例外状態と全く関係なく常にfalseで初期化されることになります。

提案文書より、他の例

struct transaction {
    // ...
    void cancel() { /* revert changes */ }
    
    ~transaction() {
    // unrollingは定数かもしれない変数
        const bool unrolling = std::uncaught_exceptions() > 0;  // falseでコンパイル時に初期化される
        
        if (unrolling) { // this will never be evaluated
            log("exception was thrown in a transaction => cancel()");
            cancel();
        }
    }
}

定数かもしれない変数unrollingC++26から定数式によって初期化されるようになります。しかも、ユーザーの意図とは全く異なる結果によって初期化されます(常にfalse)。

これらのように、変数の初期化式から開始される定数評価コンテキストではその評価の内部で例外が送出されない限り現在の例外オブジェクトが存在する状態になりません。そのため、定数かもしれない変数の初期化式でstd::uncaught_exceptions()std::current_exception()が使用されていて初期化式全体が定数評価可能だと、これらの関数は常に現在の例外が無い状態で評価され結果を取得します。

この提案はこの問題を解消しようとするもので、std::uncaught_exceptions()std::current_exception()constexprは維持したうえで、これらの関数が定数かもしれない変数の初期化のために定数評価される場合に呼び出しを定数式ではなくすることを提案しています。

これにより、上記の2例のようなケースではstd::uncaught_exceptions()std::current_exception()の呼び出しが定数式ではなくなることで、定数かもしれない変数の初期化はC++23までと同じく実行時に行われるようになります。

この提案の内容に対応するNBコメントが3件提出されているようで、それを受けてLEWGでこの提案とこの問題について審議された結果、この提案の変更は大きすぎるとしてstd::uncaught_exceptions()std::current_exception()から単にconstexprを取り除くことに若干の合意があったようです。これを受けてこの問題の解決はP3842R0に委ねられたようです。

P3819R0 Remove evaluation_exception() from contract-violation handling for C++26

std::contracts::contract_violation::evaluation_exception()を削除する提案。

C++26 Contracts機能における違反ハンドラの引数型であるstd::contracts::contract_violation型には、その違反ハンドラが契約述語からの例外送出によって呼び出されていた場合にその例外オブジェクトを取得するための関数.evaluation_exception()が用意されています。

void handle_contract_violation (const contract_violation& cv) {
  if (auto exptr = cv.evaluation_exception(); exptr != nullptr) {
    // 例外処理
    ...
  }
}

違反ハンドラを呼び出した契約違反が契約述語がfalseを返したことによって起きていた場合、.evaluation_exception()nullptrを返します。

契約違反が例外によって起きたかどうかということ自体は.detection_mode()std::detection_mode::evaluation_exceptionであるかを調べることによっても行えます。こちらの場合は再スローを行うことで現在の例外をキャッチすることができます。

void handle_contract_violation (const contract_violation& cv) {
  if (cv.detection_mode() == detection_mode::evaluation_exception) {
  try {
    throw;
  } catch (std::exception& e) {
    // 例外処理
    ...
  }
}

この方法は.detection_mode()のチェックを忘れた場合に失敗するか、悪い場合は関係ない例外を処理してしまう可能性があります(違反ハンドラ自体はcatch節の中でも呼ばれる)。.evaluation_exception()はこのような冗長なチェックを回避して、違反ハンドラの呼び出しの原因となった例外オブジェクトに簡単にアクセスするために追加されたものです。

.evaluation_exception().detection_mode()を用いた方法の構文糖衣として提案されましたが、現在の例外ではなく違反ハンドラの呼び出しの原因となった例外オブジェクトを常に取得するものであり、このことに実装上の問題がありました。

void handle_contract_violation (const contract_violation& cv) {
  // ...
  try {
    // ...
    throw X;
  } catch (...) {
    if (cv.detection_mode() == detection_mode::evaluation_exception) {
      // 現在の例外オブジェクトをXであり、契約述語から送出されたものではない
      auto evaluation_exception_ptr = cv.evaluation_exception();
      // evaluation_exception_ptrはXではなく元の例外を処理する
    }
  }
}

このような動作を実現するためには、契約違反の原因となった例外オブジェクトを通常の例外状態から退避しておく必要があります。一部の実装ではそのために例外オブジェクトのコピーを行う必要があり、そのコピーコンストラクタを通して例外送出による契約違反と違反ハンドラ呼び出しの間にユーザーコードが実行されてしまう可能性があります。

違反ハンドラは契約違反が起きている状況(スタック破損などが起きている可能性がある)で実行することが想定されているため、そのような状況に対してある程度堅牢に記述することができる一方、例外オブジェクトのコピーコンストラクタはそうではなく、スタックトレースを取得しようとしてスタックを走査する可能性があります。そして、これがセキュリティ上の脆弱性につながる可能性があります(破壊されたスタックを走査するコードを通して任意の場所にジャンプさせることで、任意のコードを実行する)。

Itanium ABIを採用するプラットフォームでは例外オブジェクトのコピーを回避する実装が知られている一方、Windows ABIでは現時点でそのような実装を確認できていません。.evaluation_exception()を今のまま導入しようとすると、このようなセキュリティリスクを回避するための要件をC++の例外システムに新たに課すことになります。

これらの問題のため、この提案では.evaluation_exception()が全てのプラットフォームで安全に実装可能であることが確認されるまで標準に導入しないようにすることを提案しています。

前述のように、問題はあるものの.detection_mode()のチェックとstd::current_exception()という代替手段があるため、.evaluation_exception()を削除しても同等のことができなくなるわけではありません。こちらの方法の場合でも例外のコピーは発生しえますが、それは違反ハンドラ内のユーザーコードで見えている物であり制御可能なものです。

この提案はNBコメント解決として2025年11月の全体会議でC++26向けに採択され、C++26に適用されています。

P3820R0 Split constexpr uncaught_exceptions into distinct runtime and consteval functions

P3820R1 Fix constexpr uncaught_exceptions and current_exception

std::uncaught_exceptions()std::current_exception()からconstexpr指定を削除する提案。

この提案の指摘する問題やモチベーションは少し上のP3818R1と共通しているのでそちらをご覧ください。

この提案はP3818R1とは異なり問題に対して次の解決策を提案しています

  • std::uncaught_exceptions()からconstexpr指定を削除する
  • 定数式で使用できる専用のstd::consteval_uncaught_exceptions()を追加する
    • 呼び出された定数評価の中での例外をカウントする
  • std::current_exception()は、呼び出されたのと同じ定数評価の中で送出され現在処理中の例外が存在する場合にのみ定数式となる、ように変更
  • 定数式で使用できる専用のstd::consteval_current_exception()を追加する
    • (呼び出された時点での)現在の定数評価内で送出された現在処理中の例外オブジェクトへのポインタを返し、それ以外の場合はnullptrを返す

この提案の場合、std::uncaught_exceptions()は定数式で使用できなくなり、定数式で使用するには名前の違うstd::consteval_uncaught_exceptions()を使わなければなりません。これによって、実行時とコンパイル時でコードを共通する場合はif constevalによる分岐が必要になります。std::current_exception()は少し異なり、定数式で呼ばれたときにnullptrを返さない場合にのみ定数評価可能になります。

P3818R1のところでも述べたように、結局どちらの提案もC++26のNBコメント解決フェーズに導入するには適さないとして、P3842R0にてstd::uncaught_exceptions()std::current_exception()からconstexprを削除することのみをC++26向けの修正とするようです。

P3822R0 Conditional noexcept specifiers in compound requirements

複合要件において条件付きnoexcept指定をサポートする提案。

複合要件とは、主にコンセプト定義中のrequires式内部で制約を記述する形式の一つで、{expr} -> return-type;のような形式で記述する要件の事です。{expr} noexcept -> return-type;のようにすると式exprnoexceptであることを指定できますが、ここのnoexceptには条件を指定できません。これにより、関数テンプレートでよく使用される条件付きnoexceptを制約としては表現できません。

この提案は、複合要件のnoexcept制約においてnoexcept(condition)の形式を許可して、条件付きnoexcept指定を制約で表現できるようにするものです。

現在 この提案
template<typename F, bool noexc>
concept invocable = noexc
  ? requires(F f) { { f() } noexcept; }
  : requires(F f) { f(); };

template<bool noexc>
struct callable_ref {
  callable_ref(invocable<noexc> auto&& fn);
  
  ...
};
template<typename F, bool noexc>
concept invocable = requires(F f) {
  { f() } noexcept(noexc);
};

template<bool noexc>
struct callable_ref {
  callable_ref(invocable<noexc> auto&& fn);
  
  ...
};

{expr} noexcept(condition) -> return-type;のように記述でき、conditiontrueと評価されているのにexprが例外を送出しうる場合、制約は満たされません。conditionfalseと評価されている場合はexprは例外を送出する可能性があることを表現します(この場合exprが例外を送出するかはチェックされない)。

条件なしのnoexceptnoexcept(true)と等価であり、noexcept(false)noexceptなしと等価です。

提案ではconditionが文脈的にboolに変換できない場合でもハードエラーにはせず、単に制約が満たされないようにすることを提案しています。これは、コンセプトはオーバーロード解決で使用されるため、予期しない入力に対しては他の候補が考慮されるという動作が期待されるからです。

P3823R0 Wording for US NB comment 10

trivially_relocatable_if_eligibleなどの文脈依存キーワードを削除する提案。

これは提出されたNBコメントの一つに対応するものです。ここでの主張はP1144での主張の一部でもあります。

trivially_relocatable_if_eligiblereplaceable_if_eligibleはリロケーションに関連して追加されたもので、クラスの宣言に対して付与して、そのクラスがリロケーション(特にトリビアルなリロケーション)が可能であることをクラスのプロパティとして追加するものです。

struct Y trivially_relocatable_if_eligible {};

これには次のような問題点が指摘されています

  • 在野の主要なプロジェクト/ライブラリにおいて使用されている・求められている意味論と整合しないため、使用できない
  • 現在リロケーションを利用している実装は、C++26モード以外ではこのキーワードを使用できない
  • C++26モードでも、optionalinplace_vectorをサポートするには不十分なことが分かっている

このような理由から、これらの文脈依存キーワードはC++26では一旦削除してC++29以降に向けて再検討することを求めるのが、NBコメント(US NB comment 10)の内容です。ただし、リロケーションというコア言語およびライブラリの仕様そのものを削除することは提案していません。

P3824R0 Static storage for braced initializers NBC examples

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

P2752R3の採択によって、C++23(DRなので実装済みのコンパイラ)ではその初期化子が全て定数であるような初期化子リストの補助配列(backing array)を静的ストレージに昇格させて保持して、静的記憶域期間を与えることが許可されています。

#include <initializer_list>

const char& first() {
  std::initializer_list<char> il = { 'h', 'w' };
  return *il.begin();
}

const char& first(std::initializer_list<char> il)
{
  return *il.begin();
}

int main() {
  const char& rilm = first();
  const char& pilm = first({ 'h', 'w' });
  return 0;
}

rilm, pilmはどちらも、コンパイラが対応する初期化子リストを静的ストレージに配置した場合にダングリング参照ではなくなります。しかし、P2752の仕様はあくまでコンパイラがそれを行うことを許可するためのもので、これを保証してはいません。

P2752は#embedが初期化子リストを利用する際にスタック領域に読み込んだバイト列を配置してしまうのを防止するためのものでした。

// こうすると2MBの補助配列がスタックに置かれる
std::vector<char> v = {
  #embed "2mb-image.png"
};

// こうすると回避できる
static const char backing[] = {
  #embed "2mb-image.png"
};
std::vector<char> v = std::vector<char>(backing, std::end(backing));

結局P2752の後でも初期化子リストの静的ストレージへの昇格は保証されていないため、プログラマはこのような回避策を書かざるを得ません。特に、移植可能なコードを書くときはそうですし、同じコンパイラでもコンパイルオプションで結果が変わる可能性があるため結局こう書くしかなくなります。

この提案は、P2752で導入された使用を許可ではなく保証することで、これらの問題を解消することを提案しています。これにより、最初の例では常にダングリング参照が生成されなくなり、2つ目の例の様な回避策を書く必要がなくなります。

また、一要素の初期化子リストが静的ストレージに配置されることを許可している延長として、定数リテラルで初期化される単一のオブジェクトに対しても同様に静的ストレージへ昇格させることを提案しています。

// P2752によって静的ストレージへ昇格することが許可されている
std::initializer_list<int> x1 = { 1 };

// P2752の適用外だが、これらを含めることを提案している
const int& x1 = {1};
const int& x2{1};
const int& x3 = 1;

// x4をstatic変数にすることを提案しているわけではない
const int x4{1};

この場合、静的ストレージに昇格される対象はx1, x2, x3の参照に束縛されているオブジェクトであって、定数リテラルで初期化されるオブジェクトに対応する変数(x4)を静的ストレージに昇格することを提案しているわけではありません。初期化子リストも文字列リテラルもどちらも参照セマンティクスを持つものであり、静的ストレージに昇格されるのは参照そのものではなく背後にある参照されている物です。

P3827R0 Wording for US NB comment 9

リロケーションによって導入された"replaceability"という用語とその仕様箇所を削除する提案。

この提案は"replaceability"という概念に反対するNBコメントに応じて書かれたものの様です。

この概念は既存のreplaceabilityという言葉を使用しているところと言葉が被っており、対応する型特性を除いてライブラリ機能では使用されていないようです。

リロケーションを利用するライブラリ実装者が使用することを意図しているようですが、その定義についても問題や反対意見があるため概念および関連する専門用語ごと削除しようとしています。

P3829R0 Contracts do not belong in the language

Contracts機能をそれ専用に設計するのではなく、より汎用的に設計された機能の組み合わせによって実現するようにする提案。

P2900で提案されC++26に導入されたContracts機能の言語機能の側面は汎用的な価値を持つ機能の特殊なケースとなっているため、それらを分解してより汎用的な個別機能としてC++に導入し、それを組み合わせてC++ Contractsを構成することで、P2900が抱えているような問題を解決してより豊富な契約APIをライブラリ機能として提供することができるとしています。

そのためにC++26のContractsをリジェクトして、このような汎用機能を個別並行的に設計しC++29で追加して評価するようすることを提案しています。

この提案が挙げている、Contractsがその要件によって導入している専用の言語機能であり、分解してより汎用的な機能として考えられるものはつぎの4つです

  1. ODRの緩和
    • あるヘッダに記述された同じ関数の定義の契約アサーションが、翻訳単位ごとに異なる評価セマンティクスを持つことを許可している(ことがODRの緩和
    • P2900ではこれについて実装定義としているが、広く有用なためこのODR緩和のセマンティクスを明確に規定する必要がある
  2. 関数デコレータ
    • pre/postアサーションは関数呼び出しの前後で処理を実行する専用の構文になっている
    • これはpythonではデコレータとしてより汎用的な機能になっており有用であるため、このデコレータ機能によってpre/postアサーションを実現する
      • 特に、リフレクションを使用することでライブラリラベルでContractsを提供できるようになる
  3. 遅延評価
    • 契約アサーションを関数で実装する場合、引数の評価が回避できず、これが言語機能としてのアサーションの必要性の大きな部分だった
    • [[lazy]]属性の様な引数の遅延評価機能によって通常の関数で利用できるようにする
  4. 深いconst
    • P2900 Contractsの暗黙const化は浅いconstであり、ポインタの参照先までconstにならない
    • 深いconstを実現する言語機能を導入して通常のコードでも使用するようにできる
      • 仮にこのような言語機能が後から導入された場合、後方互換性によって契約アサーションのデフォルトを変更できない

違反ハンドラに関しては、そもそも統一的なエラーハンドラは使いづらいものであるとして重要視していないようです。

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

P3830R0 NB-Commenting is Not a Vehicle for Redesigning inplace_vector

NBコメントを通したinplace_vectorの再設計の試みに反対する提案。

C++26は2025年6月の全体会議の時点で機能追加は終了しており、あとはNBコメント(各国委員会からのC++26CDへのフィードバックコメント)への対応のみが実質的な変更が入るフェーズとなっています。

そこで、inplace_vectorに対する設計変更を求めるコメントがいくつか来ているようで、この提案はそれらを採択しないようにすることを強く主張するものです。

来ているNBコメントは主に次の事項についてのものの様です

  • アロケータ対応
    • P3160R2
    • 要素型に対してアロケータを伝播させるAllocator aware性をサポートさせる
  • キャパシティも参加する比較
    • P3698R0
    • 異なるキャパシティを持つinplace_vectorの比較ができないのを修正する
  • optional<T&>の使用
    • P3739R4
    • try_*_back()の戻り値をT*ではなくoptional<T&>にする

この提案ではいずれの設計変更についてもC++26のスコープでは反対しており、LEWGで合意済みの設計をLEWGで合意を得られなかった設計によってこのC++26スコープの最後で変更すべきではなく、NBコメントを利用してそのような再設計をしようとすべきではないとしています。

アロケータとoptional<T&>については否定的ですが、比較の問題についてはC++29以降で修正の可能性があるものの検討に時間が必要としています。

P3831R0 Contract Labels Should Use Annotation Syntax

契約アサーションに対するラベルの指定のために、アノテーション構文を使用するようにする提案。

P3400R1ではC++29以降のContracts機能拡張の大きなものの一つとして契約アサーションに対するラベルの指定を提案しています。契約アサーションに対するラベルは、契約アサーションのセマンティクス(評価セマンティクスや違反処理など)をよりローカルに制御するためのものです。

struct my_label_t {};

constexpr my_label_t my_label;

int f(int i)
    pre<my_label>(i > 0)
    post<my_label>(r: r > 0)
{
    contract_assert<my_label>(i > 0);
    // ...
}

P3400R1ではpre<my_label>(expr)のように、アサーションを導入するキーワードに続く<>の中にラベルを指定します。これは各アサーション構文を関数と見立てた時に、関数をテンプレートによってパラメータ化することを模倣した構文です。しかし、これにはいくつか問題があります

  • NTTPに対するクラス型の要件とラベルオブジェクトの要件が異なる
    • NTTPではクラス型は構造的な型である必要がある一方、ラベル型は単にクラス型であればよい
    • 要件が異なるものが同じ構文を使用していると混乱を招く
  • このラベル指定構文のセマンティクスを個別に指定する必要がある
    • ラベル指定構文という契約アサーション専用の構文を導入すると、そのセマンティクスを個別に管理し、言語の他の部分との相互作用を常に考えなくてはならなくなる
  • 違反ハンドラからラベルオブジェクトを取得する構文が使いづらい
    • void* contract_violation::control_object()を提案しているが、戻り値は型情報が欠落している
    • 戻り値はラベルオブジェクトが多態的である場合のみ非nullptrを返し、dynamic_castで復元する必要があり、危険
  • 環境ラベル(ambient-control objects)に関して未解決の設計上の問題がある

契約アサーションに対するラベル指定は本質的に、無視できない属性構文を導入しようとしています。C++26にはこのためのソリューションが導入されており、それはリフレクションにおけるアノテーションです。

P3394R4で導入されたリフレクションにおけるアノテーションは、宣言にアノテーションを付加しそれを読み取れるようにする機能です。このようなアノテーションはリフレクションによるコード生成において便利なものであり、専用の構文とAPIによって扱えるようになっています。

struct C {
  [[=1]]  int a;
};

無視できない属性としてアノテーションを利用することには次のようなメリットがあります

  • 属性構文の構文スペースを利用しているため、構文スペースを確保する必要がない
  • アノテーションは無視できない属性そのものであるため、属性構文を利用することには妥当性がある
  • アノテーションをクエリするAPIはすでに用意されているためそれを利用でき、リフレクションの他の部分との一貫性が向上する

これらにより、この提案では契約アサーションに対するラベル指定構文として独自の構文を発明するのではなくアノテーションを利用する事を提案しています。

P3400R1 この提案
struct my_label_t {};

constexpr my_label_t my_label;

int f(int i)
  pre<my_label>(i > 0)
  post<my_label>(r: r > 0)
{
  contract_assert<my_label>(i > 0);
  ...
}
struct my_label_t {};

constexpr my_label_t my_label;

int f(int i)
  pre [[=my_label]] (i > 0)
  post [[=my_label]] (r: r > 0)
{
  contract_assert [[=my_label]] (i > 0);
  ...
}
P3400R1 この提案
struct my_label_2_t {};
constexpr my_label_2_t my_label_2;

void g(int i)
  pre<my_label | my_label_2>(i > 0);
struct my_label_2_t {};
constexpr my_label_2_t my_label_2;

void g(int i)
  pre [[=my_label | my_label_2]] (i > 0);
// or, optionally
  pre [[=my_label, =my_label_2]] (i > 0);

違反ハンドラにおけるラベルの取得のAPIは、annotations()というmeta::infoのシーケンスを取得する関数をcontract_violationに追加することを提案しています。

namespace std::contracts {
  class contract_violation {
  public:
    ...

    consteval std::vector<std::meta::info> annotations() const noexcept;
    
    ...
  }
}
struct my_dynamic_tag {
  constexpr my_dynamic_tag() = default;
};
inline constexpr my_dynamic_tag my_dynamic_label;

void handle_contract_violation(const contract_violation& violation) {
  // ラベルの取得
  constexpr auto annotations = std::define_static_array(violation.annotations());
  template for (constexpr auto annotation : annotations) {
    if constexpr (std::meta::is_same_type(
      std::meta::type_of(annotation), ^^my_dynamic_tag
    )) {
      std::cout << "Dynamic Tag!\n";
      return;
    }
  }
  std::cout << "No Dynamic Tag\n";
}
void f()
  pre<my_dynamic_label>(false)  // prints "Dynamic Tag!"
  pre(false);                   // prints "No Dynamic Tag"

さらに、P3400R1の環境ラベルの指定(Ambient-Control Objects)については次のような設計上の決定事項が未解決であると指摘しています

  • ラベルは対象の契約アサーションのラベルリストのどこに追加するのか?
    • |による合成が対称的ではない場合、問題になる
  • 暗黙の制御オブジェクト宣言(環境ラベルの注入宣言)はなぜ複数回の宣言ができないのか?
  • 基底クラスや囲む名前空間などで指定された環境ラベルを継承することはできるか?

また、P3394R4ではアノテーションにコールバックを持たせて、宣言からそれを自動的に呼び出すようにする方向性の有用性について言及されており(提案はされていない)、このような機能とリフレクションによるコード生成機能(C++29以降のもの)を検討すると、ラベルの自動的な付与が可能になる可能性があります。例えば、名前空間やクラスなどに特定のアノテーション(例えば[[=attach(label1, label2)]])を付加することで、それに属するすべての宣言に対して自動的にラベルを付加することができるかもしれません。

これらのことから、より汎用的なアノテーションコールバック機能を検討するために、環境ラベルの指定については延期することを推奨しています。

P3832R0 Timed lock algorithms for multiple lockables

タイムアウト付きでミューテックスのロックを取得する関数の提案。

複数のロック可能オブジェクト(ミューテックス)に対してロックを獲得する処理を簡素化するために、std::lockstd::try_lockstd::scoped_lockなどが用意されています。これらの操作はBasicLockableLockable要件の範囲でロック取得を行いますが、TimedLockable要件を満たすオブジェクトのtry_lock_*に対応するロック取得を行う操作がありません。

これにより、複数のミューテックスの時間制限付きのロック取得を行おうとするユーザーは、try_lock(), unlock()とリトライを使用して独自のデッドロック回避ロック取得操作を記述する必要があります。

この必要をなくすために、この提案では複数のミューテックスの時間制限付きのロック取得を行うための関数を追加することを提案しています。

追加しようとしているのは次の2つの関数です

namespace std {
  template <class Clock, class Duration, class... Ls>
  int try_lock_until(const chrono::time_point<Clock, Duration>& abs_time,
                     Ls&... ls);

  template <class Rep, class Period, class... Ls>
  int try_lock_for(const chrono::duration<Rep, Period>& rel_time, Ls&... ls);
}

これはそれぞれTimedLockable要件にある操作のtry_lock_until()try_lock_for()に対応するもので、try_lock_until()は指定された時刻までの間にすべてのミューテックスに対してロック取得を試みる関数で、try_lock_for()は指定された時間内ですべてのロック取得を試みる関数です。戻り値はstd::try_lock()と同じ意味です。

std::lockstd::try_lockと異なる点として、受けるミューテックスの個数は0個からになっています。これは、unique_lockscoped_lockを組み合わせたunique_multilockの様な汎用の関数(0個以上のミューテックスを取る)を作成する際に簡単に使用できるようにするためです。

std::timed_mutex m1, m2;
if (std::try_lock_for(100ms, m1, m2) == -1) {
  // success
  std::scoped_lock sl(std::adopt_lock, m1, m2);
  ...
} else {
  // failed to acquire within timeout
  ...
}

P3834R0 Defaulting the Compound Assignment Operators

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

複合代入演算子+=-=など)には対応する二項演算子を用いた典型的な実装が広く知られており、異なる意味でオーバーロードされるのは稀です。そのため、この典型的な実装を言語におけるデフォルトとして、複合代入演算子default指定による実装に利用しようという提案です。

例えば整数型のラッパクラスの場合、四則演算に対応する演算子をすべて実装すると次のようになります

struct int_wrapper{
  int data_;

  int_wrapper(int in) : data_{in} {}
};

int_wrapper operator+(int_wrapper lhs, int_wrapper rhs) {
  return int_wrapper{lhs.data_ + rhs.data_};
}
int_wrapper& operator+=(int_wrapper& lhs, int_wrapper rhs){
  return lhs = lhs + rhs;
}
int_wrapper operator-(int_wrapper lhs, int_wrapper rhs) {
  return int_wrapper{lhs.data_ - rhs.data_};
}
int_wrapper& operator-=(int_wrapper& lhs, int_wrapper rhs){
  return lhs = lhs - rhs;
}
int_wrapper operator*(int_wrapper lhs, int_wrapper rhs) {
  return int_wrapper{lhs.data_ * rhs.data_};
}
int_wrapper& operator*=(int_wrapper& lhs, int_wrapper rhs){
  return lhs = lhs * rhs;
}
int_wrapper operator/(int_wrapper lhs, int_wrapper rhs) {
  return int_wrapper{lhs.data_ / rhs.data_};
}
int_wrapper& operator/=(int_wrapper& lhs, int_wrapper rhs){
  return lhs = lhs / rhs;
}
int_wrapper operator%(int_wrapper lhs, int_wrapper rhs) {
  return int_wrapper{lhs.data_ % rhs.data_};
}
int_wrapper& operator%=(int_wrapper& lhs, int_wrapper rhs){
  return lhs = lhs % rhs;
}

ここでの複合代入演算子の実装はすべて対応する二項演算子を用いた典型的なものであり、これをデフォルトとしてdefault実装できるようにする場合次のように書き直せます

struct int_wrapper{
  int data_;

  int_wrapper(int in) : data_{in} {}
};

int_wrapper operator+(int_wrapper lhs, int_wrapper rhs) {
  return int_wrapper{lhs.data_ + rhs.data_};
}
int_wrapper operator-(int_wrapper lhs, int_wrapper rhs) {
  return int_wrapper{lhs.data_ - rhs.data_};
}
int_wrapper operator*(int_wrapper lhs, int_wrapper rhs) {
  return int_wrapper{lhs.data_ * rhs.data_};
}
int_wrapper operator/(int_wrapper lhs, int_wrapper rhs) {
  return int_wrapper{lhs.data_ / rhs.data_};
}
int_wrapper operator%(int_wrapper lhs, int_wrapper rhs) {
  return int_wrapper{lhs.data_ % rhs.data_};
}

int_wrapper& operator+=(int_wrapper& lhs, int_wrapper rhs) = default;
int_wrapper& operator-=(int_wrapper& lhs, int_wrapper rhs) = default;
int_wrapper& operator*=(int_wrapper& lhs, int_wrapper rhs) = default;
int_wrapper& operator/=(int_wrapper& lhs, int_wrapper rhs) = default;
int_wrapper& operator%=(int_wrapper& lhs, int_wrapper rhs) = default;

この提案は、複合代入演算子についてこのようなdefault実装を可能にすることを提案しています。

この提案における複合代入演算子(以下+=で代表)のデフォルト実装では、次のシグネチャを許容します

// AとBは同じ型であってもよい
A& operator+=(A& lhs, const B& rhs) = default;
A& operator+=(A& lhs, B rhs)        = default;
A& operator+=(A& lhs, B&& rhs)      = default;

// P2953が禁止しようとしているシグネチャ(ひとまずサポートを提案
A& operator+=(A&& lhs, const B& rhs) = default;
A& operator+=(A&& lhs, B rhs)        = default;
A& operator+=(A&& lhs, B&& rhs)      = default;

これらのシグネチャはそれぞれ意味が異なるため、ある型Aに対して同時に定義することができます。いずれのシグネチャにおいても、その関数本体は次のような実装と同等になります

{
  return lhs = std::move(lhs) + std::move(rhs);
}

このデフォルト宣言は通常のメンバ関数、フリー関数、明示的オブジェクト引数を取る関数、でサポートすることを提案しています。

struct S1{
  int value_;
  S1 operator+(S1 rhs) const { /*...*/ }
  S1& operator+=(S1 rhs) = default; // メンバ関数
};

struct S2{
  int value_;
  S2 operator+(S2 rhs) const { /*...*/ }
  S2& operator+=(this S2& lhs, S2 rhs) = default; // 明示的オブジェクト引数を取る関数 
                                                  // 引数型はそのクラスと同じ型(のCV参照修飾)でなければならない
};

struct S3{
  int value_;
  S3 operator+(S3 rhs) const { /*...*/ }
};
S3& operator+=(S3& lhs, S3 rhs) = default;  // フリー関数

このような複合演算子のデフォルト実装に使用される対応する二項演算子は、このデフォルト実装(の関数本体のコンテキスト)からアクセス可能であればよく、非メンバやメンバなど定義方法を問いません。

デフォルト実装における一番右側の代入演算子の戻り値型がA&(第一引数の素の型の左辺値参照型)ではない場合、デフォルト実装のコンテキストでABのどちらかが完全型ではない場合、コンパイルエラーとなります。また、デフォルト実装のコンテキストから互換性のある引数型を持つ対応する二項演算子が見つからない(アクセスできない)場合、そのデフォルト実装は暗黙的にdeleteされます。これらの事は、通常の代入演算子default実装の規則と一致しています。

P3835R0 Contracts make C++ less safe -- full stop

現状のContracts機能は安全性を低下させるのでC++26から削除する提案。

C++26のContracts機能にはセマンティクスを個別に制御する方法がなく、特定のコンポーネント(翻訳単位)にだけ特定のセマンティクスを持たせるということができません。それにより、あるプログラム内で契約チェックを確実に行おうとする時でも、翻訳単位を跨いだ別のコンポーネントや共有ライブラリまでそれを強制する方法がありません。

これにより、ライブラリ出荷側が契約チェックを有効化して出荷しており特定の契約違反が検出されることを期待している場合でも利用者側が契約チェックをオフにしていれば検出されなくなる可能性があります。

この提案ではこのことによって契約機能によりC++コードの安全性がむしろ低下するとして、C++26から削除することを提案しています。

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

P3836R0 Make optional<T&> trivially copyable

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

C++26に導入されたstd::optional<T&>std::optionalの部分特殊化として別に実装され、特にポインタのラッパとして実装されることが想定されます。そのためstd::optional<T&>トリビアルコピー可能であるはずですが、明確に規定されてはいません。

標準ライブラリの概要では次のようなクラス型として説明されています

namespace std {
  template<class T>
  class optional;

  template<class T>
  class optional<T&> {
    ...

    public:
      // [optional.ref.ctor], constructors
      constexpr optional() noexcept = default;
      
      ...

      constexpr ~optional() = default;

      ...

    private:
      T* val = nullptr; // exposition only
  };
}

これはあくまで説明のための例であり、実装はここにメンバを追加することができるため、純粋なT*のラッパとなりトリビアルコピー可能性が満たされるかは実装の裁量によります。とはいえ、それを要求することは過剰な制約にはならないはずです。

これは単に仕様の見落としであると思われるため、std::optional<T&>トリビアルコピー可能であることを規定し保証しようとする提案です。

これには

  • プライマリテンプレートであるstd::optional<T>との一貫性が保たれる
  • トリビアルコピー可能性により、std::optional<T&>をプロセス間やCPUとGPU間でコピーされる構造体に格納できる
  • トリビアルコピー可能性により、std::optional<T&>は暗黙的な生存期間を持つ型(implicit-lifetime class type)になり、代入演算子を用いて共用体内で生存期間を開始できるようになる

などのメリットがあります。

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

P3838R0 Restoring Private Module Fragments

プライベートモジュールフラグメントの宣言が文法違反にならないようにする提案。

プライベートモジュールフラグメントは一つのファイルでモジュールのインターフェースと実装を記述するための機能で、プライベートモジュールフラグメント宣言によってファイルをインターフェース部と実装部に分割します。

module M;

...

module : private; // プライベートモジュールフラグメント宣言

...

P3034R1ではモジュール名としてマクロを使用できないようにプリプロセッサの文法調整が行われましたが、この時の文法調整によってこのプライベートモジュールフラグメントの宣言が名前の無いモジュールパーティションとして解釈されるようになっていました。

module M:part;  // モジュールパーティションの宣言

無名のモジュールは許可されないため、これはプリプロセッサより後の翻訳フェーズにおいてエラーになります。

モジュール宣言はプリプロセッサで特別に処理された後ユーザーが記述できないキーワードによる宣言に置き換えられてC++コードとしてコンパイルされます。P3034R1のモジュール宣言のプリプロセッサ文法はおおむね次のようなものでした

pp-module:
  export(opt) module pp-tokens(opt) ; new-line

このpp-tokensの部分は次のような文法です

pp-module-name pp-module-partition(opt) pp-tokens(opt)

pp-module-partition:
  : pp-module-name-qualifier(opt) identifier

この定義に照らすと、module : privateは名前が無いモジュールパーティションとしてプリプロセッサでは解釈されてしまいます。プライベートモジュールフラグメントには専用のプリプロセッシングディレクティブ定義が用意されていますが、文法的にはこちらにかかってしまうようです。

この提案はこの問題の解決のために文法を調整しようとするものです。

修正はモジュール名とパーティション名の文法定義を分割することによって行われています

pp-module:
  export(opt) module pp-module-name(opt) pp-module-partition(opt) pp-tokens(opt) ; new-line

モジュール名が必須であることはpp-module-nameの文法で定義しています。

なお、この提案はモジュール名やパーティション名をマクロで導入できるようにはしていません。

理由は明確ではないですが、この提案はリジェクトされたようです。

おわり

この記事のMarkdownソース

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

文書の一覧

全部で26本あります。

もくじ

N5013 Programming Languages - C++

C++26規格書のCommittee Draft。委員会に所属していないと見られないようです。

N5014 Working Draft, Standard for Programming Language C++

C++26のワーキングドラフト第8弾。

N5015 Editors' Report - Programming Languages - C++

↑の変更点をまとめた文書。

N5019 Business Plan and Convener's Report: ISO/IEC JTC1/SC22/WG21 (C++)

ビジネスユーザ向けのC++およびWG21の現状報告書。

P2414R10 Pointer lifetime-end zap proposed solutions

Pointer lifetime-end zapと呼ばれる問題の解決策の提案。

以前の記事を参照

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

などです。

P2843R3 Preprocessing is never undefined

プリプロセッサに存在する未定義動作を取り除く提案。

以前の記事を参照

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

  • EWGにおいて設計上の選択を確認
    • キーワードに一致するマクロはill-formedとなるべき
    • //コメント内で垂直方向のホワイトスペースをサポートする
  • フィードバックにより、一部のケースで現在のIFNDRを維持する
    • #includeディレクティブにおいて、マクロ展開後の標準形式のいずれとも一致しない場合
    • マクロが定義済みのpreprocessor operatorに展開される場合

などです。

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

P3100R4 A framework for systematically addressing undefined behaviour in the C++ Standard

UB(及びEB)を契約違反として扱うようにする提案。

以前の記事を参照

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

  • P3754R0に合わせて提案を更新
  • Sofia会議でのフィードバックを適用
  • UBリストからUBとして指定されるべきではなく、CWG Issueがあるものを削除
  • noexceptとの相互作用に関する議論を追加
  • 提案の構成を改善
  • 提案のスコープを反映してタイトルを更新

などです。

P3337R0 Graph Library: Library Comparisons

提案中のグラフライブラリと既存のグラフライブラリの比較を行った文書。

この文書は一連のグラフライブラリ提案の一部として、提案中のグラフライブラリ(の参照実装graph-v2)と既存のグラフライブラリ(主にBoost Graph: BGL)との書き味とパフォーマンスについての比較を行った結果を報告するものです。

文書では、まずP3128 でTier1として提示されているアルゴリズムの一部について比較を行っています。扱われているアルゴリズムは次のものです

両ライブラリでこれらのアルゴリズムがどのように記述できるかを比較し、大きなデータセットにおけるその実行パフォーマンスを比較しています。

提案文書より、幅優先探索コードの比較。

BGL

using namespace std;
using namespace boost;

using G = compressed_sparse_row_graph<directedS, no_property, no_property>;
using Vertex = graph_traits<G>::vertex_descriptor;

G g;
// populate g

vector<Vertex> parents(num_vertices(g));

auto vis = make_bfs_visitor(
  make_pair(
    record_predecessors(parents.begin(), on_tree_edge())
  )
);

breadth_first_search(g, vertex(0, g), visitor(vis));

graph-v2

using namespace std;
using namespace graph;

using G = container::compressed_graph<void, void, void, uint32_t, uint32_t>;
using VId = vertex_id_t<G>;

G g;
// populate g

vector<VId> parents(size(vertices(g)));

auto bfs = edges_breadth_first_search_view<G, void, true>(g, 0);

for (auto&& [uid, vid, uv] : bfs) {
  parents[vid] = uid;
}

詳細な比較結果については提案を参照してください。

全体として、最新のC++機能を使用することでよりシンプルに記述できつつ、多くの場面でBGLを大きく上回るパフォーマンスを発揮しています。

P3347R4 Invalid/Prospective Pointer Operations

無効化されたポインタに対する一部の演算を明示的に許可する提案。

以前の記事を参照

このリビジョンでの変更は良くわかりませんが、編集者を追加したり、文書と文言の修正などのようです。

P3427R2 Hazard Pointer Synchronous Reclamation

ハザードポインタライブラリにSynchronous Reclamation機能拡張を追加する提案。

以前の記事を参照

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

  • hazard_pointer_asynchronous_reclamationhazard_pointer_try_reclamationにリネーム
  • retire_to_cohortの文言に、"May reclaim possibly-reclaimable members of c."を追加

などです。

P3428R2 Hazard Pointer Batches

ハザードポインタライブラリに複数のハザードポインタをまとめて構築・破棄する機能拡張を追加する提案。

以前の記事を参照

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

  • 事前条件を緩和し、それを文言に反映
  • reset_hazard_pointer_batchclear_hazard_pointer_batchにリネーム
  • span1, span2span_from, span_toに変更
  • なるべくNの代わりにspan.size()を使用する

などです。

P3643R1 std::to_signed and std::to_unsigned

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

以前の記事を参照

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

  • 制約を厳格化し、cv boolだけでなくCV修飾型全般を除外
  • 機能テストマクロにfreestandingを追加

などです。

P3688R2 ASCII character utilities

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

以前の記事を参照

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

  • ascii_case_insensitive_compare()の戻り値型の誤りを修正
  • ascii_case_insensitive_compare()の定義におけるstrong_orderingstd::プリフィックスを削除

などです。

P3692R2 How to Avoid OOTA Without Really Trying

P3064R2を要約した文書、および現在の実装ではOOTAが起こらないことを明確化する提案。

以前の記事を参照

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

  • volatileセマンティクスに関する変更について再検討
  • この提案が他のOOTA研究者にもたらす利点について言及
  • 著者リストの更新

などです。

P3702R1 Stricter requirements for document submissions (SD-7)

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

以前の記事を参照

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

などです。

P3719R1 std::is_vector_bool_reference

std::vector<bool>を検出する型特性を追加する提案。

以前の記事を参照

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

  • 概念・提案する文言・実装経験、について追加

などです。

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

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

以前の記事を参照

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

  • exception_ptrの検査を追加
  • define_static_{string,object,array}を追加
  • constを追加

などです。

P3774R1 Rename std::nontype, and make it broadly useful

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

以前の記事を参照

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

  • LEWGレビューを反映
    • std::nontypestd::constant_argへのリネーム
  • この提案はstd::nontypeのリネームのみを行うものであることを明確化
    • 以前の提案内容も残しておく
  • §2.2. Post-Sofia decisions にLEWGレビューの決定事項を要約
  • abstractの更新

などです。

この提案は、2025年11月の全体会議を通過し、C++26に採択されています。前述のように、リネームのみを行っています。

P3775R0 Slides for P3774R0 - Rename std::nontype

P3774R0の紹介スライド。

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

このスライドではC++26タイムフレーム内でのstd::nontypeの経緯を説明し、P3774R0での変更を簡単に紹介しています。

P3779R0 reserve() and capacity() for flat containers

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

std::flat_setstd::flat_mapstd::flat_multisetstd::flat_multimapはメモリ上で連続した領域にソート済み配列を構築することでメモリの局所性を高め、イテレーションのパフォーマンスを向上させた連想コンテナです。

これらのコンテナはいずれも内部で別のシーケンスコンテナを使用しており、デフォルトではstd::vectorが使用されています(最後の方のテンプレート引数で変えられる)。

namespace std {
  // flat_mapの宣言例
  template <class Key,
            class T,
            class Compare = less<Key>,
            class KeyContainer = vector<Key>,
            class MappedContainer = vector<T>>
  class flat_map;
}

flatコンテナはこれらの内部コンテナに関する操作をほとんど公開しておらず、特にreserve()の様なパフォーマンス上重要なインターフェースも公開していません。

そのため、flatコンテナでreserve()を行おうとすると、.keys()/.values()を使って得たconst参照をconst_castするか、.extract()を使って取り出したコンテナを.replace()で戻す、という方法を取らざるを得ません。

std::flat_map<std::string, std::string> fmap = ...;

// 1. 内部コンテナ参照をconst_cast
const_cast<std::vector<std::string>&>(fmap.keys()).reserve(100);     
const_cast<std::vector<std::string>&>(fmap.values()).reserve(100); 

// 2. 取り出した内部コンテナを戻す
auto tmp = std::move(fmap).extract(); 
tmp.keys.reserve(100); 
tmp.values.reserve(100); 
fmap.replace(std::move(tmp.keys), std::move(tmp.values)); 

1つ目の方法は、set系のコンテナが.keys()/.values()を提供していないためflat_setなどでは使えません。2つ目の方法は、これらのインターフェース特有の事情による注意事項がいくつかあるためリスクがあります。

この提案はこの問題の解決のために、.reserve().capacity()flatコンテナに追加することを提案しています。

std::flat_map<std::string, std::string> fmap = ...;

// proposed: 
fmap.reserve(100); 

// proposed: 
if (fmap.capacity() == fmap.size()) { 
  fmap.reserve(100); 
} 
  • .reserve()
    • 内部コンテナのどちらか片方がサポートしている場合に提供
    • サポートしている内部コンテナに対して対応する.reserve()を呼び出す
  • .capacity()
    • 内部コンテナの両方がサポートしている場合に提供
    • 内部コンテナの返す.capacity()の値の最小値を返す

そして、set系のコンテナに対して.keys()/.values()メンバ関数を追加することも提案しています。

std::flat_set<std::string> fset = ...;

// どちらの関数からも同じコンテナが取得できる
const auto& data1 = fset.keys();     
const auto& data2 = fset.values(); 

set系のコンテナの場合、key=valueであるので.keys()/.values()メンバ関数はどちらも同じ内部コンテナへのconst参照を返します。

P3790R1 Pointer lifetime-end zap proposed solutions: Bag-of-bits pointer class

ポインタの参照先の寿命が尽きた後でも使用可能なライブラリユーティリティを提供する提案。

以前の記事を参照

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

  • launder_bag_of_bits_ptr()bag_of_bits_ptr<T>launder_ptr_bits()ptr_bits<T>にリネーム
  • 以前のusable_ptr<T>に対する別名候補の追加

などです。

P3796R1 Coroutine Task Issues

std::taskの設計上の問題点を修正する提案。

以前の記事を参照

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

  • co_return { ... };をサポートする提案を追加
  • P3801R0への回答を追加
  • co_yield with_error(e)が使いにくい理由を追記
  • change_coroutine_schedulerschedulerが代入可能であることを要求するという議論を追加
  • operator co_awaitの追加に関する議論を追加
  • bulktask_schedulerに関する議論を追加
  • 右辺値修飾子の欠落に関する議論を追加
  • return_valuereturn_voidに関する仕様が欠落しているという問題を追加
  • その他小さな修正

などです。

この提案では、以前の記事の分類における"その他の設計上の修正"のカテゴリに次の問題を追加しています

  • co_return { ... };のサポート追加
    • プロミス型のreturn_value()はテンプレートパラメータにデフォルト値が無いことで{}を使用できない
    • テンプレート引数のデフォルトを設定することで{}による初期化をサポートする
  • co_await change_coroutine_scheduler(sched)では代入可能なschedulerが必要になっている
    • 効果の規定でstd::exchangeを使用していることでschedに代入可能であることを要求しているが、schedulerは通常それが必須ではない
    • -> std::exchangeを使用しない定義で置き換えるか、代入可能性をschedulerの置換可能性の判定に使用する
  • sender非対応コルーチンはtaskco_awaitできるべき
    • いくつかの制約(提案文書 3.5.15 Sender Unaware Coroutines Should Be Able To co_await A task を参照)を考慮すると、現時点ではtaskoperator co_awaitを追加しないことが妥当と思われる
  • task_schedulerでのbulkの使用に関する問題
    • 通常のsenderと異なり、コルーチンで使用されるschedulerの型はtaskの作成時に認識されている必要があり、接続時にカスタムschedulerを使用するためには、型消去スケジューラであるtask_schedulerを使用する
    • senderアルゴリズムを特定のスケジューラ向けにカスタマイズする場合、task_schedulerが(型消去されていることによって)そのカスタマイズを伝達できないことで、意図通りに動作しない場合がある
    • bulkアルゴリズムparallel_schedulerがその典型例
    • -> いくつかの選択肢を提示
  • 右辺値修飾子の欠落
    • task_schedulerにネストされたsenderは必ずしもコピー可能ではなく、操作は型消去されるため、task_scheduler::ts-sender::connectは右辺値修飾する必要がある
    • コルーチンはコピーできず、コルーチンハンドルを別のオブジェクトに転送する場合その操作は右辺値修飾する必要があるため、task<T, E>::connectも右辺値修飾する必要がある
  • return_valuereturn_voidの仕様の欠落
    • プロミス型のreturn_valuereturn_voidは宣言されてはいるもののその仕様が欠落している
    • -> 対応する文言を提案

また、P3801R0への回答では提示されている問題点はこの提案でもある程度カバーされており理解できるものの、std::execution::taskを削除する理由にはならないとしています。

こことP3801で挙げられている問題のうち緊急性のあるものについてはNBコメント対応やLWG Issue対応としてC++26に向けて修正するつもりのようです。

P3798R1 The unexpected in std::expected

std::expected.has_error()を追加する提案。

以前の記事を参照

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

  • 例ではstd::expected<void, std::string>を使用するようにした
  • フィードバックによる文言の改善
  • __cpp_lib_expectedの値を更新
  • 謝辞の追加

などです。

P3806R0 views::cycle

入力範囲を無限に循環する範囲を生成するRangeアダプタ、views::cycleの提案。

views::repeat(v)vの繰り返しによる範囲を生成するのに対して、views::cycle(r)は範囲rを循環イテレーションすることによる繰り返し範囲を生成するものです。現在これは、views::repeat(r) | views::joinを用いて近似できます。

std::vector<int> r = {1, 2, 3};

for (auto&& v : views::repeat(r) | views::join) {
  // 1, 2, 3, 1, 2, 3, 1, 2... という要素列をイテレーションし続ける
  ...
}

これにはviews::joinを通すことによってもっとも強くてもbidirectional_rangeにしかならないなどの問題があり、専用のアダプタを用意することで直観的に使用できるようになるとして、views::cycleを追加することを提案しています。

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

for (auto&& song : playlist | views::cycle | views::take(100)) {
  play(song);
}

元の範囲を繰り返す性質上マルチパス保証が必要となるため、views::cycle(r)rforward_rangeである必要があります。

元の範囲をRとして、その他の性質は次のようになっています

  • rangeカテゴリ
    • Rrandom_access_rangeかつsized_range: random_access_range
    • Rbidirectional_rangeかつcommon_range: bidirectional_range
    • それ以外: forward_range
  • common_range: ×
  • borrowed_range: ×
  • sized_range: ×
  • const-iterable: const Rforward_rangeであれば

Range-v3にある同等のアダプタviews::cycledでは元の範囲の終端イテレータを積極的にキャッシュすることで、元の範囲のカテゴリを可能な限り維持しようとしますが、views::cycleではそれを行っていません(カテゴリがbidirectional_range/random_access_rangeになる場合の条件)。

また、views::cycleは同じ範囲を循環していることからiter_swapを提供しません。これは、イテレータとして異なる位置にある要素でも元の範囲上では同じ要素を指している可能性があり、その状況でiter_swapすると自己代入のようなことが起こってしまうためです。

P3809R0 Should we make std::linalg reductions deduce return types like fold algorithms?

std::linalgにあるリダクション系の関数の戻り値型の決定方法を変更しないようにすることを促す提案。

std::linalgにあるdot()vector_two_norm()などのリダクション(std::accumulateranges::fold_leftなどのように、範囲の各要素に何か処理をしてその結果を集計していくような計算)を行うような関数の戻り値型の決定方法は、std::reduceなどの設計を踏襲して初期値の型を戻り値型として使用します。しかし、C++23のranges::fold_leftなどfold操作においては、初期値と範囲の参照型を使用した二項演算の結果の型を推論して戻り値型として使用します。

// linalgのリダクション操作
std::mdspan<double, std::dims<1>> vec1 = ...;
std::mdspan<double, std::dims<1>> vec2 = ...;

std::same_as<int> auto dot = std::linalg::dot(vec1, vec2, 0); // ✅

// ranges::fold系リダクション操作
std::vector<double> vec3 = ...;

std::same_as<double> auto foldl = std::ranges::fold_left(vec3, 0, std::plus<>{}); // ✅

// std::reduce
std::same_as<int> auto reduce = std::reduce(vec3.begin(), vec3.end(), 0, std::plus<>{}); // ✅

このように、C++23以降のよく似た操作であってもその戻り値型の決定過程が異なります。また、ranges::fold_leftの戻り値型決定方法は、オリジナルのstd::accumulatestd::reduceも同様)において初期値の型で戻り値型が決まってしまうことが問題点として認識されていたことからそれを改善したものです。

また、数値アルゴリズムのRange版の提案(P3732R0)でもranges::fold_leftの設計を踏襲して同じ戻り値型の決定方法を採用していることもあり、std::linalgリダクション系の関数でもranges::fold_leftと同様の戻り値型の決定方法を取るべきではないかという声が上がったようです。

この提案は、それらの設計選択は合理的なものとしつつも、std::linalgは現在の動作が理に適ったものであるとして変更しない様にすることを提案するものです。

この理由としては次の事を挙げています

  1. 式テンプレートとの相性の良さ
    • std::reduceは式テンプレートによって計算が行われるような要素型をうまく扱えるが、ranges::fold_leftはそうではない
    • 数値計算分野においては式テンプレートが比較的良く使用されるため、考慮する必要がある
  2. BLAS規格との一貫性
    • BLASFortran 95インターフェースは、初期値の型によって戻り値型が決定される
  3. 戻り値型の簡単な制御
    • 線形代数領域と混合精度数値演算に精通しているユーザーは、戻り値型を推測するのではなく明示的に指定することを好む傾向にある

これらの理由から、std::linalgの現在の動作は線形代数計算の領域においては理に適ったものであるため、現状のまま変更しないことを推奨しています。

P3810R0 hardened memory safety guarantees

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

C++26では標準ライブラリに堅牢化モードを導入し、一部の事前条件が堅牢化された事前条件としてC++26 Contractsの枠組みによって実行時にチェックされるようになります(P3471R4/P3607R0)。

コンテナ型はそこには当然含まれていますが、そのチェックにおいては.size().empty()といったメンバ関数が使用されます。これは実装を考慮すると未定義動作の余地なく実装可能と考えられますが、標準ではそのようなことを一切指定していません。

C++26 Contractsそのものが未定義動作が無いこと(契約チェック時のUBフリー)を保証してはいませんが、契約チェックに使用されるこれらの関数がUBフリーであることを保証できれば、少なくとも標準ライブラリの堅牢化モードの範囲においてはそのチェックがUBを伴わないことを保証することができます。

これらの理由からこの提案では、堅牢化モードのチェックに使用される標準ライブラリ中の基本的とみなされる操作について、未定義動作無しの実装を要求することを提案しています。

提案での対象は次の関数です

  • .size()
  • .empty()
  • イテレータの差分計算(-
  • .static_extent()
  • .extent()
  • .has_value()
  • .valueless_by_exception()
  • std::holds_alternative<I>()

これらの関数はメンバ変数の値を返すだけの単純な実装になるはずであり、UBの余地なく実装できるはずです。そしてそれはすでに行われているはずでもあるため、この提案による実装の変更は必要なく、実装者にUBフリーであることを要求することは不合理ではないとしています。

おわり

この記事のMarkdownソース

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

文書の一覧

全部で143本あります。

もくじ

N5012 WG21 2025-06 Sofia Admin telecon minutes

2025年6月2日に行われた、WG21管理者ミーティングの議事録。

前回からどのような活動があったかや、Sofia会議で何をするかなどの報告がなされています。

N5016 WG21 June 2025 Sofia Hybrid meeting Minutes of Meeting

2025年6月にSofiaで行われた全体会議の議事録。

最終日に行われた全体会議での各グループの作業報告と、全体投票の様子が記録されています。

P0085R2 Oo... adding a coherent character sequence to begin octal-literals

P0085R3 Oo... adding a coherent character sequence to begin octal-literals

8進リテラルの新しいプリフィックスの提案。

以前の記事を参照

R2での変更は

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

  • 壊れていたユニコード文字を修正
  • Wikipediaの引用を更新
  • 8進数値none0o0)の仕様を修正
  • この提案ではstd::formatについては扱わないようにした

などです。

P0260R18 C++ Concurrent Queues

P0260R19 C++ Concurrent Queues

標準ライブラリに並行キューを追加するための設計を練る提案。

以前の記事を参照

R18での変更は

  • 設計セクションに、(提案している)概念がFIFO順序付けを必要としないことを明記
  • ブロッキングpop操作の不公平性に関する注記を追加
  • spurious failureに関する文言をmutex::try_lockと整合させた
  • SC-for-DRFのacquire/releaseに関する注記を追加

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

  • std::conqueue_errcstd::conqueue_statusにリネーム

などです。

P0870R6 A proposal for a type trait to detect narrowing conversions

P0870R7 A proposal for a type trait to detect narrowing conversions

Tが別の型Uへ縮小変換(narrowing conversion)を起こさずに変換可能かを調べるメタ関数is_convertible_without_narrowing<T, U>を追加する提案。

以前の記事を参照

R6での変更は

  • 設計上の決定事項について詳細を追加

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

  • 機能テストマクロを修正し、freestandingを追加
  • LWGに関する注記を追加

などです。

P0876R21 fiber_context - fibers without scheduler

スタックフルコルーチンのためのコンテキストスイッチを担うクラス、fiber_contextの提案。

以前の記事を参照

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

  • P3472R1を適用
    • fiber_context::can_resume()constにする
  • 提案する文言から“Instantiating”と“instance”を削除する
  • 付録A/Bの不適切な動作は、[except]に対する変更提案が適用される前の実装でのみ確認されていることを明記
  • “Recent WG21 History”セクションを追加

などです。

P1040R8 std::embed and #depend

#embedのライブラリ関数版であるstd::embedの提案。

以前の記事を参照

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

  • EWGは翻訳単位固有の依存関係(translation-unit specific dependencies)について合意
    • モジュールに関しては後日検討
  • 翻訳単位ベースの依存関係の文言を統一し、§ 5.3.5 Modules 関連の議論を削除
  • § 5.3.9 UTF-8 Only? に、std::u8string_view/std::string_view/std::wstring_view引数のオーバーロードを用意することになった実装経験について追記
  • § 5.3.7 Optional Limit と § 5.3.8 Optional Offset にlimitoffsetの根拠について追記
  • § 5.3.6 Statically Polymorphic で、std::bit_castの改善の必要性について言及することで、std::embed<TYPE>(...)コンパイル時のreinterpret_castを実現する手段として位置付けていることを明確化

などです。

P1306R5 Expansion statements

コンパイル時にステートメントをループ生成することのできる、展開ステートメントの提案。

以前の記事を参照

このリビジョンでの変更は、文章と文言を書き直したことのみです。

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

P2079R9 Parallel Scheduler

P2079R10 Parallel Scheduler

ハードウェアの提供するコア数(スレッド数)に合わせた固定サイズのスレッドプールを提供するSchedulerの提案。

R9での変更は

  • 文言の改善
  • try_queryにクエリパラメータを追加
  • バックエンド関数で使用していたuint32_tsize_tに変更
  • system_context_replaceabilityのreceiverクラス名をreceiver_proxyに変更

このリビジョンでの変更は、提案する文言の改善のみです。

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

P2319R5 Prevent path presentation problems

filesystem::path.string()メンバ関数を非推奨にする提案。

以前の記事を参照

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

  • Annex Dのエントリを簡素化し、stringgeneric_stringをそれぞれsystem_encoded_stringgeneric_system_encoded_stringで定義
  • [depr.move.iter.elem]などの他のエントリとの整合性を保つため、エントリの形式を変更
  • __cpp_lib_format_pathの値をバンプするようにした

などです。

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

P2414R8 Pointer lifetime-end zap proposed solutions

P2414R9 Pointer lifetime-end zap proposed solutions

Pointer lifetime-end zapと呼ばれる問題の解決策の提案。

以前の記事を参照

R8での変更は

  • P2188R1に従う変更
    • usable_ptr<T>bag_of_bits_ptr<T>にリネーム
    • make_usable_ptr()launder_bag_of_bits_ptr()にリネーム

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

  • bag_of_bits_ptr<T>launder_bag_of_bits_ptr()を別の提案に分離
    • P3790R0(下の方)に分離

などです。

この提案は既存の標準文言の調整を行う変更のみになり、次のことを提案しています

  1. アトミック操作を再定義し、将来のポインタ値を生成して格納するようにする
  2. volatileポインタに対する操作を、将来のポインタ値を生成して格納するようにする

将来のポインタ(prospective pointer)(値)とは、生存期間開始前(である可能性のある)オブジェクトへのポインタ値のことで、zap問題の主題である参照先オブジェクトの寿命が尽きた後のポインタ(無効なポインタ)とは異なり、(参照先オブジェクトの寿命が開始される前でも)ポインタ値に対する一部の操作が許可されます。

将来のポインタ値を生成する方法は、無効なポインタのポインタ値をuintptr_tにキャストしてから元に戻すことです(これによってポインタのprovenanceを破棄する)。この提案では、アトミックポインタに対する操作とvolatileポインタへのアクセスの際に無効なポインタをこのようなキャストによって将来のポインタに戻すようにする(as-ifなので実際にこのキャストを実行するわけではない)ことで、これらの操作において無効なポインタを使用できるようにしています。

この提案はEWGのレビューを通過し、C++29に向けてCWGのレビュー中です。どうやらDRとなるようです(おそらくC++11)。

P2664R11 Extend std::simd with permutation API

std::simdに、permute操作のサポートを追加する提案。

以前の記事を参照

このリビジョンでの変更は、LWGレビューを受けての文言更新のみです。

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

P2781R9 std::constexpr_wrapper

コンパイル時定数オブジェクトを生成するクラスの提案。

以前の記事を参照

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

  • cw-fixed-valueの型と、std::constexpr_wrapperメンバ変数は説明専用とする
  • いくつかの場所でconstinitconstexprに修正
  • operator->*の実装から“e.operator”を削除
  • std::cwの目的に関するセクションの段落4を削除
  • 例を修正
  • その他文言の改善・修正

などです。

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

P2876R3 Proposal to extend std::simd with more constructors and accessors

std::simdに対して、利便性向上のために標準ライブラリにあるデータ並列型等のサポートを追加する提案。

以前の記事を参照

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

  • std::bitsetへの変換演算子を削除
  • LWGフィードバックを適用
  • その他文言の修正

などです。

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

P2929R1 simd_invoke

std::simdで組み込み関数の使用を簡易にする呼び出しラッパ関数の提案。

以前の記事を参照

このリビジョンでの変更は良くわかりません(R0と全く同じに見える)。

P2996R13 Reflection for C++26

値ベースの静的リフレクションの提案。

以前の記事を参照

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

  • コア言語の文言の変更
    • 静的匿名共用体のメンバをハンドリング
    • P3687R0からのEWGの更新を適用
      • splice template argumentsを削除
      • かっこで囲まれていないスプライス式をテンプレート引数で使用するとill-formed
      • オペランドusing宣言子である^^はill-formed
  • ライブラリの文言変更
    • reflect_constant/reflect_object/reflect_functionの使用の更新

などです。

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

P3008R6 Atomic floating-point min/max

浮動小数点数型のstd::atomicにおけるfetch_max()/fetch_min()の問題を解消する提案。

以前の記事を参照

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

  • オブジェクト値の文言を変更
  • NaNに関するremarkを明確化
  • fetch_XXXの対応関係を明確化
  • value_typeの修正

などです。

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

P3044R2 sub-string_view from string

std::stringから直接string_viewを取得する関数を追加する提案。

以前の記事を参照

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

  • string_view::subviewfreestanding-deleted指定

などです。

P3045R6 Quantities and units library

物理量と単位を扱うライブラリ機能の提案。

以前の記事を参照

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

  • Dependencies on other proposals の更新
  • Constraining a variable on the stack の拡張
  • Usage examples のリンク更新
  • Scaling overflow prevention を追加
  • Concepts の更新
  • Storage tankの例を更新
  • Safe operations of vector and tensor quantities の更新
  • Superpowers of the unit one の更新
  • Symbols of scaled units の更新
  • 共通単位のテキスト出力において、“EQUIV{…}” を “[…]” で置換
  • Inconsistencies with std::chrono::duration を追加
  • Complex operations を追加
  • Integer division を拡張
  • Bikeshedding concepts を追加
  • Supported operations and their results を更新
  • Equality and equivalence を拡張
  • Obtaining common entities を Arithmetics に変更
  • Negative constants を追加
  • その他の章におけるクリーンアップ

などです。

P3060R3 Add std::views::indices(n)

0から指定した数の整数シーケンスを生成するRangeアダプタ、views::indicesの提案。

以前の記事を参照

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

  • 機能テストマクロの追加

などです。

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

P3091R4 Better lookups for map , unordered_map, and flat_map

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

以前の記事を参照

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

  • N5008にリベース
  • constexprを追加
  • flat_mapのサポートを追加
  • 機能テストマクロのtypoを修正
  • LEWGでの投票結果を追加
  • .getという名前を維持し、.lookupに変更しないようにした

などです。

P3096R10 Function Parameter Reflection in Reflection for C++26

P3096R11 Function Parameter Reflection in Reflection for C++26

P3096R12 Function Parameter Reflection in Reflection for C++26

C++26に向けた静的リフレクションに対して、関数仮引数に対するリフレクションを追加する提案。

以前の記事を参照

R10での変更は

  • P2996R12と文言を整合させた
  • LWG等からのフィードバックによる文言の更新

R11での変更は

  • CWGのフィードバックによる文言の更新

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

  • identifier_of()has_identifier()の文言を更新
  • has_default_argument()の"Constant When"を削除
  • LEWG/EWGの投票結果を追加

などです。

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

P3100R3 Implicit contract assertions

UB(及びEB)を契約違反として扱うようにする提案。

以前の記事を参照

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

などです。

P3104R4 Bit permutations

<bit>にビット置換系操作を追加する提案

以前の記事を参照

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

  • § 6.6 Why no SIMD support? でstd::simdサポートがないことを説明
  • 数式の表示の調整
  • その他文言の修正

などです。

P3111R7 Atomic Reduction Operations

P3111R8 Atomic Reduction Operations

std::atomicにアトミックリダクション操作を追加する提案。

以前の記事を参照

R7での変更は

  • レビューノートを更新
  • セクション順序を標準に合わせて変更
  • P3008によって追加された不要な文言を削除

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

  • 浮動小数点数アトミックのmin/maxに関してP3008の最新リビジョンを反映

などです。

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

P3149R11 async_scope -- Creating scopes for non-sequential concurrency

P2300のExecutorライブラリについて、並列数が実行時に決まる場合の並行処理のハンドリングを安全に行うための機能を提供する提案。

以前の記事を参照

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

  • async_scope_tokenscope_tokenにリネーム
  • nestassociateにリネーム
    • P3865R0から
  • stop-whenの定義を形式化し、spawn_futurecounting_scope::token::wrapstop-whenに基づいて再定義
  • P3557R2に従い、impls-for::<associate_t>check-typesを追加
  • join-tnoexceptを修正
  • LWGのフィードバックを適用
  • セクション1~6を更新

などです。

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

P3179R9 C++ parallel range algorithms

RangeアルゴリズムExecutionPolicyに対応させる提案。

以前の記事を参照

このリビジョンでの変更は、文言の改善のみです。

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

P3211R1 views::flat_map

views::transformviews::joinの合成に対応する操作であるRangeアダプタ、views::flat_mapの提案。

以前の記事を参照

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

  • transform_joinflat_mapにリネーム
    • 他言語や関連する領域ではこの操作はflat_mapと呼ばれることが一般的だったため
  • flat_map_viewクラスを導入することにした
  • 関連する最適化について議論を追加

などです。

このリビジョンでは、R0とは異なりviews::flat_mapを既存のviews::transformviews::joinの単純な合成によって定義しないようになっています。これは

  • base()で元の範囲を取得できない
    • transform | joinだとtransform_viewが取得されてしまう
  • transformの結果である内側の範囲をキャッシュできない
    • transform | joinだと、joinは入力範囲の内側範囲がどのように生成されているか(生成のコストなど)を認識できず、繰り返しの呼び出しにおいて都度間接参照する
    • cache_latestはこの問題を緩和するが、input_rangeになってしまう
    • 独自のview型なら内側範囲がどのように生成されているかを認識できるためその結果をキャッシュでき、キャッシュは内部的に処理されるため範囲のカテゴリに影響を与えない
  • 不必要な入力範囲のプロパティ保持のための不要なインスタンス化が発生する
    • flat_mapはその性質上bidirectional_rangeにしかならないが、transformはランダムアクセス性を可能なら維持しようとする
    • 不要なインスタンス化とコードの肥大化を招きうる

などの理由によります。合成による定義は確かに単純ですが、少なくともこのflat_mapの場合は独自のviewを提供するメリットが上回るという判断です。

P3216R1 views::slice

元の範囲の連続した一部分を切り出すRangeアダプタ、views::sliceの提案。

以前の記事を参照

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

  • SG9のフィードバックに基づいて、slice_viewを導入する

などです。

このリビジョンでは、R0とは異なりviews::sliceを既存のviews::dropviews::takeの単純な合成によって定義しないようになっています。これは

  • base()で元の範囲を取得できない
  • reserve_hint()を常に提供できない
    • views::dropは入力がapproximately_sized_rangeである場合にのみreserve_hint()を提供するため、合成だと常にreserve_hint()を提供できない
    • しかし、views::slice(start, end)end - startreserve_hint()として常に提供できるはずであり、独自viewにすることでこれが可能になる
  • パフォーマンスの問題
    • 合成だと、複数のイテレータラッパの生成と間接参照が発生するため、オーバーヘッドがある
    • 単一のslice_viewなら、より最適化しやすくなることが期待される
  • 境界処理の一元化
    • 合成だと、範囲の境界チェックや範囲外逸脱防止処理が2つのアダプタに分散して実行され、冗長なチェックが発生する可能性がある
    • 単一のslice_viewならより一貫した境界チェックを行うことができ、行われることも把握しやすくなる
  • デバッグしやすさの向上
    • 合成によるview型はネストした型が生成されているためデバッグしづらい
  • 将来の拡張性
    • 合成の場合、views::sliceの機能拡張をしようとするとdrop, takeの両方の事を考慮しなければならない
    • 単一のslice_viewならそれらとは無関係に検討できる

などの理由によります。

P3220R1 views::take_before

入力の範囲を指定した値が最初に出現する位置を終端として切り出すRangeアダプタ、views::delimitの提案。

以前の記事を参照

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

  • views::delimitviews::take_beforeにリネーム
  • views::take_before(p, '\0')などにおいて、条件付きでborrowed_rangeとなるようにした
    • スカラ型の値は番兵に保存することで、イテレータview型の状態に依存しないようにする

などです。

P3223R2 Making std::istream::ignore less surprising

istream::ignore()の第二引数に負の値を与えた場合の動作を修正する提案。

以前の記事を参照

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

  • 追加するオーバーロードからデフォルト引数を削除
  • Annex Cのエントリを追加
  • LEWGレビュー結果を追加
  • その他文章の修正

などです。

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

P3248R4 Require [u]intptr_t

(u)intptr_tを必須にする提案。

以前の記事を参照

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

  • この変更の影響を受ける標準ライブラリ内のものに対するクリーンアップを行った
  • LEWGレビュー結果を追加

などです。

P3290R3 Integrating Existing Assertions With Contracts

既存のアサーション機構に契約プログラミング機能を統合する提案。

以前の記事を参照

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

  • P2900R14採択後のWDにリベース
  • source_location::current()の使用法を明記し、実装上の選択肢について議論を追加
  • nothrow_t引数の位置を引数の最初に変更
  • 実装経験について追記
  • detection_mode::manualdetection_mode::unspecifiedにリネーム
  • <assert.h><cassert>の両方を処理するようにする

などです。

P3293R3 Splicing a base class subobject

リフレクション機能において、基底クラスのサブオブジェクトへアクセスする簡単な方法を提供する提案。

以前の記事を参照

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

  • P2996R13にリベース
  • 時間の都合により、基底クラスサブオブジェクトへのポインタを取得する機能を削除

などです。

基底クラスサブオブジェクトへのポインタを取得する機能とは、型Tの基底クラス型Bのリフレクションbaseを用いて&[:base:]の様にスプライスした時に適切にオフセットされたメンバポインタB T::*を取得できるようにする機能の事です。このような機能は現在の言語機能に対応するものが無かったため、議論に時間がかかることが予想され、それによってこの提案からは外されました。

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

P3347R3 Invalid/Prospective Pointer Operations

無効化されたポインタに対する一部の演算を明示的に許可する提案。

以前の記事を参照

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

  • 非直接ポインタアクセスの例を追加
  • フィードバックを受けて、提案する文言の修正
  • pointer trapの表現について代替案を追加

などです。

P3348R4 C++26 should refer to C23 not C17

Cへの参照をC17からC23へ更新する提案。

以前の記事を参照

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

  • reallocの仕様から“behavior is undefined unless”という不要な文言を削除
  • [c.mb.wcs]のcuchar()に関するdrafting noteを明確化
  • [ctime.syn]からasctime(), ctime()を削除し、それらの文言を[depr.format]に追加
  • [cstdint.syn]の編集について文脈を追加
  • [diff.char16]に変更を追加

などです。

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

P3383R3 mdspan.at()

std::mdspan.at()メンバ関数を追加する提案。

以前の記事を参照

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

  • 段落8と9の入れ替え
  • indicesの代わりにIを使用(インデックス引数のパック名)

などです。

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

P3394R4 Annotations for Reflection

C++任意のエンティティ(宣言)に対して静的リフレクションのためのアノテーションを付加できるようにする提案。

以前の記事を参照

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

  • 文言の改善
  • 属性とアノテーションを同じ場所(attribute-specifier)に配置する機能の削除

などです。

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

P3411R3 any_view

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

以前の記事を参照

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

  • contiguous rangeの扱いについて修正

などです。

P3433R1 Allocator Support for Operation States

operation_state型において、アロケータを認識し伝播可能にする提案。

以前の記事を参照

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

  • 文言の改善
  • 説明専用allocator-aware-moveallocator-aware-forwardに変更

などです。

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

P3439R3 Chained comparisons: Safe, correct, efficient

誤って書かれることの多い、連鎖比較を意図通りに動作するようにする提案。

以前の記事を参照

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

  • EWGの推奨に基づいて、(連鎖比較を)ill-formedではなく非推奨とする
  • フィードバックによる文言の改善

などです。

P3440R1 Add n_elements named constructor to std::simd

先頭N個のビットが立ったsimd_maskを作成するためのファクトリ関数を追加する提案。

以前の記事を参照

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

  • オリジナルstd::simd提案の現状に合わせて文言を更新

などです。

P3480R6 std::simd is a range

std::simdrangeにする提案。

以前の記事を参照

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

  • offset_が0からsize()の範囲内にとどまるという事前条件を追加
  • 内部コンストラクタを説明専用かつprivateにする
  • 不要なstd::の削除
  • data_を初期化する前にaddressofを取得する
  • operator*の不要な事前条件を削除する
  • 可能な限り多くの演算を+= -=で表現する
  • basic_simdbasic_simd_maskbegin/endにインライン実装を追加
  • begin/endに加えて、privateイテレータコンストラクタにnoexceptを付加
  • 差分と等価比較のnoexceptを追加
  • +-には値渡し引数を使用する

などです。

P3481R4 std::execution::bulk() issues

P3481R5 std::execution::bulk() issues

std::execution::bulk()の改善提案。

以前の記事を参照

R4での変更は

  • 文言の改善
  • bulk_unchunkedに実行ポリシーを追加
  • 並行スケジューラ用のbulk_unchunkedにおける余分なチェックを削除

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

  • LWGフィードバックによる文言の修正

などです。

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

P3491R3 define_static_{string,object,array}

コンパイル時に構築した文字列や配列などを静的ストレージに昇格させて実行時に持ち越せるようにするライブラリ機能の提案。

以前の記事を参照

このリビジョンは

  • P3617R0の内容をマージ

などです。

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

P3503R3 Make type-erased allocator use in promise and packaged_task consistent

std::promisestd::packaged_taskのアロケータ対応における問題を修正する提案。

以前の記事を参照

R1およびこのリビジョンでの変更は、文言の修正のみです。

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

P3540R2 #embed Parameter offset

#embedにオフセットパラメータを渡せるようにする提案。

以前の記事を参照

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

  • R1で行われた設計変更について詳細に説明を追加
  • 文言や文章のtypo修正
  • パラメータ順序に制限がないことについて説明等を追加

などです。

オフセット引数は、#embed <sdk/jump.wav> offset(2) limit(2)のように指定して

  • offset(N)はリソースの最大サイズに達するまで、Nバイト分の項目を破棄する
  • offset(0)はno-op
  • offset(N)がリソースのサイズ以上の場合、リソースサイズは0とみなされる
  • limitoffsetの後に適用される
    • offsetは元のファイルサイズに対して適用される
  • パラメータ順序によって結果は変わらない

のような動作をします。

P3552R3 Add a Coroutine Task Type

std::executionに対応したコルーチンtask型の提案。

以前の記事を参照

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

  • LWGフィードバックによる文言の修正

などです。

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

P3557R3 High-Quality Sender Diagnostics with Constexpr Exceptions

std::executionにおける、エラーメッセージ出力を改善する提案。

以前の記事を参照

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

  • 33.9.12.10 [exec.split]の文言変更を削除
  • 未規定の例外型はすべてstd::exceptionから派生することを必須とする
  • not-a-sender::get_completion_signatures()を他の全てのget_completion_signatures()メンバ関数との一貫性を持たせるために、constexprからconstevalに変更
  • 再配置されたnot-a-sender仕様に注釈をつけ、現在のWDと比較して定義がどのように変化したかを示す
  • markdownの修正

などです。

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

P3560R2 Error Handling in Reflection

静的リフレクション機能におけるエラーハンドリング方法として例外を採用すべきとする提案。

以前の記事を参照

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

  • 文言の修正
  • P2996R13にリベース

などです。

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

P3566R2 You shall not pass char* - Safety concerns working with unbounded null-terminated strings

その長さが静的に既知ではない文字列(const char*)を受け取るstd::string/std::string_viewのコンストラクタを置き換える提案。

以前の記事を参照

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

  • P3655R2への参照を追加
  • コピーオーバーロードに関する説明を追加
  • Qtについての実験を追加
  • nullptrを空文字列として扱うことを提案していることを明確化

などです。

P3570R2 optional variants in sender/receiver

並行キューのasync_pop()の最適な戻り値型を探る提案。

以前の記事を参照

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

  • 機能テストマクロを追加する代わりに、__cpp_lib_sendersの値をバンプする
  • adapterという言葉をadaptorに統一
  • as_awaitableのプロセス中に、具体的な変換プロセスを指定
  • 文言の改善

などです。

このリビジョンでもR0から変わらず、提示されていたオプションのうちOption2.bを採用しています。

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

P3601R0 Slides for P3407R1

P3407R1の紹介スライド。

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

P3407R1のモチベーションなどが簡単に紹介されています。

P3642R1 Carry-less product: std::clmul

P3642R2 Carry-less product: std::clmul

整数のキャリーレス乗算を行う関数の提案。

以前の記事を参照

R1での変更は

  • 提案する文言の誤った式を修正
  • HWサポートについてVPCLMULQDQを追加
  • §2. Introductionにおけるstd::unsigned_integralの不適切な使用を修正
  • N5008とP3161R4にリベース
  • モチベーションについて参照を追加

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

  • 非wide版についてstd::simdサポートを追加
  • コードスタイルを標準に合わせる
  • std::clmul_wide()の詳細な設計説明を§5.2. Widening operationに追加
  • 設計をP3161R4から分離
  • 文言の改善
  • P3691R1のstd::simd命名変更に合わせてリベース

などです。

P3647R0 Slides for P3642R1

P3642R1の紹介スライド。

P3642R1(R2)に関してはすぐ上をご覧ください。

キャリーレス乗算機能の必要性などが簡単に説明されています。

P3655R2 zstring_view

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

以前の記事を参照

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

  • NVIDIAでの実装経験の追加
  • 固定長配列から構築するコンストラクタを追加
  • 事前条件をさらに追加
  • イテレータ/範囲コンストラクタを追加しない理由の説明を追加
  • フォーマッタ追加
  • 文字列中にnull文字が入っている場合のセキュリティリスクについて参照を追加

などです。

P3663R2 Future-proof submdspan-mapping

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

以前の記事を参照

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

  • 実装リンクを追加
  • canonical-iceの実装経験に基づく変更
    • 戻り値をstd::constant_wrapperではなくstd::cwを使用して指定する
    • std::cwの前に、index-castIndexTypeにキャストする
  • check-static-boundsの実装経験に基づく変更
    • 文言において、skではなくSk{}のみを使用するように修正
      • これにより、すべての式が有効な定数式になる
    • check-static-boundsは、すべてのスライスをパラメータパックで受け取るのではなく、一つのスライス型のみをテンプレート引数で受け取るように変更
    • 文言において、型がintegral-constant-likeの場合のみデフォルト構築可能であることを仮定するようにした
  • submdspanの“Effects: Equivalent to”の指定コードを修正
  • LWGのフィードバックによる文言の修正
  • canonical-iceが値Valueに対してcw<IndexType(Value)>IndexType(Value)のどちらかを返すようにする
  • 実装に関する議論を拡張し、予備的なベンチマークを追加

などです。

P3668R2 Defaulting Postfix Increment and Decrement Operations

後置インクリメント/デクリメント演算子default定義できるようにする提案。

以前の記事を参照

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

  • ターゲットをCWGに変更
  • 投票結果を追加
  • 初期化構文には()を使用するように変更
  • P2952がC++26に入らなかったことを受けてステータスを更新
  • ライブラリ文言の変更に関する提案の参照を追加

などです。

P3669R2 Non-Blocking Support for std::execution

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

以前の記事を参照

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

  • 根拠を追記
  • concurrent_schedulertry_schedulerに変更
  • 使用例を追加

などです。

P3687R1 Final Adjustments to C++26 Reflection

P2996のリフレクション機能に対する修正の提案。

以前の記事を参照

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

  • エンティティプロキシのリフレクションをTU-local値とするための文言を追加
  • [namespace.udecl]の文言を修正
  • typo修正

などです。

この提案はすでにP2996にマージされています。

P3688R1 ASCII character utilities

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

以前の記事を参照

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

  • is_ascii_digit()base引数の事前条件が堅牢化対象ではない理由を説明
  • [tab:headers.cpp]への追記漏れを修正

などです。

P3690R1 Consistency fix: Make simd reductions SIMD-generic

std::simd専用となっている一部の操作をジェネリックにする提案。

以前の記事を参照

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

  • 文言の修正
  • 例の修正

などです。

P3691R1 Reconsider naming of the namespace for "std::simd"

std::simd名前空間構造を再考する提案。

以前の記事を参照

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

  • SIMD型名をstd::simd::vecに変更する
  • 新しい命名に合わせた編集者向けの指示を追加

などです。

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

P3692R1 How to Avoid OOTA Without Really Trying

P3064R2を要約した文書、および現在の実装ではOOTAが起こらないことを明確化する提案。

以前の記事を参照

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

  • 連絡先情報の追加
  • 構文上の依存関係のサブセットであり、意味上の依存関係のスーパーセットである“preserved dependencies”を定義
  • その他文章の修正

などです。

P3697R1 Minor additions to C++26 standard library hardening

堅牢化された事前条件をさらに追加する提案。

以前の記事を参照

このリビジョンでの変更は、文言の修正のみです。

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

P3699R1 Rename conqueue_errc

P0260のconqueue_errcをリネームする提案。

以前の記事を参照

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

  • 名前のオプションをさらに追加した

などです。

P3705R1 A Sentinel for Null-Terminated Strings

P3705R2 A Sentinel for Null-Terminated Strings

文字列範囲内の終端文字(\0)を見つけるとそこで終端を示す番兵型の提案。

以前の記事を参照

R1での変更は

  • モチベーションセクションにおけるoff-by-oneを修正
  • argvとenvironを使用した例を追加
  • unchecked_take_beforeの設計の代替案を追加

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

  • unchecked_take_beforeと値渡し設計の代替案を削除
  • SG9レビューの議事録と投票結果を追加
  • null_termstd::viewsに移動
  • 機能テストマクロを追加
  • 命名に関する議論を追加
  • initializer_listの相互作用を避けるため、iter_value_t<I>{}ではなくiter_value_t<I>()を使用する

などです。

P3709R1 Reconsider parallel ranges::rotate_copy and ranges::reverse_copy

P3709R2 Reconsider parallel ranges::rotate_copy and ranges::reverse_copy

並行アルゴリズム版のranges::rotate_copyranges::reverse_copyの戻り値についての再設計の提案。

以前の記事を参照

R1での変更は

  • 編集上の修正
  • 提案する文言を追加

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

  • reverse_copy, rotate_copyの戻り値型のメンバ変数名に関する考慮事項を追加

などです。

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

P3711R1 Safer StringViewLike Functions for Replacing char* strings

char*文字列の使用を排除するためのユーティリティ関数の提案。

以前の記事を参照

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

  • starts_with, join, is_empty_or_nullの代替実装例を追加
  • SG23の投票結果を追加

などです。

この提案はLEWGのレビューにおいてより時間をかけて検討することを否決されており、リジェクトされています。

P3713R0 2025-05 Library Evolution Poll Outcomes

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

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

P3718R0 Fixing Lazy Sender Algorithm Customization, Again

std::executionに対するP2999R3の問題点を修正する提案。

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

ここでは主に、continues_onschedule_fromの扱うドメインsenderアルゴリズムがカスタマイズの探索を行う範囲・環境、あるいはsenderの完了地点の宣言そのもの)に関する問題の修正が提案されています。

  1. get-domain-late()におけるcontinues_onschedule_fromの取り違え
    • その説明の中でcontinues_onschedule_fromの役割が逆になって扱われている
  2. 遅延カスタマイズの場合のドメインの問い合わせ先の間違い
    • あるsenderconnectされた時、ドメインの問い合わせ先をそのsenderに対して行っていた
    • あるsender自身のドメインと先行senderドメインが一致する場合は問題ないものの一致しない場合に問題になる
      • continues_onは自身の完了地点と先行senderの完了地点が異なる(=ドメインが異なる)可能性があるため、問題が起こりうる
  3. get-domain-late()が優先使用するドメインが誤っている
    • get-domain-late()は渡されたsenderドメインを、receiverドメインよりも優先する
    • senderは自身がどこで完了するかを知ることができ、receiverは処理(後続sender)がどこで開始されるかを知っている
    • senderが開始される場所に基づいてディスパッチを行う場合、senderではなくreceiverに問い合わせるべき

continues_onschedule_fromはコンテキストの遷移を扱うsenderアルゴリズムであるため、ドメインの取得に関して他の一般のsenderアルゴリズムと異なる特別な対応が必要になります。

ここでは次の解決策を提案しています

  1. デフォルト実装を持たない非転送のクエリを行う、get_domain_overrideクエリを追加
  2. get_schedulerクエリに意味を持たせるため、操作の作成に使用したreceiverの環境に関連付けられたschedulerの実行エージェント上で操作を開始することを要求する
  3. SCHED-ATTRSSCHED-ENVの定義を調整し、get_domainクエリの転送を回避する
  4. 説明専用のcompletion-domainヘルパの定義を簡素化し、設定可能なデフォルトを不要にする
  5. get_domain_override(get_env(schedule_from(sch, sndr)))schドメインを返すことを規定
  6. get_domain_override(get_env(continues_on(sndr, sch)))sndrドメインを返すことを規定
  7. get_domain_override(get_env(starts_on(sch, sndr)))schドメインを返すことを規定
  8. get-domain-late(sndr, env, def)は次のいずれかと等価である必要がある
    • get_domain_override(get_env(sndr)): この式がwell-formedの場合
    • get_domain(env): そうではなく、この式がwell-formedの場合
    • get_domain(get_scheduler(env)): そうではなく、この式がwell-formedの場合
    • それ以外の場合: def
  9. sync_waitおよびsync_wait_with_variantはカスタマイズを探索する際にget-domain-late(sndr, sync-wait-env{}, get-domain-early(sndr))を使用することを指定

これらの事は、NVIDIAのCCCLというライブラリ向けのGPUスケジューラを再実装している際に発見されたもののようで、CCCLライブラリおよびstdexecにおいてこれらの設計は実装済みであるようです。

P3719R0 std::is_vector_bool_reference

std::vector<bool>を検出する型特性を追加する提案。

例えば次のようにある関数のオーバーロードを特定の型ごとに用意しなければならない場合

// シリアライズライブラリなど、型ごとのread()関数オーバーロードが大量にある
void read(bool& b);
void read(int& b);
void read(long& b);
void read(long long& b);
// ... many more

このとき、std::vector<bool>(の要素型)にも対応する必要があることに気づきます。移植性のある方法として次のように書くとおおむねうまくいきます

void read(std::vector<bool>::reference b);

しかし、アロケータがカスタマイズされたstd::vector<bool, MyAllocator>のようなものにこれでは対応できないことがあります。なぜなら、std::vector<bool, MyAllocator>::referencestd::vector<bool>::referenceが実装によっては異なる型である場合もそうでない場合もあるためです。実際、libstdc++では同じ型であり、MSVC STLでは異なる型になります。

このような場合にアロケータをテンプレート化するだけではこれを解決できません。

template <typename Alloc>
void read(std::vector<bool, Alloc>::reference b);

このような関数テンプレートにおいては、Allocは推論できないコンテキストにあるため推論に失敗します。

この提案では、std::vector<bool>::referenceをアロケータ型によらずに識別するために、std::is_vector_bool_reference<T>型特性を追加することを提案しています。このような型特性は標準においても説明専用のis-vector-bool-referenceとして存在しているため、これをユーザーが使用できるようにするだけです。

std::is_vector_bool_reference<T>は、Tが任意のアロケータ型Allocに対するstd::vector<bool, Alloc>::reference型を示しており、かつstd::vector<bool, Alloc>がプログラム定義の特殊化(ユーザー定義特殊化)ではない場合にtrueを返すものです。

これを用いると、先ほどのオーバーロードの問題はシンプルに書くことができるようになります

template <typename BitReference>
    requires std::is_vector_bool_reference_v<BitReference>
void read(BitReference b);

P3721R0 Slides for P3639R0

P3639R0の紹介スライド。

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

P3639R0の内容が簡単に解説されています。

P3722R0 Slides for P3568R1

P3568R1の紹介スライド。

主に構文候補の選択過程(WG14 N3355を採用)と、その構文の問題点の解消(重複ラベルの許可)についてを説明しています。

P3724R0 Integer division

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

C++の現在の/は切り捨て除算を行います。しかし、丸めモードは切り捨てだけではなく他のものも有用と思われます。また、標準の丸め方法では割られる数が負の場合などに剰余が負の値になる場合もあり、この動作も望ましくありません。

このような問題を回避するのは非常に単純に思えますが、実際には/に存在する未定義動作を回避しながら丸め方法を変更するのは驚くほど難しく、少し調べて出てくるような方法はほとんどが間違っているか、問題を抱えています(提案文書にはその例がいくつか掲載されています)。さらに、剰余に関しても整数オーバーフローを回避する必要性によってゼロ方向以外の丸めモードで正しく計算するのは困難です。

この提案は丸め方法を指定して商と剰余を求めるライブラリ関数を追加することで、現在の/および%に代わって所望の丸め方法で商と剰余を求める方法を提供しようとするものです。

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

const int bucket_size = 1000;
int elements = 100;

int buckets_required = elements / bucket_size; // WRONG, zero
int buckets_required = std::div_to_inf(elements, bucket_size); // OK, one bucket

提案している関数は、その名前が商を計算するものはdiv_、剰余を計算するものはdiv_rem_で始まり、それに続いて丸め方法が指定される形の命名になっています。

namespace std {
  template<class T>
    constexpr T div_to_zero(T x, T y);
  template<class T>
    constexpr T div_away_zero(T x, T y);
  template<class T>
    constexpr T div_to_inf(T x, T y);
  template<class T>
    constexpr T div_to_neg_inf(T x, T y);
  template<class T>
    constexpr T div_to_odd(T x, T y);
  template<class T>
    constexpr T div_to_even(T x, T y);
  template<class T>
    constexpr T div_ties_to_zero(T x, T y);
  template<class T>
    constexpr T div_ties_away_zero(T x, T y);
  template<class T>
    constexpr T div_ties_to_inf(T x, T y);
  template<class T>
    constexpr T div_ties_to_neg_inf(T x, T y);
  template<class T>
    constexpr T div_ties_to_odd(T x, T y);
  template<class T>
    constexpr T div_ties_to_even(T x, T y);

  template<class T>
    constexpr div_result<T> div_rem_to_zero(T x, T y);
  template<class T>
    constexpr div_result<T> div_rem_away_zero(T x, T y);
  template<class T>
    constexpr div_result<T> div_rem_to_inf(T x, T y);
  template<class T>
    constexpr div_result<T> div_rem_to_neg_inf(T x, T y);
  template<class T>
    constexpr div_result<T> div_rem_to_odd(T x, T y);
  template<class T>
    constexpr div_result<T> div_rem_to_even(T x, T y);
  template<class T>
    constexpr div_result<T> div_rem_ties_to_zero(T x, T y);
  template<class T>
    constexpr div_result<T> div_rem_ties_away_zero(T x, T y);
  template<class T>
    constexpr div_result<T> div_rem_ties_to_inf(T x, T y);
  template<class T>
    constexpr div_result<T> div_rem_ties_to_neg_inf(T x, T y);
  template<class T>
    constexpr div_result<T> div_rem_ties_to_odd(T x, T y);
  template<class T>
    constexpr div_result<T> div_rem_ties_to_even(T x, T y);
}

div_rem_関数の戻り値型のdiv_result<T>は商と剰余の値を同時に保持している集成体型です。

namespace std {
  template<class T>
  struct div_result {
    T quotient;
    T remainder;

    friend auto operator<=>(const div_result&, const div_result&) = default;
  };
}

非常にたくさんの関数がありますが、div_div_rem_で変種は共通しており、それはすべて丸めモードの違いです。

  • to_zero
    • ゼロ方向丸め
    • /と同じ動作
  • away_zero
    • ゼロと逆方向への丸め
  • to_inf
    • 正の無限大方向への丸め(切り上げ
  • to_neg_inf
    • 負の無限大方向への丸め(切り捨て
  • to_odd
    • 奇数丸め
  • to_even
    • 偶数丸め
  • ties_to_zero
    • 最近接丸め(最も近い整数への丸め
    • 最も近い整数が2つある場合はゼロ方向(絶対値の小さいほう)へ丸める
  • ties_away_zero
    • 最近接丸め
    • 最も近い整数が2つある場合は絶対値の大きいほうへ丸める
  • ties_to_inf
    • 最近接丸め
    • 最も近い整数が2つある場合は正の無限大方向(大きいほう)へ丸める
  • ties_to_neg_inf
    • 最近接丸め
    • 最も近い整数が2つある場合は負の無限大方向(小さいほう)へ丸める
  • ties_to_odd
    • 最近接丸め
    • 最も近い整数が2つある場合は奇数のほうへ丸める
  • ties_to_even
    • 最近接丸め
    • 最も近い整数が2つある場合は偶数のほうへ丸める

これに加えて、除算の剰余を常に負の無限大方向へ丸めるstd::mod()も用意しています。

namespace std{
  template<class T>
    constexpr T mod(T x, T y);
}

これは、剰余の符号が割る数と同じになり、モジュラー演算を意図するところで有効に使用できるため専用の関数として用意されています。

P3725R0 Filter View Extensions for Input Ranges

P3725R1 Filter View Extensions for Input Ranges

views::filterの問題点を軽減しようとする提案。

P3329R0で指摘されているように、views::filterには落とし穴がいくつかあります。

この提案でもいくつか新しいサンプルが提供されていますが、この提案はそれらの問題を緩和しようとするものです。

ここでの変更は

  • filter_viewbegin()/end()constオーバーロードを追加
    • 使用可能なのは、入力範囲(const化したもの)がinput_rangeでしかない場合
  • views::filterの入力範囲がinput_rangeでしかない場合は、フィルタ結果要素の変更を許可する
    • 現在は未定義動作
  • views::input_filterアダプタの追加
    • 入力範囲にviews::to_inputを適用して使用するviews::filter
    • 他の変更と合わせて、input_rangeにしかならないことで要素の変更(ムーブ)を許可するなど、既知の問題を緩和する

などです。

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

auto large = [](const auto& s) { return s.size() > 5; }; 
auto sub = coll1 | std::views::filter(large) 
                 | std::views::reverse 
                 | std::views::as_rvalue 
                 | std::ranges::to<std::vector>(); 

このコードは、as_rvalueによってフィルタ後要素をムーブしていることによって未定義動作となっています。実装が同じフィルタ後イテレータを2回以上間接参照したりすると問題が起こります。

auto large = [](const auto& s) { return s.size() > 5; }; 
auto sub = coll1 | std::views::input_filter(large) 
                 | std::views::reverse 
                 | std::views::as_rvalue 
                 | std::ranges::to<std::vector>();  // コンパイルエラー

views::filterを使用したままではこの提案でもこの問題は緩和されませんが、views::input_filterを使用する場合コンパイルエラーとして問題が報告されるようになります。

void constIterate(const auto& coll);  // 引数をconstで受ける

std::vector<std::string> coll3{"Amsterdam", "Berlin", "Cologne", "LA"};

auto large = [](const auto& s) { return s.size() > 5; };
constIterate(coll3 | std::views::filter(large));  // コンパイルエラー

現在のviews::filterはconst-iterableではないためエラーになりますが、views::input_filterを使用することで使用できるようになります。

SG9のレビューにおいては大きな反対意見はなかったようで、LEWGに転送されることが投票で可決されています。

P3726R0 Adjustments to Union Lifetime Rules

トリビアルな共用体を定数式で使用可能にするために制限を緩和する提案。

P3074R7では、共用体を使用した次のような実装によるinplace_vectorを定数式で使用可能にするための共用体のルールの調整が行われました。

template <typename T, size_t N>
struct FixedVector {
  union { T storage[N]; };
  size_t size = 0;

  constexpr FixedVector() = default;

  constexpr ~FixedVector() {
    std::destroy(storage, storage + size);
  }

  constexpr auto push_back(T const& v) -> void {
    ::new (storage + size) T(v);
    ++size;
  }
};

constexpr auto silly_test() -> size_t {
  FixedVector<std::string, 3> v;
  v.push_back("some sufficiently longer string");
  return v.size;
}

static_assert(silly_test() == 1);

ここでのunion { T storage[N]; };の役割は定数式で使用可能な遅延初期化用ストレージです。以前はこれが定数式で使用可能ではありませんでしたが、P3074R7にて定数式で使用可能になるようにいくつかの調整が加えられ、C++26で利用可能になっています。

P3074R7の変更はおおむね次のものです

  • 共用体にトリビアルなデフォルトコンストラクタとトリビアルなデストラクタを暗黙的に定義させる
  • 共用体の最初のメンバがimplicit-lifetime typeである場合、そのメンバの生存期間を暗黙的に開始する

しかし、その後寄せられたフィードバックによってこれだけでは目的を達成できないことが分かりました。

問題は、次のようなコードがP3074R7によって振る舞いが変わることです

union U { int a, b; };

template<U u>
class X {};

constexpr U make() { U u; return u; }

void f(X<make()>) {}

P3074R7以前は、X<make()>のNTTPはアクティブメンバを持たない共用体オブジェクトだったのでNTTPとして有効でした。しかし、P3074R7の変更によってこのuは最初のメンバの生存期間暗黙的に開始されることによってアクティブメンバを持つため、NTTPとして使用できずコンパイルエラーとなります。

また、仮にコンパイルが通ったとすると、アクティブメンバを持つことからマングル名を変更する必要があるため、ABI破損を引き起こす可能性があります。

アクティブメンバを持つようになるとNTTPとして適格でなくなるのは少し複雑ですが、まず[expr.const]にある定数式の制限のうち、prvalue式の結果オブジェクトの構成要素の値(constituent value)についての制限があり、そこではスカラ型の構成要素の値は不定値もしくはerroneous valueを持っていてはならない、という規定があります。

そして、この構成要素の値の定義は次のようになっています

The constituent values of an object o are

  • if o has scalar type, the value of o;
  • otherwise, the constituent values of any direct subobjects of o other than inactive union members.

問題は2つ目の条件で、共用体型を含むクラス型の場合、構成要素の値とはそのサブオブジェクトの構成要素の値の集合の事を言っており、そこには共用体の非アクティブメンバは含まれません。

共用体先頭のimplicit-lifetime typeメンバの生存期間が開始されるとそのメンバがアクティブメンバになるものの、その値は初期化されていないため不定値を持つことになります。

これによって、スカラ型メンバのみで構成されている共用体がNTTP(というか定数式内prvalue)として使用される際の振る舞いが変化しています。

このため、P3074R7の目的はまた達成されていません

constexpr std::inplace_vector<int, 4> v = {1, 2}; // ng

このコードは先ほどのFixedVectorの実装と同じ実装になる場合、union { int storage[4]; }が使用されるものの、最初の2つの要素だけが初期化されて残りの要素は初期化されないことで不定値を持ち、コンパイルエラーになります。

ここでの問題は次の2点です

  1. 共用体が定数式で意図しないタイミングでアクティブメンバを持ってしまうこと
  2. 構成要素の値の制限

この提案はこれら2つの問題をそれぞれ解決しようとするものです。

1つ目の問題についてはP3074R7の変更をrevertし、トリビアルな共用体のimplicit-lifetime typeメンバの生存期間が自動開始されるポイントをplacement newまで遅延します。

template <typename T, size_t N>
struct FixedVector {
  union { T storage[N]; };  // P3074R7はここで暗黙的にstorageの生存期間が開始されていた
  size_t size = 0;

  constexpr FixedVector() = default;

  constexpr ~FixedVector() {
    std::destroy(storage, storage + size);
  }

  constexpr auto push_back(T const& v) -> void {
    ::new (storage + size) T(v);  // この提案では、このアクセス時に暗黙的にstorageの生存期間を開始する
    ++size;
  }
};

constexpr auto silly_test() -> size_t {
 FixedVector<std::string, 3> v;
 v.push_back("some sufficiently longer string");
 return v.size;
}

static_assert(silly_test() == 1);

元々の問題の一部は、placement newの際にstorageメンバにアクセスしたときにその生存期間が開始されていない事だったので、placement newアクセス時にそれが暗黙的に行われるようになることで元の問題を解決できます。そして、union U { int a, b; };の様な共用体は再びアクティブメンバを持たなくなります。

2つ目の問題については、構成要素の値の定義を変更して、共用体内配列の各要素の初期化状態について歯抜けを許容するようにします。これにより、共用体内配列のどこかの要素が初期化されていなくても、それは非アクティブメンバと同様に構成要素の値としてカウントされ無くなるため、配列そのものの生存期間が開始されている場合にも共用体の構成要素の値内に不定値が含まれなくなります。

これらの修正(特に2つ目)によって、先ほどのinplace_vectorの例は定数式で動作するようになります。

constexpr std::inplace_vector<int, 4> v = {1, 2}; // ok、この提案後

この提案はこの記事執筆時点(C++26の機能凍結後)でもまだ採択前ですが、トリビアルな共用体について多数のNBコメントが寄せられており、その解決のために遅れてC++26に採択される予定です。

P3727R0 Update Annex E based on Unicode 15.1 UAX #31

C++標準文書のAnnex E(UAX31への参照)の記述をUnicode 15.1に合わせて更新する提案。

背景等はP3717R0を参照

この提案では、P3717とは異なり、Unicode 16ではなくUnicode 15.1でのUAX31の形式に合わせて更新しようとしています。主な変更はP3717同様に「UAX31-R1a Restricted Format Characters」に関する記述を削除することで、こちらの提案はCWG Issue 2843の解決のみを(Unicode 15.1で)行うことを意図したものの様です。

SG16などでのレビューの結果、結局CWG Issue 2843の変更を採用することになったようです。

P3729R0 Aligning span and string_view

std::spanstd::string_viewのインターフェース共通化を図る提案。

std::spanstd::string_viewはそれぞれ、配列/文字列のビューとなるクラスです。とはいえ、std::string_viewが文字列に特化しているだけで、二つのクラスの実装はほぼ同じになり、その扱いや性質もかなり共通しています。

しかし、std::spanstd::string_viewは標準コンテナのインターフェースに準拠したインターフェースについては共通しているものの、それ以外の部分はそれほど共通のインターフェースにはなっていません。

この提案は、std::spanstd::string_viewからそれぞれにだけしかないインターフェースについて、特にサブセット化APIの共通化を行おうとするものです。

提案するインターフェースの追加は次のようになります

template<typename charT, typename traits = char_traits<charT>> 
struct basic_string_view { 
  ...

  // runtime subsetting: 
  constexpr basic_string_view substr(size_type pos = 0, size_type n = npos) const; 
  constexpr basic_string_view subview(size_type pos = 0, size_type n = npos) const; 
  constexpr basic_string_view first(size_type count) const; // この提案
  constexpr basic_string_view last(size_type count) const;  // この提案
  
  // in place shrinking: 
  constexpr void remove_prefix(size_type n); 
  constexpr void remove_suffix(size_type n); 

  ...
};

template<typename T, size_type E = dynamic_extent> 
struct span { 
  ...

  // compile-time subsetting: 
  template<size_t Offset, size_t Count = dynamic_extent> 
  constexpr span<T, /*see below*/> subspan() const; 
  template<size_t Count> 
  constexpr span<T, Count> first() const; 
  template<size_t Count> 
  constexpr span<T, Count> last() const; 

  // runtime subsetting: 
  constexpr span subspan(size_type pos = 0, size_type n = dynamic_extent) const; 
  constexpr span first(size_type count) const; 
  constexpr span last(size_type count) const; 

  // in place shrinking: 
  constexpr void remove_prefix(size_type n) requires(E == dynamic_extent); // この提案
  constexpr void remove_suffix(size_type n) requires(E == dynamic_extent); // この提案

  ...
};

すなわち、std::span -> std::string_viewには

  • .first(n) : 先頭n文字のstring_viewを返す
  • .last(n) : 後ろn文字のstring_viewを返す

が移植され、std::string_view -> std::spanには

  • .remove_prefix(n) : 先頭n要素のspanを返す
  • .remove_suffix(n) : 後ろn要素のspanを返す

が移植されます。

さらに、std::string_viewに関する変更はstd::stringに対しても適用されます

template<typename charT, typename traits = char_traits<charT>, typename Allocator = allocator<charT>> 
struct basic_string { 
  ...

  //runtime subsetting: 
  constexpr basic_string substr(size_type pos = 0, size_type n = npos) const; 
  constexpr basic_string_view<charT, traits> subview(size_type pos = 0, size_type n = npos) const; 
  constexpr basic_string_view<charT, traits> first(size_type count) const; // この提案
  constexpr basic_string_view<charT, traits> last(size_type count) const;  // この提案
  
  ...
};

std::string_view vstd::span<T> pstd::string sがあるとして

現在 この提案
// removing leading elements 
v.remove_prefix(5); 
p = p.subspan(5); 
s.erase(s.begin(), s.begin() + 5); 

// removing trailing elements 
v.remove_suffix(3); 
p = p.subspan(0, p.size() - 3); 
s.erase(s.end() - 3, s.end());
// removing leading elements 
v.remove_prefix(5); 
p.remove_prefix(5); 
//s.remove_prefix(5); 提案しない

// removing trailing elements 
v.remove_suffix(3); 
p.remove_suffix(3); 
//s.remove_suffix(3); 提案しない
現在 この提案
// getting leading elements 
auto l4v = v.subview(0, 4);
auto l4p = p.first(4);
auto l4s = s.subview(0, 4);

// getting trailing elements 
auto t2v = v.subview(v.size() - 2);
auto t2p = p.last(2);
auto t2s = s.subview(s.size() - 2);
// getting leading elements 
auto l4v = v.first(4);
auto l4p = p.first(4);
auto l4s = s.first(4);

// getting trailing elements 
auto t2v = v.last(2);
auto t2p = p.last(2);
auto t2s = s.last(2);

P3730R0 Slides for P3104R3

P3104R3の紹介スライド。

P3104R3で提案されている4つのビット操作関数の簡単な説明が行われています。

P3731R0 #embed Preprocessor Parameter Order

#embedディレクティブの引数指定について順番を指定するようにする提案。

#embedディレクティブは読み込むバイナリリソースについて、読み込みの仕方などについてを指定する4つのオプションがあります。そのうちlimitオプションは読み込むサイズを制限するものです。

constexpr const unsigned char sound_signature[] = {
// a hypothetical resource
#embed <sdk/jump.wav> limit(2+2)
};

また、P3540で提案中のオプションとしてoffsetがあり、これは読み取るバイナリリソースの開始位置を指定するものです。

constexpr const unsigned char truncated_sound_signature[] = {
// a hypothetical resource
#embed <sdk/jump.wav> offset(2) limit(2)
};

このオプションは排他的ではなく、同時に指定して効果を重複させることができます。その際、オプションの指定順には意味がなく、どのような順序で指定しても同じ動作をします。

しかし、(おそらくP3540の議論中に)このオプションの指定順序、特にlimitoffsetの順序が混乱をもたらす可能性があるとの指摘があったようです。その際寄せられた意見の中には、limitの後にoffsetを指定された場合にコンパイルエラーを出すべき、というものもあったようです。

この提案はそれを受けて、オプションの順番を規定するための文言を提供しようとするものです。ここでは、次の二種類の文言が用意されています

  1. オプションの順番を規定し、違反をコンパイルエラーとする文言
  2. オプションの順番を規定し、違反を警告とすることを推奨する文言

提案ではこの選択を委員会の決定に委ねています。

いずれの文言でも、ここで提案している順序付けは次のようなものです

  • offsetlimitの前
  • limitif_empty, prefix, suffixの前
  • limitがない場合でも、offsetif_empty, prefix, suffixの前

#embedはC23で先んじて採択されているため、clang/gccの新しいものであれば実装済みです。さらにそこでは、実装定義のオプションとしてoffsetオプション(gnu::offset/clang::offset)が実装されていますが、現在の#embedが意図するようにオプションの指定順によらず正しく動作しています。

P3732R0 Numeric Range Algorithms

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

C++20でアルゴリズムのRange対応がなされ、C++26では並行アルゴリズムのRange対応がなされています。しかし、そのどちらにおいても意図的に数値アルゴリズムは除外されています。これは数値アルゴリズムは通常のアルゴリズムとは少し異なる要件を持っていることから、まとめて扱えない(あるいは扱うべきではない)と判断されているためだと思われます。

C++26で通常のアルゴリズムが並行アルゴリズムまで含めて(ほぼすべて)Range対応したことで、その設計を踏襲する形で数値アルゴリズムのRange対応(並行版も含めて)を行おうとするのがこの提案のようです。

ただし、全ての数値アルゴリズムに対してRange版を追加することは提案しておらず、ここでは次のものだけを提案しています

  • std::reduce
  • std::transform_reduce
    • 単項/二項両方
  • std::inclusive_scan
  • std::exclusive_scan
  • std::transform_inclusive_scan
  • std::transform_exclusive_scan

これ以外のものはRangeアダプタ等既存のもので代替できるか、設計について特別な議論が必要となるためここでは除外しています。

そして、提案されているこれらのRange版アルゴリズムは、<algorithm>にあるものとは異なり射影をサポートしません。これは、reduce, inclusive_scan, exclusive_scanで射影を指定することは、それらに対応する(射影なし)transform版を使用することと同等になるためです。

struct foo {};

std::vector<std::tuple<int, foo, std::string>> v1 = 
{
  {5, {}, "five"}, {7, {}, "seven"}, {11, {}, "eleven"}
};

constexpr int init = 3;

// reduce with projection get_element<0>
auto result_proj =
  std::ranges::reduce(v1, init, std::plus{}, get_element<0>{});
assert(result_proj == 26);

// transform_reduce with unary transform get_element<0>
auto result_xform =
  std::ranges::transform_reduce(v1, init, std::plus{}, get_element<0>{});
assert(result_xform == 26);

// reduce with transform_view (no projection)
auto result_xv = std::ranges::reduce(
  std::views::transform(v1, get_element<0>{}), init, std::plus{});
assert(result_xv == 26);

※ここでのget_elementはP2769R3で提案されているもので、まだ正式機能ではありません。tuple-likeあるいは構造体から指定したインデックス番目の要素を抽出するものです。

Rangeアルゴリズムにおける射影の提供する一番のメリットは、要素の引き当てと変換という2つの操作を分離して指定することで可読性を向上させ、なおかつ最適化機会を提供することにあります。数値アルゴリズムの場合、そのインターフェースのために射影を用意せずとも同等の事を達成できます。

また、既存のfoldアルゴリズムranges::reduceに最も近い動作をする)はすべて射影を取らないこともこの根拠としています。

transform_reduceに射影を追加しないのは、transform_reduceのための演算の指定と射影の指定の順序が曖昧になるためです。

struct bar {
  std::string s;
  int i;
};

std::vector<std::tuple<int, std::string, bar>> v = 
{
  { 5,   "five", {"x", 13}},
  { 7,  "seven", {"y", 17}},
  {11, "eleven", {"z", 19}}
};

constexpr int init = 3;

// 射影されてから(get_element<2>で`bar`を取り出してから)、単項演算(get_element<1>)が適用される
// first get bar, then get bar::i
auto result_proj = std::ranges::transform_reduce(
  v, init, std::plus{}, get_element<1>{}, get_element<2>{});  // 適用の順序が逆に見える
assert(result_proj == 52);

// first get bar, then get bar::i
auto getter = [] (auto t) {
  return get_element<1>{}(get_element<2>{}(t)); // imagine that get_element works for structs
};
auto result_no_proj = std::ranges::transform_reduce(
  v, init, std::plus{}, getter);
assert(result_no_proj == 52);

二項のtransform_reduceviews::zip_transformを用いてranges::reduceによって同等の事を達成することができますが、可読性を損なうことやzip_transform_viewという別のレイヤが介在することによる最適化機会の損失などの理由により二項のtransform_reduceもRange対応させることを提案しています。

この時、二項のranges::transform_reduceだけは射影があったほうがパフォーマンス上のメリットがある場合があります(要素の一部の抽出をしながらtransform_reduceする際に、views::transformが必要になる)が、インターフェースの一貫性と可読性を優先させ、これに対しても射影サポートは追加していません。

これら一連の議論では(transform_)reduceで代表して説明しましたが、inclusive_scan, exclusive_scanでも同様の事が言えます。

さらに、利便性のためのranges::reduceのラッパアルゴリズムを提供することも提案しています。

  • ranges::sum(rng): ranges::reduce(rng, range_value_t<R>(), plus{})
    • 要素型をデフォルト構築したものを初期値として、rngの総和を求める
    • 演算の順序が不定std::accumulateのデフォルト動作に対応
  • ranges::product(rng): ranges::reduce(rng, range_value_t<R>(1), multiplies{})
    • 要素型をデフォルト構築したものを初期値として、rngの総乗を求める
  • ranges::dot(x, y): ranges::transform_reduce(x, y, T(), plus{}, multiplies{})
    • T = decltype(declval<range_value_t<X>>() * declval<range_value_t<Y>>())
    • 2つの範囲のドット積を求める

要素型の決定などの細部はもう少し複雑になります。これらはranges::reduceにデフォルト引数を一切持たせないようにすることを選択したことによって生まれるラッパです。

ラッパも含めたすべてのアルゴリズムは対応する並行アルゴリズムもRange版が用意されます。

これらのアルゴリズムの入力と出力の型は次のように決定されています

特に、この提案は非並行アルゴリズムで範囲出力にイテレータではなくoutput_rangeを取る初めてのものとなります(inclusive_scan, exclusive_scan)。

この提案の設計は多くの部分で既存の類似物の設計を踏襲しています。例えば

  • ranges::fold_left,ranges::fold_rightと同じ制約によって二項演算を制約し、戻り値型を導出する
  • C++17とlinalgを模倣して、GENERALIZED_NONCOMMUTATIVE_SUMGENERALIZED_SUMを用いてreduce*_scanの動作を指定する
  • それ以外のところでは、P3179R8(並行Rangeアルゴリズム提案)のアプローチに従う
  • P2248R8(Rangeアルゴリズムにおいて値の指定に{}初期化を使用できるようにする)の内容を適用

などです。

P3733R0 More named universal character escapes

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

C++23では文字/文字列リテラルにおけるエスケープ文字の一種として\N{NO-BREAK SPACE}のようにユニコード文字の名前を指定する形のユニバーサル文字名が使用可能になっています。この名前はユニコード標準で規定されているもので、一部の文字に対してはエイリアスとして複数の名前が対応する場合があります。

static_assert(u8'\N{LINE FEED}' == u8'\N{NEW LINE}'); // ✅

このエイリアスユニコード標準で規定されているもので、エイリアスにはいくつかの種類があります

  • correction Aliases : 誤った文字名が割り当てられてしまった場合の代替表記
  • control Aliases : 制御文字のエイリアス
  • alternate Aliases : 広く使用されている代替名のエイリアス
  • figment Aliases : 文書化されたものの標準では採用されなかったエイリアス
    • 現在次の3つのみ
    • U+0080 PADDING CHARACTER
    • U+0081 HIGH OCTET PRESET
    • U+0099 SINGLE GRAPHIC CHARACTER INTRODUCER
  • abbreviation Aliases : 一般的な略語によるエイリアス

ユニコード標準の意図として、一度公開した名前はたとえそれが間違っているものだったとしても変更せず、代わりのエイリアスを提供することによって修正するという方針があります(すなわち、安定性を強く保証している)。correctionエイリアスはそのためにあるものです。

これらのうち、C++標準ではfigmentとabbreviationのエイリアスが除外されており、使用できません。この提案は、これらのエイリアスを使用可能にすることを提案しています。

なぜこのようになっているかというと、P2071R2(ユニコード文字名エスケープの提案)の議論と採択の当時のC++が参照していたユニコード標準が古かったため(正確には参照先が異なったため)、これらのエイリアスが含まれていなかったからの様です。

しかしその後P2736R2の採択によって参照先が更新されたためこのような制限は無くなっており、C++標準の文書の変更のみでこれらのエイリアスに対応することができます。

この提案で使用可能になるエイリアスの例

using namespace std::literals;

// U+007F
static_assert(u8'\N{DELETE}' == u8'\N{DEL}'); // `DEL`はabbreviationエイリアス

// U+0081
static_assert(u8"\u{0081}"sv == u8"\N{HIGH OCTET PRESET}"sv); // `HIGH OCTET PRESET`はfigmentエイリアス

P3734R0 Not all predicates must be regular

述語を使用するアルゴリズムに対する、その述語が要素を変更しないという要件を緩和する提案。

std::find_ifなど(そのrange版も含めて)述語を受け取るアルゴリズムにおいて述語はregular_invocableによって制約されており(indirect_unary_predicate -> predicateを通して)、それによって述語の呼び出しを通じた範囲要素の変更やその他副作用が禁止されています。

しかし、範囲のイテレーションを一回で済ませることを意図してこれらのアルゴリズムの述語において要素を変更したい場合があり、特に要素を一回しか参照しないアルゴリズムではそれを行ったとしても動作には影響がないはずです。

ranges::remove_ifによる例

bool allowed_in_id(char c) {
  return c >= 'a' && c <= 'z';
}

void sanitize_id(std::string& id) {
  auto removed_range = std::ranges::remove_if(id, [](char& c) {
    if (c == ' ') {
      c = '-';  // 👈 ここで要素を変更している
      return false;
    }
    return not allowed_in_id(c);
  });

  id.erase(removed_range.begin(), removed_range.end());
}

このコードは"abc //def"の様な文字列入力を"abc-def"のように変換するものです。これは[a-z]とスペース以外の文字をremove_ifした後でスペースを-に置き換えるような2回のアルゴリズムを伴うコードよりも効率的なものです。しかし、前述のようにremove_ifranges版でなくても)はその述語が要素を変更することを許可しておらず、違反すると未定義動作となるためこのコードは合法ではありません。

しかし一方で、remove_ifはその動作に当たって要素を一回しか参照しないため、その一回の参照時にその要素を変更したとしてもその動作がおかしくなることはありません。上記の例はそのことを仮定しており、標準ライブラリの複雑な要件を良く知らない一般ユーザーにとっては自然な推論となるはずです。

この提案は、述語を受け取るアルゴリズムのうち要素を一度しか参照しないなどの事から要素を変更しても問題ないものについては、述語に対するregular_invocableの要件を緩和することを提案するものです。

ここでの対象は次のものです

  • all_of
  • any_of
  • none_of
  • find
  • find_last
  • count
  • mismatch
  • equal
  • starts_with
  • copy_if
  • replace
  • remove
  • is_partitioned/partition

regular_invocableの制約は意味論的なものであり、この提案が採択されても実装を何か変更する必要はないはずです(制約を除いて)。

このような変更に対する懸念の一つはこれら範囲を変更しないアルゴリズムの冪等性が失われるというものだと思われますが、実際には現状でもその保証は無く、未定義動作の上に成り立っている淡い期待のみであり、実装によって何か静的・動的にチェックされているものではありません。範囲を変更する述語を渡さない限り現在の推論が失われるものでもないため、現状より悪くなるものではないとしています。

P3735R0 partial_sort_n, nth_element_n

中間イテレータを指定する必要があるアルゴリズムについて、その必要のないバージョンを追加する提案。

partial_sortアルゴリズムは、範囲のN要素分の部分ソートを行うアルゴリズムであり、実行後の範囲は先頭N個がソートされた状態になります。この時、このNは数値で指定するのではなく、イテレータで指定する必要があります。

#include <ranges>
#include <algorithm>
#include <vector>
#include <print>

int main() {
  constexpr int N = 5;
  std::vector vec = {5, 3, 10, 4, 1, 11, 0, 9};

  std::println("before: {}", vec);

  std::ranges::partial_sort(vec, vec.begin() + N);

  std::println("after: {}", vec);
}
before: [5, 3, 10, 4, 1, 11, 0, 9]
after: [0, 1, 3, 4, 5, 11, 10, 9]

partial_sortで部分ソートしたい要素数Nイテレータで指定する際は、begin() + Nのようなイテレータの進め方がなされることが多々あります。これは当然、Nが範囲の長さを超えていれば未定義動作となります。筆者の方の経験によれば、このように未定義動作に陥っているコードを何度も見てきたとのことです。

安全に書く方法としてはstd::ranges::next()を使用する方法があります

std::ranges::partial_sort(vec, std::ranges::next(vec.begin(), N, vec.end()));

しかしこれはあまりにも冗長で、バグの本質的原因を解決するものでもありません。このバグは、アルゴリズムがユーザーにイテレータの加算を強いていることが原因です。そのため、Nを直接アルゴリズムに指定できれば解決されるはずです。

std::ranges::partial_sort(vec, N);

この提案は、partial_sortの様に中間イテレータをとるアルゴリズムについてこのように処理数Nを直接指定できるようにする~_nバージョンを追加しようとするものです。ここで提案しているのは次の2種類のアルゴリズムです

  • partial_sort_n : partial_sort
  • nth_element_n : nth_element

他のアルゴリズムはランダムアクセスイテレータを使用していないなどの問題からここでは対象にしていません。また、変更はrange版だけでなく旧来の非rangeアルゴリズムに対しても行うことを提案しています。

SG9のレビューではこの提案の方向性が好まれたようで、おおむね受け入れられてLEWGに転送されています。ただし、命名については_nではなく_at_mostが好まれたようです。

P3736R0 Slides against P2971R3 - No implication for C++

P2971に反対することを説明するスライド。

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

P2971で提案されている=>演算子に反対するいくつかの理由が説明されています。

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

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

std::arrayの実装については標準はあまり多くを語っていないものの生配列をラップした集成体型によるものと理解されており、現在の標準ライブラリ実装はそのようになっています。その実装方法についてはほぼ他の選択肢が無いのですが、より細かい実装については指定していない部分も多く、例えば次のような実装が準拠実装となります

struct alignas(1024) malice_and_evil {
  constexpr malice_and_evil() { }
  malice_and_evil(const malice_and_evil&) { }
};

template <typename T, size_t N>
struct array {
  T __array[N];
  malice_and_evil evil;
};

このように実装されたstd::arrayはコピーが定数式にならず、要素型に関わらずトリビアルコピー可能ではなく、サイズとアライメントはT[N]よりも大きくなります。

この提案は、std::arrayの実装自由度を制限することでこのようなおかしな準拠実装を排除し、std::arrayが生配列の単純なラッパとなるようにしようとするものです。また、std::array<T, 0>の実装についてを細かく規定するようにしています。要素数がゼロのstd::arrayについては実装もそれぞれ微妙に異なっているようで、この提案ではstd::array<T, 0>が満たすべき実装の制約についてを追加しています。

  • Tトリビアルコピー可能・標準レイアウト・構造的な型、のいずれか(もしくはすべて)であるなら、std::arrayもそうなる
  • std::arrayは基底クラスを持たず、単一の公開非staticメンバとしてN要素の生配列を持つ
  • std::array<T, 0>
    • 配列メンバ変数を持たない
      • 代わりに、未規定のトリビアルコピー可能で標準レイアウトな空の集成体型Uの非staticメンバ変数を持つ
      • Uのサイズとアライメントは1 ~ sizeof(T)の間で実装定義
    • 値表現は空
    • イテレータ取得関数はすべて値初期化された結果を返す
    • fill()/swap()は関数本体が空で例外を送出しない
    • [], front(), back()の末尾に実行が到達するとstd::terminate

などの制限を加えることを提案しています。

P3738R0 Make std::make_from_tuple SFINAE friendly

std::make_from_tupleをSFINAE friendlyにする提案。

LWG Issue 3528の採択によって、std::make_from_tuple()の説明専用関数であるmake-from-tuple-impl()に対してrequires節による制約が追加されました。

template<class T, class Tuple, size_t... I>
  requires is_constructible_v<T, decltype(get<I>(declval<Tuple>()))...> // 👈 これが追加
constexpr T make-from-tuple-impl(Tuple&& t, index_sequence<I...>) {
  return T(get<I>(std::forward<Tuple>(t))...);
}

しかし、これによってstd::make_from_tuple()が呼び出し可能かどうかをSFINAEによって調べるコードがハードエラーを起こすようになる場合があります。

template <typename T, typename Tuple, typename = void>
inline constexpr bool has_make_from_tuple = false;

template <typename T, typename Tuple>
inline constexpr bool has_make_from_tuple<
  T, Tuple,
  std::void_t<decltype(std::make_from_tuple<T>(std::declval<Tuple>()))>> = true;

struct A {
  int a;
};

// make_from_tuple()呼び出しにおいて集成体初期化は考慮されない
static_assert(!has_make_from_tuple<int*, std::tuple<A*>>);  // ng、`static_assert`ではない理由によってエラーになる

これは、std::make_from_tuple()の実装が内部でmake-from-tuple-impl()と同等の関数を呼び出すようにしている場合、追加された制約を満たさない場合にstd::make_from_tuple()関数定義内でエラーになることでハードエラーになってしまっています。

template<class T, class Tuple, size_t... I>
  requires is_constructible_v<T, decltype(get<I>(declval<Tuple>()))...> // A
constexpr T make-from-tuple-impl(Tuple&& t, index_sequence<I...>) {
  return T(get<I>(std::forward<Tuple>(t))...);
}

template<class T, class Tuple, size_t... I>
constexpr T make_from_tuple(Tuple&& t, index_sequence<I...>) {
  return make-from-tuple-impl(std::forward<Tuple>(t));  // Aの制約が満たされない場合、ここでエラーになってしまう
}

この提案は、このmake-from-tuple-impl()に追加された制約をstd::make_from_tuple()の宣言に移すことで、制約が満たされない場合にstd::make_from_tuple()の宣言で置換可能なエラーになるようにしようとするものです。

P3739R0 Standard Library Hardening - using std::optional

P3739R1 Standard Library Hardening - using std::optional

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

この提案では、std::inplace_vector特有のメンバ関数で現在ポインタを返しているものについてstd::optional<T&>を代わりに返すようにすることで、std::optional<T&>モナド/rangeインターフェースを利用できるようになるとともに、一部の安全ではないアクセスも堅牢化モードによるnullチェックされることにより安全性を高めようとするものです。

提案しているのは、コンテナの末尾要素を追加を試行するtry_~系の3つの関数です

namespace std {
  template<class T, size_t N>
  class inplace_vector {
  
    ...

    // 現在の宣言
    template<class... Args>
    constexpr pointer try_emplace_back(Args&&... args);

    constexpr pointer try_push_back(const T& x);
    
    constexpr pointer try_push_back(T&& x);

    // この提案
    template<class... Args>
    constexpr optional<T&> try_emplace_back(Args&&... args);

    constexpr optional<T&> try_push_back(const T& x);
    
    constexpr optional<T&> try_push_back(T&& x);

  };
}

これらの関数はinplace_vectorの末尾に要素を追加を試みて、そのキャパシティ余裕が無い場合にnullptrを返すものです。この提案ではポインタの代わりにoptional<T&>を使用することでこのインターフェースをより安全にしようとしています。

int main() {
  std::inplace_vector<int, 5> vec = {1, 2, 3, 4, 5};

  // 現在
  if (auto ptr = vec.try_push_back(6); ptr != nullptr) {
    // 成功した時の処理
  }

  // この提案
  vec.try_push_back(7).and_then([](int& n) {
    // 成功した時の処理
  });
}

P3740R0 Last chance to fix std::nontype

P3740R1 Last chance to fix std::nontype

std::nontypeの使用を再考する提案。

std::nontypestd::function_refメンバ関数ポインタ対応を行うために追加されたユーティリティで、メンバ関数ポインタをNTTPとして持っておくことで本来関数ポインタを保存するためのストレージを対応するthisを保存するために使用できるようにするものです。

void f(std::fuction_ref<int(int)> func);

struct S {
  int m(int) {
    ...
  }
};

int main() {
  S s{};

  f({std::nontype<&S::m>, s});  // ok、function_refでメンバ関数ポインタと対応するthisを束縛する
}

std::nontypeがない場合、別途ファクトリ関数と専用コンストラクタを用意して対応する必要があるなど、std::nontypeと比較すると複雑なアプローチをとる必要がありました。

しかし、C++26の他の機能の導入などの結果として、このstd::nontypeにはいくつかの問題が生じています。

  • std::constant_wrapperと機能が重複する(P2781
  • non-type template parameterという言葉はconstant template parameterに変更された(P2841

どちらも最終的にはC++26に導入されています(この提案の提出時点ではP2781はまだ入っていませんでした)。そのため、std::nontypeの名前を変更するのか、std::constant_wrapperで置き換えるのか、あるいはこのままいくのか、を決めなければなりません。

ここではstd::nontypeの必要性などから初めて様々な選択肢を検討した結果

  • std::nontypestd::constant_wrapperで置き換える
  • std::nontypeをリネームする

のどちらかが良いとして、両方に対応する文言変更を提案しています。

P3741R0 views::set_operations

集合演算を行うRangeアダプタの提案。

ソート済み範囲を集合と見立てて差集合や共通部分などの集合演算を行う操作は、アルゴリズムとしては存在しています。しかし、アルゴリズムの場合は出力の範囲を別に用意する必要があります。

一方、Rangeアダプタとしてこれを実行できれば、遅延評価されることによって出力用の範囲は必要なくなり、オンザフライで集合演算を実行できます。

// アルゴリズムによる集合演算
std::vector<int> diff;
ranges::set_difference(v1, v2, std::back_inserter(diff));

// Rangeアダプタによる集合演算
auto diff = v1 | views::set_difference(v2);

この提案は、既存の集合演算アルゴリズムに対応する集合演算を行うRangeアダプタを提案するものです。ここでは、std::includeを除いた4つの集合演算

  • std::set_union
  • std::set_intersection
  • std::set_difference
  • std::set_symmetric_difference

に対応するRangeアダプタ

  • views::set_union
  • views::set_intersection
  • views::set_difference
  • views::set_symmetric_difference

を提案しています。

これらのRangeアダプタはそれぞれ専用のview型を持ちますが、それぞれの集合演算の性質を踏まえて制約等は大きく2つのタイプに分かれます

// set_intersectionとset_differenceのview型の制約
template<view V1, view V2>
  requires input_range<V1> && input_range<V2> &&
           indirect_strict_weak_order<ranges::less, iterator_t<V1>, iterator_t<V2>>
class set_[intersection|difference]_view;
// set_unionとset_symmetric_differenceのview型の制約
template<view V1, view V2>
  requires input_range<V1> && input_range<V2> &&
           indirect_strict_weak_order<ranges::less, iterator_t<V1>, iterator_t<V2>> &&
           concatable<V1, V2>
class set_[union|symmetric_difference]_view;

前者は片方の範囲(V1)の要素型を結果とすればよいのに対して、後者は両方の範囲の要素型を結果とする必要があるため、concatable<V1, V2>の違いがあります。

どのアダプタも範囲v1, v2に対してv1 | views::set_xxx(v2)の様にパイプライン記法で記述できるようにしています。

4つのアダプタ(view)いずれの場合でもそのイテレータによって演算が行われます。まずイテレータが構築されるとsatisfy()関数(その集合演算に対応した要素判定関数)によって最初の出力要素が特定され、その後は++イテレータが進むごとに次の集合要素を特定していきます。動作としてはviews::filterに近いです。

このsatisfy()関数では2つの入力範囲の両方を順番に進めていく必要があるため、イテレータは2つの入力範囲のイテレータと番兵を共に保持している必要があります。

// set_intersectionのviewのイテレータの実装イメージ
class set_intersection_view::iterator {
  iterator_t<V1> current1_;
  sentinel_t<V1> end1_;
  iterator_t<V2> current2_;
  sentinel_t<V2> end2_;

  constexpr void satisfy() {
    while (current1_ != end1_ && current2_ != end2_) {
      // V1の中から次の有効な要素を探す処理
      ...
    }
  }

  constexpr iterator(iterator_t<V1> current1, sentinel_t<V1> end1,
                     iterator_t<V2> current2, sentinel_t<V2> end2)
    : current1_(std::move(current1)), end1_(end1),
      current2_(std::move(current2)), end2_(end2) {
    satisfy();
  } 

public:
  constexpr decltype(auto) operator*() const { return *current1_; }

  constexpr iterator&
  operator++() {
    ++current1_;
    ++current2_;
    satisfy();
    return *this;
  }

  friend constexpr bool operator==(const iterator& x, default_sentinel_t) {
    return x.current1_ == x.end1_ || x.current2_ == x.end2_;
  }
};

基本的にはこのようなイテレータ実装の細部を少しづつ調整することで4つのviewを実装することができます。set_union/set_symmetric_differenceの場合は両方のイテレータから値を読み取る必要があるため、ある時点でどちらのイテレータを使用するべきかを表すフラグが一つ必要になります。

その他の性質は次のようになっています

  • rangeカテゴリ: もっとも強くてもforward_range
  • common_range: ×
  • borrowed_range: V1V2がともにborrowed_rangeなら
  • const-iterable
    • set_union: 〇
    • それ以外: ×
      • begin()のキャッシュの必要がある

サンプルコード

int main() {
  const auto v1 = std::views::iota(1, 6);
  const std::vector v2 = {1, 3, 5, 7};

  auto uni = v1 | views::set_union(v2);
  // [1, 2, 3, 4, 5, 7]
  
  auto intersection = v1 | views::set_intersection(v2);
  // [1, 5]
  
  auto difference = v1 | views::set_difference(v2);
  // [2, 4]
  
  auto symmetric_difference = v1 | views::set_symmetric_difference(v2);
  // [2, 4, 7]
}

P3742R0 C++ Standard Library Ready Issues to be moved in Sofia, Jun. 2025

6月に行われたSofia会議でWDに適用されたライブラリに対するIssue報告の一覧

P3745R0 Rebuttal to P1144R13

P1144R13に対して反論を行うスライド。

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

P1144R13はP2786R13に対して反対を表明するものですが、これはさらにP1144R13の主張に反論するものです。

スライドでは、関連する提案の提出タイミング等についての説明を行った後、P1144R13の主張(の一部)に対して一つ一つ反論を行っています。

P3746R0 LEWG Slides for P3637R0 Inherit std::meta::exception from std::exception

P3637R0の紹介スライド。

std::meta::exceptionstd::exceptionを継承しないことの原因が現在問題ではないことを説明し、そうすることの利点などを簡単に説明しています。

P3747R0 Call side return type deduction

式の戻り値の型を取得する構文の提案。

std::numeric_limitsstd::numbersなどの利用時には、数値型を指定して値を取得する必要があります。

double i = std::numeric_limits<double>::max();

float pi = std::numbers::pi_v<float>;

このような場合、初期化先の型とテンプレートパラメータの型が一致しており、冗長に指定しなければなりません。この時に、この初期化先の型を初期化している式で取得できると冗長な型の指定を省略することができます。

double i = std::numeric_limits<deduce>::max();  // deduceはdoubleが取得される

float pi = std::numbers::pi_v<deduce>;  // deduceはfloatが取得される

この提案は、このように初期化式の初期化先の型を取得することのできる言語機能を提案するものです。この提案ではこれを仮にdeduceとしています。

上記のような変数初期化であれば、変数の宣言の方をautoにしておけば型を2回指定する必要は無くなりますが、autoを使用できない所では2回指定しなければなりません。

// メンバ変数定義での利用の例
struct S {
  // これはできない
  auto i = std::numeric_limits<int>::max();

  // この提案
  int i = std::numeric_limits<deduce>::max();
};
// これは関数テンプレートになってしまう
void func(auto i = std::numeric_limits<int>::max());

// この提案
void func(int i = std::numeric_limits<deduce>::max());
// Double/Float を double/float の強エイリアスとする
Double func(Double);

Float f = 1;

func(f); // ng、変換できない
func(static_cast<deduce>(f)); // 自動キャスト

このような初期化先の型の取得は、テンプレート型変換演算子において現在でも限定的に利用することができます。しかし、std::numbersなどの既存のものを今からテンプレート型変換演算子を備えた型に変更することは後方互換性を保てない(ABI破壊になる)ために不可能です。このソリューションであれば、他の部分にほとんど影響を与えることなく導入できます。

P3748R0 Inspecting exception_ptr works should be constexpr.

std::exception_ptr_cast()constexpr指定する提案。

C++26で定数式での例外送出対応を受けて、<exception>ヘッダのexception_ptr関連のユーティリティはconstexpr指定されて定数式で利用可能なようになっています。

しかし、同じくC++26で追加されたstd::exception_ptr_cast()はそうなっていません。

この関数の実装にあたって定数式で実行できないような障害はないはずだとして、この提案ではこの関数にconstexprを追加することを提案しています。

P3749R0 Slides in response to P3655R2 - Concerns regarding std::zstring_view

P3655R2(zstring_view提案)への懸念を説明したスライド。

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

主に、パフォーマンスの検討が足りない事、std::string_viewを挟むと変換できなくなること、命名が分かりづらいこと、についての懸念を表明しています。

P3752R0 Core Language Working Group "ready" Issues for the June, 2025 meeting

2025年06月に行われたSofia会議でWDに適用されたコア言語に対するIssue報告の一覧。

P3753R0 Slides for P3740R0 - Last chance to fix std::nontype

P3753R1 Slides for P3740R1 - Last chance to fix std::nontype

P3740R1の紹介スライド。

P3740R1は少し上の方にあります。

P3740R1をはじめとするstd::nontypeの修正関連の提案(今月いくつかある)の背景となる、現時点で結果的にstd::nontypeに起きてしまっていることについて簡単に説明されています。

P3754R0 Slides for P3100R2 presentation to EWG

P3100R2の紹介スライド。

P3100R2に関しては以前の記事を参照

P3100R2の成り立ちや現状、コア言語UBホワイトペーパーにおける立ち位置を説明したうえで、P3100R2の内容を解説しています。

P3757R0 Remove value-type invocability requirement from indirect unary callable concepts

indirectly_unary_invocableコンセプトから、イテレータの値型によって呼び出し可能であることを要求する制約を削除する提案。

indirectly_unary_invocable<F, I>コンセプトは、I型のイテレータ経由で値を取得してその値によってFのオブジェクトが呼び出し可能であることを表現するコンセプトです。このコンセプト定義中では、イテレータの参照型(間接参照結果)だけでなく値型に対しても呼び出し可能であることを要求しています。

template<class F, class I>
  concept indirectly_unary_invocable =
    indirectly_readable<I> &&
    copy_constructible<F> &&
    invocable<F&, indirect-value-t<I>> && // 👈
    invocable<F&, iter_reference_t<I>> &&
    common_reference_with<
      invoke_result_t<F&, indirect-value-t<I>>, // 👈
      invoke_result_t<F&, iter_reference_t<I>>>;

indirect-value-t<I>は射影の結果を反映して値型を取得するコンセプトですが、射影を考慮しない場合はstd::iter_value_t<T>&が使用されます。イテレータの値型は多くの場合素のprvalueな型であるため、この制約によって実際には左辺値参照で呼び出さないような場合にコンパイルエラーになることがあります。

vector<string> v;
ranges::for_each(v | views::as_rvalue, [](string&& s) { /* */ }); // ng、右辺値で呼び出しできれば十分だけど・・・

また、この値型の呼び出し要求に伴うcommon_reference要件によって、イテレータの参照結果をムーブ不可能な型に射影するユースケースがサポートされなくなっています(common_reference_withコンセプト内で変換可能性がチェックされるため)。

歴史を紐解くと、indirectly_unary_invocableコンセプトのこのような要件は、その二項版コンセプトであるindirect_binary_predicateindirect_equivalence_relationにおける同様の制約から来ているようです。これらの二項版コンセプトでは確かに、それを使用するアルゴリズムにおいてイテレータの値型へのコピーとそれを用いた関数呼び出しが発生するためこれは必要な制約です(ranges::unique_copyなど)。

単項版のコンセプトは二項版からそれを継承してこのような制約をもっているようです。しかし、単項版コンセプトの使用場所を見てみるとそれはどうやら不要な制約であるようで、この提案ではこの制約を削除しようとしています。

indirectly_unary_invocableコンセプトを直接的/間接的に使用しているもの

  • ranges::for_each
  • ranges::all_of, any_of, none_of
  • ranges::find
  • ranges::count_if
  • ranges::copy_if, replace_if, replace_copy_if, remove_if, remove_copy_if
  • ranges::is_partitioned
  • ranges::partition, stable_partition, partition_copy, partition_point
  • ranges::filter_view, take_while_view, drop_while_view
  • std::projected<I, Proj>
  • std::projected_value_t<I, Proj>

このうち、最後のstd::projected_value_tだけは実際に値型のこのような制約が重要な意味を持っています。そのため、std::projected_value_tにはこの提案の変更の影響を受けないように修正しています。

この提案後のindirectly_unary_invocableコンセプトの定義

template<class F, class I>
  concept indirectly_unary_invocable =
    indirectly_readable<I> &&
    copy_constructible<F> &&
    invocable<F&, iter_reference_t<I>>;

また、これと同じ変更を同種のindirectly_regular_unary_invocableindirect_unary_predicateにも適用しています。

P3760R0 Presentation: constexpr 'Parallel' Algorithms

P2902の紹介スライド

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

P2902では並行アルゴリズムを定数式で使用可能にすることを提案していますが、このスライドではそのモチベーションについて詳細に説明しています。

P3763R0 Remove redundant reserve_hint members from view classes

標準のview型がsize()を提供している場合にreserve_hintを削除する提案。

P2846R6の採択によってreserve_hintCPOおよび、各標準view型(Rangeアダプタ実装クラス)にreserve_hintメンバ関数が定義されるようになっています。これは、size()が利用できない場合に範囲サイズの見積もりを伝達するもので、ranges::toや一部の標準コンテナのfrom_rangeコンストラクタなどで活用されています。

これについて定義をよく見ると、少しおかしなところがあります。

まず、sized_rangeコンセプトはapproximately_sized_rangeコンセプト(reserve_hintCPOが使用可能かどうかを表す)によってreserve_hintが利用可能かどうかをチェックしています。

template<class T>
concept sized_range =
  approximately_sized_range<T> && requires(T& t) { ranges::size(t); };

次に、各view型ではsized_rangeコンセプトとapproximately_sized_rangeコンセプトを使用して.size()/.reserve_hint()を定義しています。

template<range R>
struct some_view : public view_interface<some_view<R>> {
  […]
  constexpr auto size()               requires sized_range<R>
  constexpr auto size() const         requires sized_range<const R>
  constexpr auto reserve_hint()       requires approximately_sized_range<R>
  constexpr auto reserve_hint() const requires approximately_sized_range<const R>
  […]
};

そして、ranges::reserve_hintCPOは.size()が利用可能であれば.reserve_hint()よりもそちらを優先して使用します。

すなわち、各view型は.size()を持つ場合は必ず.reserve_hint()も持ちますが、範囲の正確なサイズ(.size())が利用可能な場合にサイズの見積もり(.reserve_hint())を提供しても実用的ではありません。標準ライブラリ内ではranges::reserve_hintCPOを介してサイズ見積もりを取得するため、これをサポートするユースケースはありません。

この提案では、reserve_hintは正確なサイズが得られない場合にのみ提供されるべきであり、.size()が利用可能な場合は不要であるため削除することを提案しています。

これを残しておくと、不要なインスタンス化のオーバーヘッド(コンパイル時間増大)にもつながります。

提案では、view型の.reserve_hint()メンバ関数の制約を修正して、approximate_sized_rangeが満たされてsized_rangeが満たされていない場合にのみ定義されるようにしています。

P3764R0 A utility function for propagating the most significant bit

整数の最上位ビットだけを残したビットマスク値を生成する関数std::msb_to_maskの提案。

ビット操作においては、符号付整数値の最上位ビット(すなわち符号ビット)をその他すべてのビットに伝播させたビットマスクを作成して使用することが多々あり、これを用いると整数が正か負かで分岐する必要のある処理において分岐無しの実装が可能になる場合があります。

// ナイーブなmod実装
int mod_naive(int x, int y) {
  int rem = x % y;
  if (rem < 0) rem += y;
  return rem;
}

// 分岐無しの実装
int mod_branchless(int x, int y) {
  int rem = x % y;
  rem += y & (rem >> (INT_WIDTH - 1)); // 👈
  return rem;
}

このような最適化はコンパイラが優秀であれば自動で適用してくれる場合もあるようですが、コードが複雑化したりするとその信頼性は低下します。また、このような形のビットマスクの生成は少し面倒であり、符号付/符号なし整数型の両方をサポートするような汎用実装では正しい記述が少し難しくなります。そして、このような関数の使用例はgithubにおいて多数見つけることができます。

この提案は、このような符号ビットからのビットマスク生成を行うビット演算関数std::msb_to_mask()<bit>に追加する提案です。

std::msb_to_mask()は次のような関数です

namespace std {
  template<class T>
  constexpr T msb_to_mask(T x) noexcept;
}

Tとしては符号付整数型と符号なし整数型の両方をサポートします。また、std::simdオーバーロードも追加することを提案しています。

呼び出すと、引数xの符号ビットをそれ以下の全てのビットに伝播させたビットマスクに対応する整数値を返します。

サンプルコード

int main() {
  std::println("{:032b}", std::bit_cast<std::uint32_t>(10));
  std::println("{:032b}", std::bit_cast<std::uint32_t>(msb_to_mask(10)));
  std::println("{:032b}", std::bit_cast<std::uint32_t>(-10));
  std::println("{:032b}", std::bit_cast<std::uint32_t>(msb_to_mask(-10)));
  std::println("{:032b}", 10u);
  std::println("{:032b}", msb_to_mask(10u));
}

出力例

00000000000000000000000000001010
00000000000000000000000000000000
11111111111111111111111111110110
11111111111111111111111111111111
00000000000000000000000000001010
00000000000000000000000000000000

処理の特性上、符号なし整数型に対しては常に0を返します(はずです)。

提案文書より、実装例。

namespace std {
  template<signed-or-unsigned-integer T>
    constexpr T msb_to_mask(T x) noexcept {
      using S = make_signed_t<T>;
      return static_cast<T>(static_cast<S>(x) >> numeric_limits<S>::digits);
    }
}

CPUによってはこれを行う命令を備えている場合があるようです。

P3765R0 Deprecate implicit conversion from bool to character types

boolから文字型への暗黙変換を非推奨にする提案。

例えば次のようなコードにおいて、boolからchar型への暗黙変換が発生していますが、これは警告などが発っせられることもなくコンパイルされ、実行できてしまいます。

std::string_view str = /* ... */;
if (str.ends_with('\n' || str.ends_with('\r'))) {
//                   ^^
  ...
}

このコードでは'\n'の後にかっこを入れ忘れたうえで別の場所にかっこを挿入してしまっている例です。このコードにおける'\n' || str.ends_with('\r')は全体として常にtrueになります。

まず、組み込み||オペランドboolでなければならないため、'\n'boolに変換されます。これはchar(0)ではないためbool値としては常にtrueになるため、str.ends_with('\r')の結果がなんであれtrue || str.ends_with('\r')trueになります。そして、その結果を用いたstr.ends_with(true)の呼び出しはbool型からcharへの暗黙変換によってstr.ends_with(char)オーバーロードを呼び出します。

しかし当然、このコードはほとんどの場合に意図通りに動作しないでしょう。

このような変換は意味のあるものとは思えず、上記例のようにバグの元にしかならないため、この提案ではこの変換を非推奨にしようとしています。

提案しているのは、bool型から文字型、すなわちchar, wchar_t, char8_t, char16_t, char32_t型への暗黙変換を非推奨とすることです。削除することは提案しておらず、bool型からsigned char/unsigned char型への暗黙変換は対象ではありません。

int x = /* ... */, z;
unsigned char y = x % 2 != 0; // ok、提案後も非推奨ではない
z += y;

また、明示的変換も非推奨ではありません。

bool b = /* ... */;

char d1 = b;                    // deprecated
char d2(b);                     // deprecated
char d3{b};                     // deprecated
char d4 = {b};                  // deprecated
char d5 = char{b};              // deprecated

char k1 = char(b);              // OK
char k2 = (char)b;              // OK
char k3 = static_cast<char>(b); // OK

'a' + b;                        // OK

char d6 = +b;                   // OK

この提案はEWGの議論にてより時間をかけることに合意を得られず、リジェクトされています。

P3769R0 Clarification of placement new deallocation

配置new式におけるdelete演算子の選択に関する仕様を明確化する提案。

P3492ではユーザー定義配置new演算子を呼び出すnew式において、オブジェクト初期化失敗時に呼び出されるdelete演算子に対して確保したサイズに関する情報を渡せるようにしようとするものです。この仕様の策定・検討時に、配置new式においてどのdelete演算子が選択されるかについての仕様が不十分であったことが発覚し、P3492R2ではその修正も含める形の文言が提案されていました。

しかし、それはP3492の主たる提案とは少し外れたところにあり、変更点を明確にするために別の提案に分離されることになり、これがその提案です。

ここでは、次の提案から関連する文言を分離してきています

  • P3492R2
  • P2719R5

また、この提案の内容はDRとすることを提案しています。

この提案において、グローバルスコープでnew式に対応するdelete演算子が探索される場合、次のように選択されます

  1. 候補が関数テンプレートである場合、テンプレート引数推論を用いて候補関数テンプレートの特殊化を生成する。推論に使用される引数は、最初の引数とそれに続く引数。
  2. 仮引数宣言が省略記号(...)で終了する関数は、(選択されている)new演算子の仮引数宣言が同様に省略記号で終了していない場合、以降の検討候補から外す。
  3. 仮引数宣言が省略記号(...)で終了しないものの、(選択されている)new演算子の仮引数宣言が省略記号で終了している場合、以降の検討候補から外す。
  4. 引数の数が(選択されている)new演算子の引数の数と等しくない場合、以降の検討候補から外す。
  5. 引数の変換の後の関数引数の型が、それぞれの最初の引数を除いて、(選択されている)new演算子の引数の型と一致しない場合、以降の検討候補から外す。
  6. (ここまでの選別の後で)関数が一つだけ残っている場合、それを選択して選択プロセスを終了する。
  7. それ以外の場合、関数を選択せずに選択プロセスは終了する。

この選択の過程(特に4~5)において、delete関数末尾にデフォルト引数を指定する形で追加されている引数でもそれによって省略されたりせずに、厳密にマッチングされます。

struct A {};
struct T {};

void* operator new(std::size_t s, A& al); // #1

template<int = 0>
void operator delete(void* p, A& al); // #2

A al;
new (al) T(); // ok、#1と#2のペアを使用する
template<int I>
struct A {};
struct T {};

void* operator new(std::size_t s, A& al); // #1

template<int I>
void operator delete(void* p, A<I>& al); // #2
void operator delete(void* p, A<0>& al); // #3

A<0> al;
new (al) T(); // ok、#1を使用(deleteは選択されない
struct A {};
struct T {};

void* operator new(std::size_t s, A& al);
void operator delete(void* p, A& al) = delete;

A al;
new (al) T(); // ng、削除された関数が選択された

P3771R0 constexpr mutex, locks, and condition variable

標準ライブラリのミューテックスや条件変数関連のものを定数式で使用可能にする提案。

この提案は以前にstd::atomic関連のものを定数式対応させた提案(P3309R3)の延長であり、それに続いてミューテックスとロック、そして条件変数を定数式で使用可能にしようとするものです。ただし、その動機は実行時とコンパイル時で同じコードを使用可能にすることにあり、定数式で並行処理を行おうとするものではありません。

if constevalによってコンパイル時処理のための分岐は可能になっていますが、これはコードに余分な分岐と重複をもたらし、記述しやすさと可読性を低下させます。標準ライブラリのエンティティについてはconstexpr対応させておくことで、利用側のコードは何もせずに定数式と実行時の両方で動作するようになります。

この提案でconstexpr指定を追加しようとしているのは

  • std::mutex
  • std::recursive_mutex
  • std::timed_mutex
  • std::recursive_timed_mutex
  • std::shared_mutex
  • std::shared_timed_mutex
  • std::lock_guard
  • std::scoped_lock
  • std::unique_lock
  • std::shared_lock
  • std::call_once
  • std::condition_variable
  • std::condition_variable_any
  • std::notify_all_at_thread_exit

一部のミューテックス型が備えている、.try_lock_until()などの時間指定でロック取得を試みる関数は、定数式においては即処理を返すようになります。この時、対象のミューテックスがすでにロックされていたら即時に失敗するようにしています。これは、定数式においては時間計測の方法がないことと、実行がシングルスレッドであることによる動作の選択です。

また、いずれにおいても.native_handle()constexpr指定されません。

定数式においては未定義動作は禁止(コンパイルエラー)であり、デッドロックの発生は未定義動作となるためコンパイルエラーとなります(シングルスレッド動作時のそれは同じミューテックスに対して2度ロックを取得することであるため、検出は可能・・・?)。

P3772R0 std::simd overloads for bit permutations

<bit>のビット置換系操作APIstd::simdオーバーロードを追加する提案。

P3104R3では、C++29向けにビット置換系のビット演算に対応する関数を追加しようとしています。一方、C++26で追加されるstd::simdに対してはP2933R4で<bit>にすでにあるビット操作関数についてstd::simd対応がなされています。

この提案は、P3104R3で追加されるビット操作関数についてもstd::simd対応しておこうとするものです。

P3774R0 Rename std::nontype, and make it broadly useful

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

P3740R1(少し上)のLEWGでのレビューにおいては、std::nontypestd::constant_argにリネームするオプションが最も支持を集めましたが、LWGの時間不足もありC++26に採択しないという決定がなされたようです。

この提案は、そのオプションの方向性において、std::nontypestd::fnにリネームすることを提案しています。

std::constant_argではなくstd::fnとするのは、同時にC++29以降の拡張として提案しているメンバの追加によって、std::fnが関数ポインタを型システムに持ち上げる役割を担うようなユーティリティになることを見越しての事です。

この提案では、C++29以降の拡張としてstd::fnに関数呼び出し演算子と関数ポインタへの変換演算子を追加することを提案しています。これによって、std::fnは関数ポインタに対してラムダ式std::function_refなどと同等の関数ラッパとして働くようになります。

std::fn<f>std::nontype<f>)は関数fの関数ポインタをNTTP値として保持するため、関数ポインタ保持のためにストレージ領域を使用しません。さらに、std::fn<f>は異なるfに対して異なるインスタンス化をもたらします。これによって、標準アルゴリズムにおいて関数ポインタを使用した時でもインライン展開しやすくなります。

void f(int);
std::ranges::for_each(r, f);                           // インライン展開を妨げるため推奨されない
std::ranges::for_each(r, [](int x) { return f(x); });  // 正しいが面倒
std::ranges::for_each(r, std::fn<f>);                  // 正しく、簡潔

また、関数ポインタへの変換演算子はある程度の自由度を持たせておくことで、引数型の変換を考慮した関数ポインタの変換機能を提供できます。

bool is_prime(long long x);

bool(*p)(int) = std::fn<&is_prime>;

このように、std::fn<f>fのアドレス値を型名に取り込むことによって関数ポインタを型システムに持ち上げる役割を担うようになるため、より短く適切な命名としてstd::constant_argよりもstd::fnを選択しています。

提案文書より、std::fnの実装例

template<auto f>
struct fn_t
{
  using type = decltype(f);

  static constexpr bool is_function_ptr = std::is_function_v<std::remove_pointer_t<type>>;

  template<bool Noex, typename Ret, typename... Args>
  using func_type = Ret(*)(Args...) noexcept(Noex);

  constexpr operator type() const noexcept
    requires is_function_ptr
  { 
    return f;
  }

  template<bool Noex, typename Ret, typename... Args>
    requires (Noex ? std::is_nothrow_invocable_v<Ret, type const&, Args...> 
                   : std::is_invocable_r_v<Ret, type const&, Args...>)
  constexpr operator func_type<Noex, Ret, Args...>() const
  {
    if constexpr (is_function_ptr && std::is_convertible_v<type, func_type<Noex, Ret, Args...>>)
      return f;
    else
      return [](Args... args) noexcept(Noex) -> Ret {
        return std::invoke(f, std::forward<Args>(args)...);
      };
  }

  template<typename... Args>
    requires (!is_function_ptr)
  static constexpr std::invoke_result_t<type const&, Args...> operator()(Args&&... args)
    noexcept(std::is_nothrow_invocable_v<type const&, Args...>)
  { 
    return std::invoke(f, std::forward<Args>(args)...);
  }
};

template<auto f>
constexpr fn_t<f> fn;

この提案のC++26部分(リネームのみ)に関してLEWGの議論の結果、std::constant_argという名前にリネームすることに合意が取れたようです。

P3778R0 Fixing type_order template definition

std::type_orderによる比較結果の表現方法を修正する提案。

P2830R10では2つの型を実装定義の全順序の上で比較するためのメタ関数std::type_orderが提案され、これはC++26に採択されています。std::type_orderstd::type_order_v<T, U>のように使用して、2つの型T, Uの実装定義の<比較の結果をstd::strong_orderingの値で得ることができます。

このstd::type_orderの実装は次のようになっています

template <class T, class U>
struct type_order : integral_constant<strong_ordering, see below> {};

template <class T, class U>
constexpr strong_ordering type_order_v = type_order<T, U>::value;

このようにintegral_constant(特にstd::bool_constant)を使用してメタ関数の実装をある程度簡略化するのは他のメタ関数でもよくやられていることです。しかし、std::strong_orderingがNTTPとして使用できないため、この実装は不可能でした。

NTTPとして使用可能な型は構造的な型(structural type)という型のカテゴリで定義されており、クラス型の場合は集成体型のようにすべてのメンバ変数がpublicである必要があります。しかし、std::strong_orderingは通常プライベートメンバを持つ形で実装されるため、NTTPとして使用できません。

この提案は、std::type_orderstd::integral_constantを使わないように修正することでこの問題を解決しようとするものです。

std::type_orderの実装が先ほどの例のようにstd::integral_constantを使用する必要があるのは、Cpp17BinaryTypeTraitという要件に従うように指定されているからです。この要件の意図するところは次のようなものの様です

  1. std::integral_constant<T, v>への暗黙変換を行うことで、メタ関数の引数型を隠蔽することができる。
    • 意図的に使用すると、テンプレートのインスタンス化数を減少させられる
  2. ::typeメンバは1を明示的に実行するのに使用できる
  3. メタ関数の結果として::valueメンバを提供する
  4. value_typeメンバによって::valueの型を取得できる
  5. 利便性向上のための、constexpr暗黙変換演算子::valueへ変換)
  6. constexpr operator()は関数呼び出しを期待するコンテキストから値を取得するのに役立つ

これらの性質を実現するのにstd::integral_constantを使用するのは最も簡単なため、現在の定義はそうしています。この提案では、これらの性質を維持し他のメタ関数とほぼ同じ動作をさせながらもstd::integral_constantを使用しないようにするために、std::type_orderの構造体を独自かつ具体的に定義するようにしています。

変更後の定義は次のようになります

template<class T, class U>
struct type_order {
  static constexpr strong_ordering value = TYPE-ORDER(T, U);

  using value_type = strong_ordering;

  constexpr operator value_type() const noexcept { return value; }
  constexpr value_type operator()() const noexcept { return value; }
};

そして、std::type_orderCpp17BinaryTypeTraitへの適合の規定を削除しています。これによって、上記性質の1と2以外のものは維持されたままで、std::strong_orderingを比較結果に使用しつつ実装可能になります。

この提案は、2025年11月の全体会議を通過し、C++26に採択されています。

P3780R0 Detecting bitwise trivially relocatable types

トリビアルリロケーション可能な型が、memcpyによってトリビアルリロケーション可能であるかを調べる型特性を追加する提案。

P2786では、オブジェクトの再配置(リロケーション)という操作をライブラリ実装で使用可能にするための言語の調整と最低限のライブラリ機能が提供されます。リロケーション操作は通常ムーブ+デストラクタ呼び出しの複合操作となりますが、一部の条件を満たした方はもっと単純にmemcpyによるビットコピーによってリロケーションを行うことができます。

このmemcpyによるリロケーションが可能な型の事をトリビアルリロケーション可能(trivially relocatable)な型と呼び、P2786は専らこれを正式にC++コード上で利用可能にすることを目的としています。

トリビアルリロケーション可能な型とは、スカラ型やポインタ型などのプリミティブ型は当然含まれますが、普通のクラス型でもトリビアルリロケーション可能となる場合があります。例えば、通常の実装のstd::vectorトリビアルリロケーション可能になります。

P2786によるトリビアルリロケーション可能の定義では、多態的な型(polymorphic type)もトリビアルリロケーション可能であるとされます。これは、多態的な型は隠れた非静的メンバ変数としてポインタ(仮想関数テーブルへのポインタ)を持っているとみなすことができ、ポインタ型はトリビアルリロケーション可能であるため、多態的な型はその継承構造に関わらずトリビアルリロケーション可能となります。

しかし、一部のアーキテクチャやABIにおいてはポインタの署名が行われる場合があり、署名による認証に失敗するとポインタアクセスに失敗するようになります。例えばARM64eではPointer Authentication Code(PAC)と呼ばれる拡張命令を備えています。そして、仮想関数テーブルへのポインタがこの署名の対象になる場合があります。

このポインタの署名にはポインタの参照先アドレスとポインタ自体のアドレスが少なくとも使用されます。これにより、ポインタ署名をサポートする(+仮想関数テーブルへのポインタの認証を行う)ような環境においては、多態的な型を単純なmemcpyによってリロケーションしてしまうとうまく動作しなくなります(仮想関数テーブルへのポインタのアドレスが変化することでポインタ認証に失敗してしまう)。

P2786ではこれが考慮されており、std::trivially_relocate()関数を用いてトリビアルリロケーションを行うことで、この関数内でポインタ署名の再署名を行えるようにしています。

結局、P2786のトリビアルリロケーション可能な型の定義においては、多態的な型もトリビアルリロケーション可能となっています。しかし、それは必ずしもビットコピーによってトリビアルリロケーション可能であることを意味していません。このことは、多態的な型をバイト列として内部的に保持しうるようなラッパ型の実装時に問題となります。

例えば次のようなラッパ型を考えます

class Wrapper {
  // オブジェクトか、ヒープへのポインタを格納するバイト配列
  alignas(A) std::byte storage[N];
  /* ... other data ... */

public:
  Wrapper();

  // デストラクタとムーブコンストラクタがユーザー定義される
  ~Wrapper();
  Wrapper(Wrapper &&);
  /* other special member functions, rest of the API, etc. */
};

これをP2786のトリビアルリロケーションの枠組みにアダプトさせると次のようになります

class Wrapper trivially_relocatable_if_eligible {
  // オブジェクトか、ヒープへのポインタを格納するバイト配列
  alignas(A) std::byte storage[N];
  /* ... other data ... */

public:
  Wrapper();
  
  // デストラクタとムーブコンストラクタがユーザー定義される
  ~Wrapper();
  Wrapper(Wrapper &&);
  /* other special member functions, rest of the API, etc. */
};

このような型の具体例としてはstd::anyがあり、optionalinplace_vectorの実装の一つとして一般的なものです。

storage以外のメンバは通常整数やポインタ等のプリミティブ型しか必要ないため、この型はトリビアルリロケーション可能となります。ただしそのためには、storageに保存する型もまたトリビアルリロケーション可能でなければならず、そうではない場合は構築を禁止するかヒープに配置してそのポインタを(storageに)保存するようにする必要があります。

そのように実装されているとして、このWrapperクラスオブジェクトをリロケーション(std::trivially_relocate())する際に単純なmemcpyによって実行しても大丈夫でしょうか?特に多態的な型(前述のようにこれはトリビアルリロケーション可能)を保持している(storageに格納されている)場合にmemcpyによるリロケーションは安全でしょうか?

前述のように、ポインタ認証を行う環境においては、多態的な型を保持するWrapperクラスオブジェクトをmemcpyによってリロケーションするとポインタ認証に失敗するポインタが生成されてしまうため安全ではありません。std::trivially_relocate()によってリロケーションを行うときでも、std::trivially_relocate()Wrapperクラスの実装についての知識を持たないため、単純なmemcpyでリロケーションしていいのかポインタ再認証が必要なのか(そしてそのポインタはどこにあるのか)は分かりません。

そのため、このトリビアルリロケーション可能なWrapper型に直接格納できる型の要件はトリビアルリロケーション可能というだけではなく、memcpyによってリロケーション可能であるという要件も必要になります。これはP2786でも提供されていません。この提案は、これを検出する型特性を提案するものです。

ポインタ認証をサポートするかはプラットフォームによって異なり、また型が多態的であるかどうかということはこの問題の本質ではないため、std::is_polymorphicではこの問題の解決には適さないため、専用の型特性が必要となります。

この提案では、この型特性をstd::is_bitwise_trivially_relocatableとして提案しています。

Wrapperクラスでは、コンストラクタや代入演算子などでこの型特性を使って格納しようとしている型を内部バッファ(storage)に配置すべきかヒープに配置してポインタを保持すべきかを決定するようにすることで、プラットフォームによらず安全なトリビアルリロケーション対応を達成することができます。

また、このmemcpyによってリロケーション可能であるという要件は、動作するUBの上ですでにリロケーションを活用している既存のライブラリ実装が想定している定義でもあります。既存のライブラリの要件と合致する型特性を追加しておくことでC++26リロケーションへの移行を促しやすくなる効果もあります。

P3781R0 is_*_type should imply is_type

リフレクション(std::meta::info)が何らかのカテゴリの型を反映しているかを取得する関数群について、型ではないリフレクションの入力がエラーにならないようにする提案。

リフレクションのユースケースとしてかなり一般的になると予想されることの一つは何らかのバインディングを作成することだと予想されます。その際、対象が関数なのかクラスなのかによって処理が変わることがあると予想され、その場合はそれを判定して分岐することになります。例えば次のような単純な例が考えられます

// 名前空間fooのリフレクションを取得
constexpr auto namespace_info = ^^foo;

// foo名前空間以下のもののリフレクションを取得しイテレーションする
for (int i = 0; auto element : std::meta::members_of(namespace_info, std::meta::access_context::current()))
{
  // elementがクラスかを判定
  if (std::meta::is_class_type(element))
  {
    // クラスに対する処理をここで行う
    classes[i++] = std::meta::identifier_of(element);
  }
}

どのようなバインディングを作成するかは置いておいて、何かしらの対象(ここでは名前空間)のエンティティの処理は例えばこのようになるでしょう。しかしこのコードは実際には期待通りに動作しません。なぜなら、std::meta::is_class_type(element)elementが型のリフレクションではない場合にfalseを返すのではなくコンパイルエラーになるためです。この例では名前空間のメンバについてイテレーションしているので、関数や名前空間が出てきたところでエラーになります。

正しくは、std::meta::is_typeelementが型のリフレクションであるかどうかを先に判定する必要があります。

  // elementがクラスかを判定
  if (std::meta::is_type(element) && std::meta::is_class_type(element))

とはいえこれは直感的ではなく冗長です。

std::meta::is_*_typeという関数は多数ありいずれも同様の問題があります。is_*_typeという命名からは、それが型かどうかをbool値で返すものであるという妥当な推測が立ち、型ではないものについてクエリしたときにfalseではなくエラーを返すのは直感的ではありません。

このため、この提案ではstd::meta::is_*_type系の関数においてstd::meta::is_typeによる判定をまず行うようにすることを提案しています。これにより、上記の例ではstd::meta::is_class_type(element)だけで意図通りに動作するようになります。

ただし、std::meta::is_*_type系の関数のうち、2項で型以外の入力が意味を持たないものについては除外しています。除外する関数は次のものです

// associated with [meta.unary.prop], type properties
consteval bool is_assignable_type(info type_dst, info type_src);
consteval bool is_trivially_assignable_type(info type_dst, info type_src);
consteval bool is_nothrow_assignable_type(info type_dst, info type_src);

consteval bool is_swappable_with_type(info type_dst, info type_src);
consteval bool is_nothrow_swappable_with_type(info type_dst, info type_src);

// associated with [meta.rel], type relations
consteval bool is_same_type(info type1, info type2);
consteval bool is_base_of_type(info type_base, info type_derived);
consteval bool is_virtual_base_of_type(info type_base, info type_derived);
consteval bool is_convertible_type(info type_src, info type_dst);
consteval bool is_nothrow_convertible_type(info type_src, info type_dst);
consteval bool is_layout_compatible_type(info type1, info type2);
consteval bool is_pointer_interconvertible_base_of_type(info type_base, info type_derived);

template <reflection_range R = initializer_list<info>>
consteval bool is_invocable_r_type(info type_result, info type, R&& type_args);

template <reflection_range R = initializer_list<info>>
consteval bool is_nothrow_invocable_r_type(info type_result, info type, R&& type_args);

これらの関数ではどちらかの引数に型以外のものを入れた時にfalseを返すと誤解を招く可能性があります。例えば、is_same_type(r1, r2)は一方もしくは両方の引数が型を反映したものでない場合にfalseを返すと型として一致していなかったのか引数が型ではなかったのかわからなくなります。

P3784R0 range-if

範囲if文の提案。

範囲が空であるかによって処理を分岐したい場合ということは良くあります。単純にはranges::emptyを使用して分岐すればよいのですが、真にジェネリックに記述しようとすると、emptyが使用できない場合があり得るためイテレータを使用しなければなりません。

// パイプラインを使用するなどして生成されている何か範囲
auto && r = a-view-pipeline; 

if(!r.empty()) { // ❌ すべてのビュー型がempty()を提供するわけではない
  for(auto & x : r) { 
    … 
  } 
} else { 
  // 範囲が空の場合の処理
}
auto && r = a-view-pipeline;

// 😬 明示的にイテレータを使用する
if(auto it{r.begin()}; it != r.end()) { 
  for(; it != r.end(); ++it) { // 冗長なチェック
    auto & x{*it}; 
    … 
  } 
} else { 
  // 範囲が空の場合の処理
}

この用途に範囲forを使用できないのは、else節に相当するものが無いためです。

範囲for + フラグという方法もありますが、フラグの扱いそのものやそのスコープについてなどの問題がありベストな解決策とは言えません。

// 😬 空かどうかのフラグ
auto empty{true}; 
for(auto & x : a-view-pipeline) { 
  empty = false; 
  … 
} 

if(empty) { 
  // 範囲が空の場合の処理
}

この提案は、範囲forに対応する範囲if文によってこのような場合の記述の問題を言語機能で解決しようとするものです。

提案している構文は通常のif文に対して、条件を記述するところに範囲forのように記述する構文です

if (auto & x : a-view-pipeline) { 
  // 範囲のイテレーション処理
  // ここはループする
} else { 
  // 範囲が空の場合の処理
  // ここは一回で抜ける
}

これは範囲forにかなり近いもので、それと同じように展開されます

if constexpr(opt)(init-statementopt for-range-declaration : for-range-initializer) 
statement1 else statement2

の様な文は

{
  init-statement(opt); 
  auto && range = for-range-initializer; 
  auto begin = begin-expr; 
  auto end = end-expr; 
  if constexpr(begin != end) 
    do { 
      for-range-declaration = *begin; 
      statement1 
    } while(((void)++begin), begin != end); 
  else 
    do { statement2 } while(false); 
}

のように展開されます。

この範囲ifではループ部とelse節の両方でbreakcontinueを使用可能にすることを意図しており、そのためにelse側(statement2)もダミーループで囲われます。

P3785R0 Library Wording Changes for Defaulted Postfix Increment and Decrement Operators

P3668の後置インクリメント/デクリメント演算子default定義を利用して標準ライブラリの規定を変更をする提案。

P3668では後置インクリメント/デクリメント演算子が対応する前置演算子を用いて典型的に記述できることを利用して、そのような実装をデフォルトとするdefault定義できるようにしようとしています。

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

この提案は、標準ライブラリ中で後置インクリメント/デクリメント演算子を定義している部分でこのdefault指定を使用するようにすることで、標準の規定を圧縮しようとするものです。

この提案による変更は既存の動作を変更したりするものではなく、標準の文言を簡素化することにあります。そのため、この変更は編集上のものとしてLWGのレビューにかけることを提案しています。

変更の候補は全部で53個あり、表記の揺れはあるもののデフォルト実装と一致する規定を持つものと、デフォルト実装と一致しているもののコンパイル時の分岐を伴う物や事前条件や制約を持つものの大きく2種類に分けて、その文言とともにリストアップしています。

P3787R0 Adjoints to "Enabling list-initialization for algorithms": uninitialized_fill

P2248R8での変更をuninitialized_fillにも適用する提案。

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

P2248R8では値を取るタイプのアルゴリズムにおいて、イテレータから取得した値型を利用することで{}の指定ができるようになっています。しかし、uninitialized_fillはそこから漏れていたようでその変更が適用可能であるのに適用されていないため、特にfillアルゴリズムの仕様と一致するようにuninitialized_fillも変更しようとする提案です。

int main() {
  std::array<int, 10> arr;  // 未初期化配列

  std::ranges::uninitialized_fill(arr, {}); // この提案後ok
}

P3788R0 Fixing std::complex binary operators

std::complexの二項演算において暗黙変換が考慮されないことを修正する提案。

std::complex<T>Tに暗黙変換可能な数値型から構築することができます。一方で、std::complex<T>を用いた二項演算においては数値として使用可能なのはT型そのもののみで、Tに暗黙変換可能な型を使用できません。

long double ld = 2.5;
std::complex<float> c = 3.14;           // OK!!!

auto res1 = c * 3.14f;                  // OK
auto res2 = c * 3.14;                   // DOES NOT COMPILE!!!
auto res3 = c * ld;                     // DOES NOT COMPILE!!!
auto res4 = c * static_cast<float>(ld); // OK but verbose

if (c == 3.14f) {                       // OK
  // ...
}
if (c == 3.14) {                        // DOES NOT COMPILE!!!
  // ...
}

これはstd::complex<T>の二項演算の演算子オーバーロードの定義のされ方に問題があります。例えば、二項*演算子オーバーロードは次のように定義されています。

template<class T>
constexpr complex<T> operator*(const complex<T>&, const complex<T>&);

template<class T>
constexpr complex<T> operator*(const complex<T>&, const T&);

template<class T>
constexpr complex<T> operator*(const T&, const complex<T>&);

この演算子(およびほかの二項演算子)は非メンバの非friend関数として定義されています。下二つの演算子定義において、std::complexではない値を受け取る引数の引数型がstd::complex<T>Tを直接使用してしまっていることに問題があります。このTはテンプレート引数推論の対象であり、両方のオペランドから推論した型が一致しなければなりません。その結果、Tに対してはほとんどすべての変換は行われず、std::complex<T>に対して丁度T型の値しか渡すことができません。例えば、std::complex<float>doubleを受け取れる*は存在しません。

また、両方のオペランドstd::complexである場合でも、その数値型が異なるとやはり利用できる演算子がなくなります(std::complex<double>std::complex<float>など)。

これと同じことが他の二項演算子においても起きているため、std::complexを使用する場合の使用感を損ねています。特に、std::complexは数式をコードに起こす場合に使用されることが多く、そのようなコードを動作させるためにキャストが多く必要になると元の数式と乖離することで可読性が低下します。

この提案は、これを修正してstd::complex<T>の二項演算でTに暗黙変換可能な型の値を使用できるようにしようとするものです。

後方互換性を重視して、この提案ではTを直接取っていたところをstd::complex<T>::value_typeを使用するように変更することを提案しています。

template<class T>
constexpr complex<T> operator*(const complex<T>&, const complex<T>&);

template<class T>
constexpr complex<T> operator*(const complex<T>&, const typename complex<T>::value_type&);

template<class T>
constexpr complex<T> operator*(const typename complex<T>::value_type&, const complex<T>&);

このようにすることで、下二つのオーバーロードの非std::complex引数はテンプレートパラメータの推論に寄与しなくなり、std::complex<T>引数からTが決定された後でTに対する変換が効くようになります。

これと同じことを、他の二項演算(+ - * / ==)に対しても適用します。

この変更は既存の動作しているコードに対しては影響がなく、オーバーロード解決結果も変更されません。動作していなかったコードが動作するようになります。

P3790R0 Pointer lifetime-end zap proposed solutions: Bag-of-bits pointer class

ポインタの参照先の寿命が尽きた後でも使用可能なライブラリユーティリティを提供する提案。

現在のC++のポインタの意味論においては、参照先のオブジェクトの寿命が尽きた後のポインタ値は不定となり、その使用(ポインタ値の読み出し、保存、キャスト、比較)は実質的に許可されていません。一方で、既に広く使用されている並行アルゴリズムの中にはそのような不定なポインタ値が使用できることに依存している場合があります。

この問題はPointer lifetime-end zapと呼ばれており、先行する提案によって否定的に(不定なポインタの使用を禁止する形で)解決されようとしています。その場合、現在存在しているそのような操作に依存しているアルゴリズムは未定義動作になってしまいます。

それを防止するために、この提案では これは、整数値アドレスを格納する変数の様な単純なポインタの意味論(Bag of bits)をエミュレートするライブラリ機能です。

ここで提案しているのは、launder_bag_of_bits_ptr()という関数とbag_of_bits_ptr<T>というクラステンプレートです。

launder_bag_of_bits_ptr()はポインタを受け取り、そのポインタに対応する将来のポインタ値を返す関数です。参照先の寿命が尽きているポインタはそのままだと不定値を取りますが、この関数を通すことで同じアドレスに再度配置される別の(将来の)オブジェクトのアドレスを指す有効なポインタが得られます。名前の通り、この関数はポインタのロンダリングを行います。

bag_of_bits_ptr<T>は参照先の寿命が尽きた後にも無効化しないポインタ型をエミュレートする型です。このクラステンプレートはTのポインタとして使用することができ、このクラスオブジェクトとして使用する限りポインタの参照先の寿命が尽きた後でもポインタ値が有効であり続けます。また、このクラスによるポインタはポインタのprovenanceを切ることもできます。

これらのユーティリティは、zapおよびprovenanceによって問題を受けるコードを保護するためのテクニックである、ポインタ値をreinterpret_castによって整数値に落としてから戻す(戻す際にprovenance再計算とその時点で有効なポインタの取得が行われる)という操作をラップするユーティリティです。現在不定なポインタの使用に依存しているコードでは、生ポインタを使用する代わりにこれらのユーティリティを介してポインタを使用するようにすることで、将来的にPointer lifetime-end zapが否定的に解決されprovenanceが導入された場合でも、従来の動作を維持することができます。

この提案はP2414で提案されていたライブラリ機能を単離したもので、bag_of_bits_ptrは以前はusable_ptrと呼ばれていました。

P3791R0 constexpr deterministic random

決定論的なアルゴリズムによる乱数生成器と、乱数生成器を使用するアルゴリズムconstexpr指定する提案。

この提案では決定論的なアルゴリズムによる乱数生成器、すなわち疑似乱数生成器の全てに対してconstexpr指定しようとしています。それに加えて、shufflesampleアルゴリズムconstexpr指定しようとしています。

constexpr sample_to_sketch(std::span<const T> items, sketch<T> & sk) noexcept {
    auto engine = std::mt19937_64{/* default seed is fine*/}; // この提案後ok
    std::ranges::sample(items, std::insert_iterator(sk), sk.capacity(), engine);  // この提案後ok
}

ただし、std::random_deviceなど非決定論的な乱数生成を伴うものについては対象にしていません。

この動機については他のconstexpr対応と同様に、実行時とのコード共通化や定数式でのテストを可能にすることでUB検出機能付きでテストできるようにすることなどを挙げています。

P3792R0 Why constant_wrapper is not a usable replacement for nontype

std::nontypestd::constant_wrapperで置き換えられない理由を説明する文書。

P3740R1(上の方に詳細あり)ではstd::nontypeをリネームするかstd::constant_wrapperで置き換えるかの2つのオプションを提示しており、LEWGの投票ではリネームすることに合意がありました。std::constant_wrapperはNTTPをより汎用的に取り扱うものであるため、std::nontypeを完全に置き換えられると考えるのは自然なことです。この投票結果においてはそのことがメンバーに共有された上でのものであり、この文書はそれを説明するものです。

std::constant_wrapper<V>はNTTP値Vをラップして通常のオブジェクトとして扱えるようにしたもので、std::integral_constantを汎用化したものです。std::constant_wrapperの特徴はほぼすべての演算子オーバーロードを提供することでVで可能な演算をstd::constant_wrapper上で実行できるようにしていることで、std::constant_wrapperという型を通してコンパイル時定数の計算を型システム上で簡単に行うことができます。

この演算子オーバーロードの中には関数呼び出し演算子も含まれており、次のように定義されています。

template<constexpr-param T, constexpr-param... Args>
constexpr auto operator()(this T, Args...) noexcept
  requires requires(Args...) { constant_wrapper<T::value(Args::value...)>(); }
{
  return constant_wrapper<T::value(Args::value...)>{};
}

std::cw<V>(args...)std::cw<V>std::constant_wrapper<V>の略記)の様な呼び出しは、V(args.value...)の結果を再びstd::constant_wrapperにラップしたstd::cw<V(args.value...)>を返します(std::constant_wrapper<V>::valueVにアクセスできる)。

std::cw<V>(args...)において、この関数呼び出し演算子が使用可能であるためには、V(args.value...)の呼び出しが定数式で実行可能であり、argsはすべてstd::constant_wrapper型の値(もしくは互換のある型の値)である必要があり、戻り値型がNTTPとして利用可能(戻り値型が構造的な型)である必要があります。そうでない場合、この関数呼び出し演算子は使用できません。

これによって、std::constant_wrapper自体が呼び出し可能になる場合があり得ます。それでも、std::nontypeの代わりにstd::constant_wrapperを使用することは可能であり、単にstd::constant_wrapperを受け取るコンストラクタにおいてstd::constant_wrapper<V>::valueを使用するようにするだけです。

では何が問題なのかというと、現在std::nontypeを使用しているところがstd::function_refのみであるため、std::function_refとそれ以外の関数ラッパ(std::move_only_functionなど)との間でstd::cw<f>を渡した場合の挙動が異なってしまうことです。std::function_refでは::valueからf(関数ポインタ)を取り出してそれを保持するのに対して、他の関数ラッパではstd::cw<f>そのものを呼び出し可能オブジェクトとして保持します。前述のように、std::cw<f>(args...)args...の値は静的メンバ変数valueを取り出せなければならないためいつも起こるわけではありませんが、意図しないでこの不一致が発生する可能性が生まれてしまいます。

例えば次のような呼び出し可能な型があり

static constexpr struct foo_t
{
  constexpr auto operator()(auto &&...args) const -> int
    requires(std::integral<std::remove_cvref_t<decltype(args)>> && ...)
  {
    return (0 + ... + args);
  }

  constexpr auto operator()(auto &&...args) const -> int
    requires(std::integral<
                 decltype(std::remove_cvref_t<decltype(args)>::value)> &&
             ...)
  {
    return sizeof...(args);
  }

} foo = {};

次のような型の値があったとして

static constexpr struct baz_t final
{
    static constexpr int value = 42;
} baz = {};

次のコードは動作します

auto main() -> int {
  move_only_function<int(baz_t)> fn(cw<foo>);
  assert(fn(baz) == 42);  // ✅
}

cw<foo>は構築可能であり、cw<foo>(baz_t)の呼び出しは有効でcw<foo(baz_t::value)>を返します。このため、move_only_function<int(baz_t)>への格納も行うことができ、fn(baz)の呼び出しも行うことができて、アサートはパスします。

しかし、このmove_only_functionfunction_refに変えると動作が変化します(std::nontypestd::constant_wrapperで置き換えた場合)

auto main() -> int
{
  function_ref<int(baz_t)> fn(cw<foo>);
  assert(fn(baz) == 42); // ❌ fn(baz) == 1
}

function_refにおいてはcw<foo>からの構築はfooを取り出しこれをそのまま関数呼び出し対象として利用する形になります。それによって、foo_tで定義された関数呼び出し演算子の2つ目の方が選択され(baz_t::valueがアンラップされずにそのまま渡される)るようになります。

これを防止するにはstd::nontypeを維持するか、std::constant_wrapperを取りstd::function_refと同等に動作するコンストラクタを他の関数ラッパにも追加するかのどちらかをする必要があり、前者が選ばれたのがP3740R1のLEWGでの投票の結果です。後者はP2511で提案されていたものの否決されていたこともあり、C++26ギリギリの状況では選択しづらかったものと思われます。

P3793R0 Better shifting

整数のビットシフトを行うライブラリ関数の提案。

C++のシフト演算子<< >>)は基本的にCから受け継いだものそのままで、いくつかの落とし穴が知られています

  1. 演算子の優先順位が+ -二項演算子)よりも低い
    • シフト演算が実質乗除算として機能することを考えると直感的ではない
  2. シフト量がシフト対象のビット幅以上、あるいは負の値の場合、未定義動作となる

1の問題はGCCの最適化がこのことに依存しているため変更することが難しく、2の問題は動作を定義するよりもEBにすることを好む向きがあるため議論の余地があります。この提案では特に2の問題の解決に焦点が置かれており、シフト対象の幅と同じ値を指定した場合の未定義動作を回避するために気を使わなければならない不便さなどを動機として挙げています。

この提案では、これらの問題を解決したシフト演算を提供するために、シフト演算を行うライブラリ関数を追加しようとしています。提案しているのはstd::shl()std::shr()という2つの関数です。

// <bit>に追加
namespace std {
  template<class T>
  constexpr T shl(T x, int s) noexcept;
  
  template<class T>
  constexpr T shr(T x, int s) noexcept;
}

shl(x, s)xsビット左シフトし、shr(x, s)xsビット右シフトします。ここでのシフトは算術シフトを行うため、論理シフトを行う場合はx引数を符号なし整数型にキャストする必要があります。

そして、シフト量が大きい場合のシフトについては未定義動作とせず、シフト対象のビット全てが外に押し出されることになり、論理シフト(符号なし整数型)の場合は0になり、算術シフト(符号付整数型)の場合は負の値であれば-1、そうでなければ0になります。

一方、シフト量が負の場合のシフトについては実装定義の結果を伴うEBとしています。

また、他のビット操作関数と同様にstd::simdstd::simd::vec)に対するオーバロードも用意しています。

P3794R0 An idea or two on renaming the nontype tag

std::nontypeのリネーム名の別案の提案。

C++26でNTTPという言葉が標準で使用されなくなったのを受けて、std::nontypeの名前を変更する必要があることが認識されています。そのリネーム候補としてここでは、swiftのWitness Tableと呼ばれる概念からとったstd::witnessを代わりの名前として提案しています。

LEWGにおける検討では少なくともstd::witnessは支持を得られなかったようです。

P3795R0 Miscellaneous Reflection Cleanup

リフレクション関連提案の間での文言の整合性を調整する提案。

2025年6月に行われた全体会議では、リフレクション関連の提案がほぼ同時にいくつもWDに採択されました。これらの提案はリフレクション機能に関するものではあるものの、それぞれスコープが異なるものが同時並行的に進行していたことによって、それぞれの仕様の間で不整合が生じている部分があります。この提案は、その様な問題点を修正しようとするものです。

この提案の修正は次のものです

  1. 不足している述語の追加
    • inlineconstexprなどの指定を取得するクエリ関数を追加する
      • is_inline(info) -> bool
      • is_constexpr(info) -> bool
      • is_consteval(info) -> bool
  2. スコープ識別機能の追加
    • std::meta::access_contextが追加されたことで、これを利用してある場所がどのスコープ(名前空間・クラス・関数など)なのかを判定することができるようになった
    • このような有用な情報のクエリは正式にサポートすべきなので、対応するメタ関数を追加する
  3. data_member_spec()において、型とアノテーションの指定をdata_member_optionsで指定するようにする
    • define_aggregate()において集成体型のメンバ変数を作成するためのdata_member_spec()関数では、メンバ型のリフレクションとその他プロパティを指定するオプション(data_member_options構造体)を受け取る
      • data_member_spec(info type, data_member_options options) -> info
    • 型だけを特別扱いして他のオプションと別に指定するのは奇妙なので、型の指定もdata_member_options構造体で指定する
    • また、生成したメンバに対してアノテーションを付加しておくためのオプションをdata_member_options構造体に追加する
  4. 関数引数へのアノテーションのサポート
    • P3394R4(リフレクションのためのアノテーションサポート)と、P3096R12(関数引数のリフレクション)は独立して作業されほぼ同時に採択された提案
    • そのため、関数引数へのアノテーションは検討されていなかった
    • 関数引数へのアノテーションサポートを追加する
  5. P1317R2で追加された型特性に対応するconstevalメタ関数の追加
    • P1317R2も6月の会議で採択されたが、そこで追加された3つの型特性に対応するリフレクションメタ関数は提供されていなかったため、これを追加する
      • is_applicable_type(info fn, info tuple) -> bool
      • is_nothrow_applicable_type(info fn, info tuple) -> bool
      • apply_result(info fn, info tuple) -> info
  6. エラー処理APIを一貫させる
    • P3650R2によってリフレクションのエラー報告は例外によるものになったが、P2996R13の型特性関数には適用されておらず、他の関連提案の関数にも適用されていない
    • 未適用のリフレクション関連関数のエラー報告は例外送出で一貫させる
  7. エラーの内容をより詳細に指定する
    • リフレクション関数の失敗時に例外を送出する際に、std::meta::exceptionfrom()から例外送出元関数のリフレクションを取得できることを明記する
    • リフレクション関数F()が例外を送出した場合、そのfrom()からは^^Fが取得できるようにする

この提案はNBコメント解決の一環としてC++26に向けて作業されています。

P3796R0 Coroutine Task Issues

std::taskの設計上の問題点を修正する提案。

std::taskは2025年6月の全体会議でC++26に向けて採択済みですが、その投票の前後でいくつかの問題点が指摘されていたようです。全体会議ではそれらの指摘が遅かったことやstd::taskC++26に間に合わせるためなどの理由によりそのまま投票にかけられてC++26に採択されました。

この提案はそのようなstd::taskに対する問題点とその解決案についてまとめたものです。ここで報告されているのは次のおおきく5つです

  1. affine_onについて
  2. std::taskの開始と終了の方法についての懸念
  3. アロケータの取り扱い
  4. std::stop_tokenの管理方法
  5. その他の設計上の修正

これらの項目それぞれについていくつか個別の問題があり、問題を説明するとともに一部はその解決策も提案しています。

  1. affine_onについて
    1. affine_onのデフォルト実装の仕様不足
      • -> デフォルト実装を指定する
    2. affine_onの動作が明確ではない
      • -> 明確になるようなセマンティクスを提示
        • affine_onsenderの完了は、開始された実行エージェント上でインラインで完了するか、指定されたスケジューラに関連付けられた実行エージェント上で非同期的に完了するかのいずれかとする
    3. affine_onシグネチャが改善できうる
      • -> スケジューラをreceiverの環境から取得できる可能性がある
    4. affine_onの暗黙的スケジュール操作には停止要求を伝播させない
      • コルーチンが中断したコンテキストで再開するために、affine_onの動作をキャンセルすべきではない
      • -> そのための設計選択肢を提示
    5. affine_onの暗黙的スケジュール操作には停止要求を伝播させない
      • 提案の議論時間の都合からカスタマイズについて曖昧にされているが、このアルゴリズムは他のsenderにおいてもカスタマイズが有効である可能性がある
      • -> カスタマイズをサポートする方法を規定することを推奨
  2. std::taskの開始と終了の方法についての懸念
    1. std::taskstart()時に再スケジュールすべきではない
      • std::taskの実行が関連付けられたスケジューラ出確実に行われるようにするために、サスペンドしてスケジューリングしてから再開、という手順を取る
      • 中断再開等の度にスケジューラを往復することになり効率的ではない
      • -> 再スケジュールしないような設計選択肢を提示
    2. std::taskを待機しているstd::taskは再スケジュールすべきではない
      • std::taskはその特性上開始時と同じスケジューラで完了する可能性が高いため、再スケジュールを回避できうる
      • -> その情報をクエリする方法など、そのための仕組みを整備する
    3. std::taskは対称転送をサポートしていない
      • 仕様では対称転送について一切言及がない
      • -> ここでの3つの問題合わせて、affine_onas_awaitableのカスタマイズによって解決することが望ましい
  3. アロケータの取り扱い
    1. std::generatorとアロケータカスタマイズの方法が異なる
      • std::taskの場合、環境からアロケータ型を取得してその型に合致するアロケータをコルーチン引数で受け取る
      • 環境から取得できない場合にデフォルト(std::allocator<std::byte>)以外を受け取れない(ill-formedになる)
      • これは、receiverの環境を介したget_allocatorクエリを処理するためだが、ここでのアロケータはコルーチンフレームの確保に使用されるもの
      • -> コルーチンフレームの確保に使用するアロケータについては、std::generator同様にstd::allocator_arg引数によって任意のものを指定できるようにする
    2. std::generatorstd::allocator_arg引数の位置が異なる
      • std::generatorが引数の先頭なのに対して、std::taskでは任意の位置でよい
      • -> std::taskも引数の先頭にする(任意の位置を許可するのはstd::generatorと同時に別に行う)
    3. 環境のアロケータを隠蔽しない
      • std::task下流からのget_allocatorクエリはstd::taskにおいてコルーチンフレームの確保に使用されたアロケータを返し、あとから接続されたreceiverの環境のアロケータを隠蔽してしまう
      • std::taskでもreceiverの環境のアロケータを使用する方が良い可能性が高いが、std::taskは接続前に環境型を確定してしまう(下流の操作は同じアロケータを使用すべきというコルーチンの原則による)ため、困難がある
      • -> アロケータの型消去や互換性のあるアロケータのみ転送するなど、環境のものを優先することを検討する
  4. stop_tokenの管理方法
    1. stop_sourceを常に新規作成し格納してしまう
      • std::task::promise_typeはストップトークン/ソースがデフォルトであるかに関わらずそれらを保持し、リレー的に上流の停止要求を伝播させる
      • これにはスペースと同期のコストがかかる
      • -> 接続後のoperation_stateにおいてストップトークン/ソースの型がデフォルトである場合は何も保持せず、receiverを介して上流のものを伝播させる
        • 型がデフォルトではない場合にのみ現在と同様に保持してリレーする
    2. stop_token型がデフォルト構築可能であるように扱っている
      • stop_tokenは通常stop_sourceから取得する必要があるためデフォルト構築可能ではないが、1の様に保存のためにメンバ変数を指定していることによってデフォルト構築可能性を要求(あるいは仮定)しているように見える
      • -> 1のように、stop_tokenを保存しないようにする
        • 環境から取得するか、stop_sourceから取得する
  5. その他の設計上の修正
    1. std::taskが積極的に開始される場合があるように見える(遅延実行されない場合があるように見える)
      • -> 紛らわしい文言を改善しかつより明確化することで、常に遅延実行することを明確にする
    2. コルーチンフレームの破棄が遅い
      • std::taskが中断されたコルーチンからfinal_suspendに到達することなく完了した場合、コルーチンフレームはoperation_stateオブジェクトの破棄まで保持され続ける
      • コルーチンフレームは完了関数が呼び出されてからかなり後に破棄される可能性がある
      • -> 完了関数が呼び出される前に破棄されることを指定する
    3. task<T, E>にはデフォルト引数がない
      • -> T = voidE = env<>に設定する
    4. unhandled_stopped()noexceptがない
      • -> この関数はnoexcept関数から呼び出されるため、noexcept指定する
    5. 環境の保存が非効率な場合がある
      • co_awaitで待機したsenderに接続するために使用されたreceiverget_envから返される環境は、プロミス型のstateメンバを参照して指定されているが、この中に場合によって無駄なコピーを発生させるものが入っている
      • -> 本当に必要になるまで環境を保存しないようにする
    6. 完了スケジューラがない
      • std::taskは、std::get_completion_scheduler<Tag>クエリに応答するget_envを定義していない
      • これにより、正しいスケジューラで完了している場合でも再スケジュールが必要になる場合がある
      • -> std::taskは完了スケジューラを知れるのはstd::taskの処理が完了した後であり、get_completion_schedulerが有用な場面はなさそう。将来必要になったら再検討する 7. 待機可能な非sender型がサポートされない
      • await_transform()オーバーロードsenderを満たす引数を必要とするように制約されており、senderではない待機可能な型はstd::taskのコルーチンに内で待機できない
      • -> await_transform()の制約を緩和して、サポートを拡大する
    7. std::task::promise_typewith_awaitable_sendersを使用していないこと
      • with_awaitable_sendersはコルーチンのpromise_typeの基底クラスとして使用して、senderco_await可能にするためのものです
      • しかし、その対象の第一号のはずのstd::taskはいくつかの理由からこれを使用しておらず、with_awaitable_sendersの設計の再検討が必要かもしれない
      • -> 少なくとも、std::taskのプロミス型の仕様のunhandled_stoppedの文言において、set_stopped完了が正しいスケジューラで呼び出されることを指定していない問題は修正すべき
    8. エラー発生時のco_yieldを回避できる実装を取れる可能性
      • std::taskでは、例外を送出せずにエラー発生時に完了させるにはco_yield with_error(x)を使用するが、co_yieldは通常コルーチンの途中の処理であるため、利用者にとって驚きがある可能性がある
      • -> 現在提案中の機能を活用することでより優れたエラーハンドリングを行える可能性があるので、それを考慮しておく
    9. TLSを退避・復帰する機能がない
      • TLSへアクセスしている既存のコードの移行時に、コルーチンの中断とそのスレッドで別のコルーチンの再開が起きたり、std::taskがスレッドプール上で実行していた場合に必ずしも同じスレッドに戻れないなど、移行の問題が起こりうる
      • sender/receiverではreceiverの環境をこの目的で使用することが推奨されているが、既存のTLSを使用しているプログラムからの移行時にはTLSを中断時に退避し再開時に復帰する機能が必要
      • -> affine_onのカスタマイズによって対応できる

affine_on(sndr, sch)continues_on(sndr, sch)とほぼ同じ動作をしますが、sndrの処理がsch上で完了することが分かっている場合に追加のスケジューリングを回避するように動作することを意図するものです。これはstd::taskと一緒に導入されているsenderアルゴリズムです。コルーチンの中断と再開を正しく取り扱うためには、コルーチンが中断したのと同じ実行コンテキストで再開をする必要があり、そのためにcontinues_onが使用できますが、affine_onを使用することでその操作の最適化を図ることができます。

affine_onstd::taskに必須のアルゴリズムとしてstd::taskと同時に導入されているものの、その動作の詳細や最適化・カスタマイズの方法などの仕様がかなりぼかして記述されており明確ではありません。これはstd::taskC++26に間に合わせるための時間制約によるものとみられており、それによってaffine_onに関連して問題が多く指摘されています(それだけではないですが)。

P3798R0 The unexpected in std::expected

std::expected.has_error()を追加する提案。

std::expectedstd::optionalと共通するインターフェースを基本としており、オブジェクトが正常値を保持しているかどうかを調べるために.has_value()もしくはboolへの変換演算子を使用できます。

一方で、std::expectedstd::optionalとは異なり値もしくはエラー値を保持するものであるため、そのエラー状態の側に特化した独自のインターフェースを持つことで、エラー処理に重点を置く場合により自然に記述することができるようになります。

この提案は、そのために.has_error()メンバ関数を追加しようとするものです。

.has_error()はその名の通り.has_value()の逆の動作をするもので、エラー状態の場合にtrueを返します。

現在 この提案
if (!result.has_value()) {
  log_and_exit(result.error());
}
if (result.has_error()) {
  log_and_exit(result.error());
}
現在 この提案
// somewhere in unit tests
ASSERT_TRUE(!result.has_value());
EXPECT_EQ(result.error(), "myError");
// somewhere in unit tests
ASSERT_TRUE(result.has_error());
EXPECT_EQ(result.error(), "myError");

P3799R0 2025-07 Library Evolution Polls

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

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

P3125R5はC++29導入を目指して、P3778R0はC++26DRとするために、LWGに転送するための投票です。

P3801R0 Concerns about the design of std::execution::task

std::execution::taskC++26での採用を再考することを促す提案。

P3796R0とも共通するものですが、こちらにはstd::execution::task(P3552R3)が採択されるまでの過程が詳しく書かれています。

それによれば、P3552R3はC++26 feature freeze期限(おそらくLWGの期限、2月頃)に間に合わなかったものの、期限後にLEWGとLWG共同のスピードレビューによって2025年6月の全体会議にかけられ、異議申し立てはあったもののC++26に採択されました。

しかし、このような例外的な経路を辿っているにもかかわらず必要な手続きが取られておらず、P3552R0の提出も2025年に入ってからと遅かったため議論が尽くされていない可能性があります。実際著者の方も、2025年6月の会議直前までP3552R3がC++26向けに検討されていることに気づいていなかったため問題点の指摘が遅れたとしています。

この提案はそのような拙速なプロセスへの非難と、std::execution::task設計の問題点について指摘し、C++26から取り下げることを提案するものです。

  • 重大な懸念事項
    1. 対称転送をサポートしていないこと
    2. taskのコルーチンフレームの破棄が遅い
      • コルーチン引数とローカル変数の破棄が、taskoperation_stateの破棄まで延期される
      • |でつないだパイプライン全体が終了するまで、コルーチン状態が破棄されない
    3. ダングリング参照への対策の欠如
      • コルーチン引数に参照を渡すと、それが浅くコピーされ(参照のままコピーされ)コルーチンフレームに格納されることで、参照の生存期間に注意を払う必要がある
      • 言語の改善には時間がかかるが、ライブラリソリューションがすでに存在しているため、対応すべき
  • その他の懸念事項
    1. co_yield with_error(x)というエラー処理の方法
      • 直観的ではないため、言語を修正してco_returnを使用できるようにすべき
    2. co_await ex::schedule(sch)という高コストなno-op
      • これは通常のsenderチェーンであればスケジューラを変更することを意味するが、taskにおいては異なる
        • 正確には、change_coroutine_schedulerを使用する
      • 1の変更によってco_yieldを解放し、co_yield schのようにして簡易にスケジューラ変更をできるようにする
    3. コルーチンのキャンセルがアドホック
      • C++20コルーチンはそのキャンセルをサポートしていないため、std::executionではコルーチンのプロミス型を拡張してunhandled_stoppedというカスタマイズポイントを設けた
      • 言語そのものを拡張して、コルーチンのキャンセルをネイティブにサポートすべき
        • これにより、対称転送をサポート可能にもなる

おおくはP3796R0での指摘と共通しています。

その他の懸念事項についてはC++26に間に合わせる必要はないものの、時間的余裕があれば解決が可能だった可能性があるとしています。

P3552R3については設計の検討不足をその著者の方も認めているようで、問題はNBコメントを通して解決することを意図していたようです。この提案はそれに反対しており、問題があることが分かっていたのならば無理に間に合わせるべきではなかったとして、C++26へのP3552R3採択を再検討することを要求しています。

P3802R0 Poor Functions

呼び出されている場所のコンテキストに依存する関数から、そのコンテキスト依存性を分離する提案。

このような関数の代表は、std::source_location::current()です。

namespace std { 

  struct source_location { 
    static consteval source_location current() noexcept; 
    ... 
  }; 
} 

宣言だけを見ればこれは普通の静的関数であり、その他の関数にラップしても動作するように見えます。

consteval auto curr_loc() { 
  return std::source_location::current(); 
}

しかし実際にはこれは意図通りに機能しません。std::source_location::current()コンパイル時のグローバル状態や呼び出しが行われるローカルコンテキストに依存した結果を取得するため、関数にラップしたものを呼び出すとラップしない場合と結果が異なります。

using srcloc = std::source_location; 

consteval auto number(srcloc loc = srcloc::current()) { 
  return loc.line(); 
}

auto r = number(); 

したがって、このコードにおいてsrcloc::current()の呼び出しをcurr_loc()の呼び出しに置き換えることはできません。これは、srcloc::current()の呼び出しはそれが出現するソースロケーションコンテキストを取得するものの、デフォルト引数として使用されている場合はそのデフォルト引数が使用されるコンテキストを取得するためです。

上記curr_loc()の様な関数においてこのような振る舞いを再現する方法はありません。

std::source_location::current()はこのような振る舞いを持つ標準内で最初の関数でしたが、C++26ではstd::meta::access_context::current()という関数が同様の振る舞いを持つものとして新しく追加されています。また、同種の関数の提案が潜在的に存在しているようです。

この提案では、これらの関数について暗黙的なローカルコンテキストに依存するのではなく、ローカルコンテキストを明示的にし、それに依存する通常の関数として定義するようにすることを提案しています。

namespace std { 
  struct source_location { 
    static consteval source_location current(std::meta::info = __local_ctx) noexcept; 
    ... 
  };
}

この__local_ctxというのはキーワードによって指定される言語組み込みのもので、現在std::source_location::current()が特別に持っているローカルコンテキスト情報を表す値です。

これを用いると、curr_loc()は次のように書き直すことができます

consteval auto curr_loc(std::meta::info c = __local_ctx) { 
  return std::source_location::current(c); 
}

これにより、curr_loc()std::source_location::current()のような関数をラップしながらもその望ましい動作を達成することができます。そして、元来std::source_location::current()が持っていたような魔法的な性質は__local_ctxに分離されることで、同種の関数を普通の関数として定義することができるようになります(std::source_location::current()の性質はユーザー定義関数では完全に再現できない部分があり、特別な規定を必要としている)。

__local_ctxは当初は実装依存のものとして導入しておき、将来必要になった際に標準構文へ昇格させることを提案しています。また、型としてはstd::meta::infoが完全に適切としています。

おわり

この記事のMarkdownソース

[C++]C++26 Contracts

大遅刻しましたが、これはC++ Advent Calendar 2025の22日目の記事です。

C++26では契約プログラミング機能が使用可能になります。この記事はその機能の解説を行うものです。なお、契約プログラミングそのものやその意義などについてはここでは解説しませんので、各自で調べてください。

契約アサーション

C++26 Contracts機能によって、契約プログラミング(契約による設計)の主要概念である事前条件と事後条件に対応するアサーションとその他のアサーションを専用の構文によってコード中に配置し、関数契約をC++コードの一部として記述できるようになります。これらのアサーションのことを契約アサーション(contract assertions)と呼びます。

契約アサーションには前述の事前条件・事後条件・その他アサーションに対応して3種類のアサーションがあります。

3種類のアサーションは基本的には同じ使用法になり、()の中には契約条件を表す式を指定します

int f(const int x)
  pre (x != 1)  // 事前条件アサーション
  post(r : r == x && r != 2) // 事後条件アサーション
{
  contract_assert(x != 3); // アサーション文

  return x;
}

事前条件と事後条件アサーションは関数の宣言の直後(requires節が存在する場合、その後)に対して付加し、関数本体など他の場所には指定できません。contract_assert()は逆にpre()/post()の場所には配置できず、文(statement)の一種として関数本体内などの文が配置できる場所に配置することができます。

関数ヘッドの直後(requires節が存在する場合、その後)、(定義がある場合は)本体ブロックの直前、が事前条件と事後条件アサーションを配置する専用の構文スペースになります。ここでは、pre()post()を空白区切りで任意の個数配置することができます。pre()post()の出現順は任意であり、両方あってもどちらか片方だけしかなくてもokです。一方で、他のものはここには配置できません。

int f1(int x) pre(x != 1) pre(x != 2) pre(x != 3) ... pre(x != 1000) ...  // ok
{
  ...
}

int f2(const int x) post(x != 1) post(x < 0) pre(x == 0) post(r: r != x)  // ok
{
  ...
}

void f3(int x) contract_assert(x != 0)  // ng、ここに配置できるのはpre()とpost()のみ
{
  ...
}

template<typename T>
void f(const T& x) requires std::integral<T> pre(x != 0) post(0 < x)  // ok
{
  ...
}

関数に対する事前・事後条件アサーションの指定はフリー関数に限定されたものではなく、メンバ関数でも使用可能です。

template<typename T>
struct S {
  T t;

  int f1(int x) pre(x == 0) post(r: x != 0) // ok
  {
    ...
  }

  auto f2() const & noexcept [[attribute]] -> T requires std::integral<T> pre(t != 0) post(r: r < T(0));  // ok

  int f3(this S& self) pre(self.t == 0) post(r: r < 0); // ok
};

事後条件アサーションだけは関数の戻り値をキャプチャするための専用の構文スペースが用意されており、post(ret: expr)の様に条件式exprの前のretの部分に戻り値を指すように任意の名前を指定できます。戻り値を表すこの名前は続くexpr内で使用することができ、値としてはその関数の戻り値が取得できます。戻り値を使用しない場合は事前条件アサーションなどと同じように書くことができます。

事後条件アサーションにおけるこの結果名の使用はその性質上、戻り値の無い(void戻り値型の)関数では使用できません。

void f()
  post(r: ...)  // ng、戻り値の無い関数で結果名を使用できない
  post(...)     // ok、結果名を使用しない事後条件は指定可能
{
  ...
}

事後条件アサーションにおけるこの結果名は、事後条件アサーションが関連付けられている関数の戻り値のオブジェクト/参照を表す束縛(result binding)であり、言語参照(T&)とは異なる形の参照の様な名前になります(これは構造化束縛において導入される各束縛の名前と同じ扱いのものです)。そして、結果名の式としての値カテゴリ(decltype(r))はconst左辺値(const T&Tは戻り値型)となります。

戻り値への参照ではなく束縛としていることによって、return文におけるRVO等の影響を受けないようにしており、結果名はそれらの適用された関数戻り値を直接示しています。

なお、pre/postoverride等と同じ文脈依存キーワードであり、contract_assertはキーワードとして新規追加されています。そのため、pre/postは他の名前として使用できる一方、contract_assertは使用できません。

int pre(const int x)
  pre(x != 1)           // ok
  post(post: post == x) // ok
{
  int contract_assert;  // ng、これはダメ

  ...
}

以下、この記事では事前条件アサーションと事後条件アサーションの事を単に事前条件と事後条件と呼びます。

契約アサーションの述語(契約条件式)

アサーションの種類によらず、契約アサーションには何かしら契約条件を表現する条件式を指定する必要があります。この条件式の事を述語(predicate)と呼びます。この述語というのはアルゴリズムの文脈などで使用される言葉と同じ意味です。

述語は文脈的にbool変換が可能(contextually converted to bool)な式である必要がある他は任意のC++式(文法上のconditional-expression)を記述することができます。すなわち、ifや条件演算子の条件式として使用できるものが使用できます。ただし、後述するconst化の違いがあります。

void f1(int* ptr) pre(ptr)  // ok、ptr != nullptr と等価
{}

void f2(std::optional<int> opt) pre(opt)  // ok、opt.has_value() と等価
{}

契約アサーションに述語を指定しないでおく、ということはできません、

void f(int n)
  pre()   // ng、条件式が必要
  post()  // ng、条件式が必要
{
  contract_assert();  // ng、条件式が必要
}

以下、この記事ではこの述語の事を契約条件式と呼ぶことにします。

const

3種類いずれの契約アサーションにおいても、その契約条件式内から参照される外部の変数はすべて、暗黙的にconst化されます。

このconst化は契約条件式の振る舞いがC++の他の部分とかなり異なる点です。これは、契約条件式の評価に伴う副作用の発生を防止するための措置の一つです。

契約条件式には文脈的にbool変換が可能であれば任意の式を記述することができます。(const化を除いて)それ以外に特に制限は無いため、その条件式を起点として任意のC++コードを実行することができます。プログラムの任意時点の状態をチェックするための契約アサーションからプログラムの状態を変更することは通常間違った行いであるため、これをなるべく防止するためにこのconst化の措置が取られています。

void f() {
  std::vector<bool> vec{true, false};

  contract_assert(vec.emplace_back(true) == true);  // ng、vecはconst
}  

このconst化は契約条件式の外部の変数の使用すべてに対して作用します。関数引数、ローカル変数、グローバル/名前空間スコープの変数、static/thread_local変数、NTTPや構造化束縛、そしてthisおよび*thisまで、あらゆる外部の変数はconst化されます。事後条件における結果名の値カテゴリがconst左辺値なのもconst化の一部です。

int n = 0;
struct X { bool m(); };

struct Y {
  int z = 0;

  void f(int i, int* p, int& r, X x, X* px)
    pre (++n)     // ng、const左辺値を変更しようとしている(グローバル変数
    pre (++i)     // ng、const左辺値を変更しようとしている
    pre (++(*p))  // ok、ポインタの参照先には伝播しない
    pre (++r)     // ng、const左辺値を変更しようとしている
    pre (x.m())   // ng、非constメンバ関数呼び出し
    pre (px->m()) // ok、ポインタの参照先には伝播しない 
    pre ([=, &i, *this] mutable {
      ++n;        // ng、const左辺値を変更しようとしている
      ++i;        // ng、const左辺値を変更しようとしている
      ++p;        // ok、コピーキャプチャされたクロージャ型のメンバの変更(ポインタ
      ++r;        // ok、コピーキャプチャされたクロージャ型のメンバの変更(非参照
      ++this->z;  // ok、コピーキャプチャされた*thisの変更
      ++z;        // ok、コピーキャプチャされた*thisの変更
      int j = 17;

      [&]{
        int k = 34;
        ++i; // ng、const左辺値を変更しようとしている
        ++j; // ok
        ++k; // ok
      }();

      return true;
    }());

  template <int N, int& R, int* P>
  void g()
    pre(++N)      // ng、prvalueを変更しようとしている
    pre(++R)      // ng、const左辺値を変更しようとしている
    pre(++(*P));  // ok、ポインタの参照先には伝播しない

  int h()
    post(r : ++r) // ng、const左辺値を変更しようとしている
    post(r: [=] mutable {
      ++r;  // ok、コピーキャプチャされたクロージャ型のメンバの変更
      return true;
    }());

  int& k()
    post(r : ++r); // ng、const左辺値を変更しようとしている
};

契約条件式には任意の式を使用できるため、当然ラムダ式が使用できます。契約条件式内ラムダ式内部で宣言された変数(コピーキャプチャも含めて)にはこのconst化は適用されません。

また、このconst化は浅いもので、深く伝播することはありません、例えば、外部のポインタを使用しているときそのポインタそのものはconst化されていますが(T* const)、ポインタの参照先(*ptr/ptr->~)までは伝播しません。

仮にconst化を解除したい場合は、const_castを使用します。

struct S {
  int m_resource;
  std::mutex m_mtx;

  // 非constのステータスチェック
  bool cond() {
    std::lock_guard _{m_mtx};

    return m_resource != 0;
  }

  int f()
    pre(this->cond())                 // ng、非constメンバ関数呼び出し
    pre(const_cast<S*>(this)->cond()) // ok
  {}
}

事後条件における関数引数の使用

事後条件から関数引数を使用することは良くありそうですが、その場合に使用する引数は参照であるか、そうでないならconst宣言されていなければなりません。非参照の場合、これは暗黙const化とは関係なく明示的にconstと宣言されている必要があります。

void f(int a1, const int a2, int& a3)
  post(a1 == 0) // ng、事後条件からの非参照非const引数の使用
  post(a2 == 0) // ok、const引数
  post(a3 == 0) // ok、参照引数

これは、事後条件から使用される非参照非const関数引数について、どの時点の値をキャプチャすべきか?という問題を回避するためのものです。

例えば次のようなライブラリ関数があるとします

// ユーザーが見る宣言
int generate(int lo, int hi)
  pre (lo <= h)
  post(r: lo <= r && r <= hi);

この宣言からは、事後条件によって引数lo, hiに対して戻り値が[lo, hi]の範囲内に収まっていることが明確に読み取れます。しかしこの関数の実装が次のようになっていたとしたらどうでしょうか

// 開発者が見る定義
int generate(int lo, int hi)
  pre (lo <= h)
  post(r: lo <= r && r <= hi) // 事後条件からの引数使用に制限がないとする
{
  int result = lo;

  while (++lo <= hi) // loが更新される
  {
    if (further())
      ++result;      // loよりもゆっくりとインクリメントされる
  }
  return result;
}

この関数は値渡しで引数を変更しています。これにより、この定義から見る事後条件の意味は全く違ったものになっています。

関数の非参照引数を変更するというのは正気ならやらないとは思われますが、言語が暗黙的にこれを行う場合があります。

std::string g(std::string p)
  post (r: starts_with(p))  // 事後条件からの引数使用に制限がないとする
{
  return p; // 引数の暗黙ムーブ
}

関数引数は暗黙ムーブの対象であるため、事後条件でそのような関数引数を使用すると意図通りに動作しなくなります。

このように、関数定義内で引数が変更されていると、関数宣言の契約アサーション(特に事後条件)のみから戻り値に対する推論を行うことができなくなります。このことは事前条件では問題にならず(変更前であるため)、事後条件でのみ問題になります。また、引数が参照である(変更されるものであるため)かconstである場合(変更できないため)も問題になりません。

これを防止するために、事後条件から使用できる関数引数は参照もしくは非参照のconstなものに限定されています。

宣言と定義が分かれている場合

C++の関数はその宣言と定義を分けて記述することができるほか、宣言だけなら何度も再宣言することができます。この場合にも契約アサーション(事前条件と事後条件アサーション)を指定することはできますが、少し注意が必要です。

関数に対して事前条件と事後条件アサーションを指定する場合、翻訳単位におけるその関数の最初の宣言に対して指定しておかなければなりません。そして、同じ翻訳単位内で続く再宣言は事前条件と事後条件アサーションの指定について次のどちらかを順守する必要があります

一つの翻訳単位内でこれが満たされない場合、コンパイルエラーとなります。

// 最初の宣言
int f(const int x)
  pre (x != 1)
  post(r : r == x && r != 2);

// 有効な再宣言: 事前/事後条件アサーションを持たない
int f(const int x);

// 有効な再宣言: 同一の事前/事後条件アサーションを持つ
int f(const int x)
  pre (x != 1)
  post(r : r == x && r != 2);

// 有効な再宣言(定義): 事前/事後条件アサーションを持たない
int f(const int x)
{
  contract_assert(x != 3);  // contract_assertは関係ない

  return x;
}

// 不正な再宣言: 異なる事前/事後条件アサーションを持つ
int f(const int x)
  pre (x != 1);     // ng

// 不正な再宣言: 異なる事前/事後条件アサーションを持つ
int f(const int x)
  pre (0 < x)
  post(r: r == 0);  // ng

後の宣言で事前条件と事後条件アサーションが指定されていない場合、暗黙的に最初の宣言の事前条件と事後条件アサーションと同一のものが指定されているものとして扱われます。

最初の宣言に事前条件と事後条件アサーションが指定されていない場合、あとの宣言で追加することはできません。

// 契約アサーションを持たない関数宣言
int f(const int x);

// 不正な再宣言:  事前/事後条件アサーションが追加された
int f(const int x)
  pre (x != 1)
  post(r : r == x && r != 2); // ng
{
  contract_assert(x != 3);

  return x;
}

翻訳単位が複数ある場合、ある関数に対応する最初の宣言は翻訳単位毎に1つづつ存在しえます(このような状況はヘッダファイルに契約アサーションを持つ関数の宣言を記述して、そのヘッダを複数のソースファイルでインクルードした時に起こります)。ある関数に対応するそのような複数の最初の宣言の間で同一の事前条件と事後条件アサーションが指定されていない場合、診断不要のill-formed(IFNDR)となります。

/// 翻訳単位1におけるf()の宣言
int f(const int x)
  pre (x != 1)
  post(r : r == x && r != 2);

/// 翻訳単位2におけるf()の宣言
int f(const int x)
  pre (x != 1)
  post(r : r == x && r != 2); // ok

/// 翻訳単位3におけるf()の宣言
int f(const int x); // IFNDR、契約アサーションの指定がない

IFNDRは診断されない、すなわちコンパイルエラーとなることを要求されていないため、未定義動作となります。これはODR違反が起きているのとほぼ同じことです。ここでの翻訳単位には共有ライブラリの様なものも当然含まれます。

これらのルールを理解したうえで問題となるのは、事前条件と事後条件アサーションの同一性の定義です。ある2つの事前/事後条件アサーションのシーケンスは次の場合に同一とみなされます

  1. 同一の契約アサーション指定(1つのpre(...)/post(...))が
  2. 同じ順序で指定されている

この一つの契約アサーション指定の同一性は、ODRの意味での同一です。これは、関数宣言d1d2がありそれぞれ契約アサーション指定c1c2(このc1. c2は単一のpre(...)/post(...))を持っているとき、その述語p1p2は、宣言d1d2に対応する仮想的な関数本体に配置されコンパイルされたときにODRに違反しない、場合にODRの意味で同一となります。すなわち、この時に仮想的なd1, d2の定義が異なっていない状態の場合に、契約アサーション指定c1c2は同一となります。

このODRの同一性をより具体的に言えば、この仮想的なd1, d2がビット単位で全く同じ機械語コードにコンパイルされてなおかつ同じ動作となる場合、がODRの意味で同一と言えます(最適化の事は考えないでください)。したがって、異なる翻訳単位にある時でも、同じ関数に指定された事前/事後条件アサーションが参照する変数や関数等は同じものにならなければなりません。

一方で、関数引数の名前やテンプレートパラメータ名、事後条件における結果名、などはここでのODR同一性においては考慮されないため、異なっていても問題ありません(これは通常の関数のODRよりも緩いルールです。通常のinline関数や関数テンプレートでは、字句的に同一であることが求められます)。

/// 翻訳単位1におけるf()の宣言
int f(const int x)
  pre (x != 1)
  post(r : r == x && r != 2);
  
/// 翻訳単位2におけるf()の宣言
int f(const int y)
  pre (y != 1)
  post(b : b == y && b != 2); // ok
  
/// 翻訳単位3
static int x = 0;

/// 翻訳単位3におけるf()の宣言
int f(const int a)
  pre (x != 1)
  post(r : r == x && r != 2); // IFNDR、翻訳単位1と2の契約アサーションと同一ではない
  
/// 翻訳単位4におけるf()の宣言
int f(const int y)
  post(b : b == y && b != 2)
  pre (y != 1);               // IFNDR、順番が異なる

この同一性の違反は翻訳単位が同じであればチェックされてコンパイルエラーとなります。

事前条件と事後条件アサーションにおけるラムダ式の使用

ラムダ式がリンケージを必要とする場所(関数宣言付近)に現れる場合、そのクロージャ型は必ず固有のものになるというルールがあります。これと契約アサーション指定の同一性のルールによって、事前条件と事後条件アサーションラムダ式が使用されている場合は同じ翻訳単位内で全く同じ文字列によって再宣言していたとしても(仮に契約アサーション指定を省略したとしても)、同一性を満たすことができません

// 同じ翻訳単位内にあっても
void f() pre([]{ return true; }()); // ok、最初の宣言
void f() pre([]{ return true; }()); // ng、最初の宣言の契約アサーションと同一ではない
void f();                           // ng、最初の宣言の契約アサーションと同一ではない

このような場合でも再宣言をしなければ事前条件と事後条件アサーションラムダ式を使用することに問題はありません。

ラムダ式のこのような性質についてはC++20 P0351R4のこの解説を参照してください

事後条件での関数引数の使用

事後条件で関数引数を使用するにはその引数が非参照ならconstである必要があるというのはもう説明したことです。これは単に事後条件が存在する場合のみconstにしておけばいいのではなく、すべての宣言においてconstでなければなりません。通常の関数宣言では、値引数のconst有無は関数宣言のマッチングにおいて考慮されないため、少し注意が必要です。

void f1(int n);
void f1(const int n); // ok、f1()の再宣言

void f1(const int n) post(r: r == n);
void f1(int n);   // ng、事後条件で使用している引数が(非参照)非const

再宣言において事前条件と事後条件アサーションが省略された場合、最初の宣言と同一の契約アサーション指定がなされているとみなされるため、後の宣言でも事後条件で使用している引数にconstが必要です。

これはここまでで懇々と説明してきたODR同一性とは少し関係がないことですが、契約アサーション付き関数の再宣言においては注意すべきことです。

評価セマンティクス

ここまでで、C++ Contractsにおける契約アサーションはどのように記述できるか、ということはなんとなくわかったと思います。すると気になってくることは、この契約アサーションはどう振舞うのか?ということでしょう。

もちろん、契約アサーションは書いて終わりではなく、通常のコードと同様にその記述の正しさなどが検証され、実行時にチェックするようにすることもできます。契約アサーションがどのようにふるまうのか、すなわち契約アサーションのセマンティクスは、大きく評価(条件式のチェック、実行)と評価の後に起こることの2つに分割されて定義されており、どのように評価されるのかについての事をContractsの評価セマンティクスと呼びます。

4つのセマンティクス

C++ Contractsの評価セマンティクスは単一ではありません。評価するかしないかやどう評価するのか、評価してどうするのかについてはプログラムによって様々な要求・要件があります。言語標準の機能としてそれらの要求をなるべく受け止められるようにするために、評価セマンティクスは固定ではなくいくつか規定されたセマンティクスから選択して適用可能なようになっています。

C++26時点では、4つの評価セマンティクスが標準で規定されています。

  1. ignore
  2. observe
  3. enforce
  4. quick enforce

これに加えて、実装が実装定義のセマンティクスを追加しても良いことになっています。

これらのセマンティクスは主に、契約条件式を評価(チェック)するかしないか、評価した後に契約違反ハンドラを呼び出すか出さないか、契約違反が起きた後に実行を継続するかどうかが異なっています。

C++26時点の各セマンティクスの違いは表にして一覧できます

セマンティクス チェック 違反ハンドラ 違反後継続
ignore しない 呼び出さない する
observe する 呼び出す する(条件付)
enforce する 呼び出す しない(条件付)
quick enforce する 呼び出さない しない

表のチェック列の意味は、プログラムの実行が契約アサーションに到達した時にそこに指定された契約条件式を評価するかどうかです。チェックするセマンティクスでは契約条件式が評価されてその結果がbool値として取得されます。契約条件式がfalseを返した場合、その契約アサーションの表明する契約が破られている状態であり、これを契約違反(contract violation(が発生した状態)と呼びます。

違反ハンドラ列の意味は、契約条件式が評価されて契約違反が発生した後に、契約違反ハンドラと呼ばれるコールバック処理を呼び出すかどうかです。呼び出すセマンティクスでは契約違反発生後に違反ハンドラ(専用のライブラリ関数)を起動して、契約違反をハンドリングします。この違反ハンドラについては次の章で詳しく解説します。

違反後継続列の意味は、契約違反が発生した後でもプログラムの実行を継続するかどうかです。observeenforceセマンティクスでは違反後に違反ハンドラが呼ばれてさらにその後に継続するかどうかです。しないセマンティクスでは契約違反が起こるとプログラムが終了されます。

observeenforceセマンティクスの継続に関する条件とは、違反ハンドラの呼び出しが正常にリターンするかどうかであり、これらのセマンティクスにおいて違反ハンドラの呼び出し後の振る舞いは次のようになります

  • observe : 違反ハンドラが正常にリターンした場合、違反を起こした契約アサーションの直後から元の実行パスを再開する
    • 違反ハンドラがリターンしなければ、元の実行パスの再開はされない
  • enforce : 違反ハンドラが正常にリターンした場合、プログラムを終了する
    • 違反ハンドラがリターンしなければ、プログラムは終了されない

observeenforceのデフォルトの継続動作はそれぞれするとしないですが、違反ハンドラがリターンするかどうかによってこの動作をオーバーライドできます。これについては次章で詳しく説明します。

ignoreセマンティクスはチェックを行わないため契約違反が発生しないので違反ハンドラ呼び出しもプログラム終了も行われません。そのため実際に契約違反が発生していたとしてもそれを検出することはできません。しかし、これにより契約アサーションの評価によるオーバーヘッドをゼロにすることができます。なお、契約アサーションignoreセマンティクスでコンパイルし実行される場合でも契約条件式のコンパイルは行われています(これはassertマクロのセマンティクスとは異なる点です)。

observeセマンティクスは契約違反をロギングしながらプログラムの実行をなるべく優先するもので、レガシーなコードからの移行期に選択するセマンティクスとなることを目的とするものです。ただし、契約違反が発生した状態でプログラムの実行を継続すると早晩未定義動作に突入するため安全なセマンティクスとは言えない側面があります(これはignoreセマンティクスも同様)。

enforceセマンティクスは契約違反をロギングしつつも違反時に終了することでバグの影響を最小にとどめることを目的とするものです。契約違反が発生したら速やかにプログラムは終了されるため、未定義動作に陥ることがないという意味で安全なセマンティクスでもあります。このため、enforceセマンティクスはデフォルトの評価セマンティクスとしておくことが推奨されています。

observeenforceセマンティクスでは契約違反時の診断情報(違反が発生した場所などの情報)が提供されます(これは推奨事項ではあります)。

quick enforceセマンティクスは違反ハンドラ呼び出しを伴わず、チェックして違反が発生すると即プログラムを終了させます。これは契約アサーションによって生成されるコードサイズを最小限に抑えるとともに、違反が起きたらなるべく速やかにプログラムを終了させることを目的としています。quick enforceでは契約違反に関する診断情報が提供されないか最小限のものになる可能性があります。

プログラムの終了方法

単純にプログラムを終了させるといっても、その方法には色々な方法があります。

enforce/quick enforceセマンティクスにおける終了方法の選択は実装定義とされつつも、次の3つの方法のいずれかによって行われます

  1. std::terminate()の呼び出し
  2. std::abort()の呼び出し
  3. プログラム実行の速やかな終了

3が選択される場合は結局その方法は実装定義となるのですが、このような自由度は実行環境とコンパイラにおける様々なユースケースに対応するためのものです。

例えば、std::terminate()はプログラムを終了させると言いつつもstd::set_terminate()で登録されたハンドラを呼び出してからstd::abortを呼び出すなど少し時間がかかります。std::abort()でもSIGABRTに対応するシグナルハンドラが登録されているとそちらが呼び出されてしまいます(これで終了が阻まれることは無いとは思いますが)。

3の選択肢は、そもそもstd::abort()のような機構でさえも利用できない環境であったり、セキュリティを重視して言葉通り即座にプログラムを終了させるオプションを実装が提供することに対応するためのものです。

例えば、Clangは__builtin_trap()__builtin_verbose_trap()(デバッガのアタッチに対応している)といった余計なことをほとんど一切行わずに即座にプログラムを終了させる方法を提供しています。quick enforceセマンティクスの場合はこうした実装を取ることがその意義からしても合理的です。

また、このプログラム終了の方法はセマンティクス毎に異なることができ、同じセマンティクスであっても状況によって異なることが許可されています(後の方の「契約条件式の評価中の例外」のところで少し触れています)。

評価セマンティクスの選択

契約アサーションのデフォルトのセマンティクスとしてはenforceセマンティクスが標準で推奨されていますが、セマンティクスを選択する方法は実装定義とされています。

通常これはコンパイラオプションを通じてコンパイル時に一括で制御することが想定されていますが、より柔軟にリンク時(異なる設定によってコンパイルされた翻訳単位の結合時)・ロード時(設定ファイルや環境変数など)・実行時(外部のデバッガの指定など)などに変更できるようにすることもサポートされています。

全ての契約アサーションのセマンティクスを一括で指定するようなコンパイラオプションはほぼ確実に提供されるため、通常はそれを使用することになると思われます。

評価セマンティクスの選択方法の自由度に対応して、ある契約アサーションの評価にどの評価セマンティクスが選択されるかも実装定義とされます。特に、この評価セマンティクスの選択は契約アサーション毎に異なっていても良く、さらに同じ契約アサーションでも評価ごとに異なるセマンティクスを選択することも許可されています。

例えば、Clangの試験実装では-fcontract-evaluation-semanticというオプションによって指定することができ、さらに-fcontract-group-evaluation-semanticというオプションで別途属性で指定したグループごとに制御する方法を提供しています。

評価セマンティクスは4つありましたが、実装は4つの評価セマンティクスの全てをサポートしていなくても良いとされています。

契約アサーションの評価タイミングと評価回数

チェックされる評価セマンティクスにおいて、契約アサーションの契約条件式の評価のタイミングは厳密に指定されています。

  • 事前条件(pre()) : 関数呼び出し時、関数引数の初期化後、関数本体の実行前
  • 事後条件(post()) : 戻り値が初期化され、ローカル変数がすべて破棄された後、関数引数の破棄の前
  • アサーション文(contract_assert()) : 制御フローがその文に到達した時

ある関数に対して事前条件または事後条件が複数指定されている場合、事前条件と事後条件それぞれの評価の内側で、個別の事前条件/事後条件は宣言順(指定された順序)で評価されます。

int f(const int n, int& out)  // (0) : 関数引数初期化
                              // (10) : 関数引数破棄
  pre(n != 0)     // (1)
  post(0 < out)   // (2)
  pre(n < 100)    // (3)
  post(r: n < r)  // (4)
{
  int m = 10; // (5)

  out = g(n, m);

  contract_assert(out != 0);  // (6)
  contract_assert(0 < m);     // (7)

  return m + n; // (8)
} // (9) : ローカル変数破棄

この例において、関数f()が呼び出されるとまず、事前条件((1)(3))が次の順序で評価されます

(0) => (1) => (3) => (5)

次に関数本体が評価され

(5) => (6) => (7) => (8)

最後に事後条件((2)(4))が評価されます

(8) => (9) => (2) => (4) => (10)

ここでの評価とは評価セマンティクスによる選択の前の契約アサーションそのものの評価のことです。各契約アサーションがこの順で評価されていく際に、ignoreセマンティクスが選択されている契約アサーションではその契約条件式の評価がスキップされます。以下、この節では評価という言葉を専らこの意味で使用しています。契約アサーションの評価とその条件式の評価の違いに注意してください。

コンストラクタ/デストラクタやコルーチンなどにおいては少し異なる点があるものの、この評価順序には自由度はほぼありません。例えば同じ契約アサーションのシーケンス(事前条件や事後条件の集まり)内で順番を入れ替えて宣言順を逸脱することはできません。

ただ、このような評価の順序とは別にそれぞれの契約アサーションが何回評価されるのかは実装定義となります。

ある関数について、次の3種類の順次評価される(evaluated in sequence)契約アサーションの集合が形成されます

  1. 関数に指定されている事前条件アサーション
  2. 関数に指定されている事後条件アサーション
  3. 関数本体内で連続しているアサーション

1と2はある関数に最大1つづつしかありませんが、3は1つの関数に対して複数存在しえます。

先に述べたような評価順序で評価が行われる一方で、このような集合の1つの評価中には、そこに含まれる評価済みの契約アサーションを、実装定義の回数まで任意の評価セマンティクスで再評価することが許可されます。すなわち、契約アサーションは1回以上評価され、契約条件式は0回以上評価されます。そして、この上限は必ずしも1ではありません。

void f(int i)
{
  // この2つのアサーション文によって1つの順次評価される集合が形成される
  contract_assert(i > 0);   // #1
  contract_assert(i < 10);  // #2

  // この集合においては次の評価順序と回数は有効(合法)
  // #1 => #2
  // #1 => #1 => #2 => #2
  // #1 => #2 => #1 => #2
  // #1 => #2 => #2 => #1
  
  // 次の評価順序と回数は無効(合法ではない)
  // #2 => #1
  // #1
}
void f(int *p)
  pre( p != nullptr ) // precondition #1
  pre( *p > 0 );      // precondition #2

// 順次評価される集合は{#1 #2}
// この集合においては次の評価順序と回数は有効(合法)
// #1 => #2
// #1 => #1 => #2
// #1 => #2 => #1 => #2
// #1 => #2 => #2 => #1 

// 次の評価順序と回数は無効(合法ではない)
// #2 => #1
// #2 => #2
// #1
// #1 => #1

推奨事項として、コンパイラはこのような契約アサーションの評価の回数を指定するオプションを提供することと、デフォルトの動作は各契約アサーションが正確に1回だけ評価されるようにしておくことが推奨されています。

このような複数回の評価が起こる場合の分かりやすく合理的な例は、事前条件/事後条件を持つ関数が共有ライブラリで定義されている場合です。契約アサーションの指定に関して前述のように最初の宣言に指定されている必要があることから、ヘッダファイル経由でそのような関数に対する事前条件と事後条件は呼び出し側(共有ライブラリ使用者)でも呼び出される側(共有ライブラリ内部)でも同じ契約アサーションが見えています。その場合に呼び出し側と呼び出される側の両方の翻訳単位がチェックされるセマンティクス(例えばenforce)を指定してコンパイルされているとします。

この時、実装の取りえる評価方法として、呼び出し側と呼び出される側の双方で契約アサーションの評価およびその条件式の評価を行うという方法があります。この場合、事前条件は関数呼び出し直前に、呼び出し側と呼び出される側の両方で2回評価され、事後条件も同様に2回評価されます。

/// ヘッダファイル
int f(int *p)
  pre( p != nullptr ) // #1
  pre( *p > 0 )       // #2
  post(r: 0 < r)      // #3
  post(r: r < 100);   // #4

/// 共有ライブラリcpp
#include "header.hpp"

int f(int *p) {
  // 関数契約に違反しない定義とする
  ...
}

/// 共有ライブラリ利用側cpp
#include "header.hpp"

int main() {
  int n = 100;

  int r = f(&n);
  // 呼び出し側と呼び出される側の両方で契約アサーションを評価する場合
  // #1 #2 |翻訳単位の壁| #1 #2 f() #3 #4 |翻訳単位の壁| #3 #4
  // の順序で契約アサーションが評価される
}

このような評価方法は想定され許可されている物であり、なおかつ合理的なものでもあります。リンク時やロード時のセマンティクス選択がサポートされている場合、このような評価を削減できる可能性はありますが、それをサポートするかどうかやどうサポートするかは実装定義となるため、このように評価される可能性を排除するには実装がどうなっているかを調べる必要があります。

それ以外に繰り返し評価が発生するケースとして、アサーションのテストのためにあえて複数回試行する評価方法が挙げられています。

const化と合わせてこの契約アサーションの評価回数の不定性によっても、契約条件式が副作用を伴いまたそれに依存するような使い方はC++26 Contractsの使い方としては間違っている物であり、推奨されません。

さらに、コンパイラがある契約条件式についてその評価結果がtrueになるかfalseになるかを証明できる場合、コンパイラはその式を評価する代わりにその結果を使用することが許可されています。別の言い方をすると、その契約条件式(副作用をもちうる)と同じ結果になる副作用の無い式を代わりに使用して契約条件式の評価を行うことができます。

このような契約条件式の置換は契約条件式の一部分ではなく全体に対して行われるため、契約条件式の持つ副作用はすべてが起こるか全く起こらないかのどちらかになります。

このことからも、契約アサーションの契約条件式には副作用を含めるべきではありません。

定数式における契約アサーション

契約アサーションの評価は定数式でも使用可能です。そこでも4つの評価セマンティクスのいずれかが適用されることになるのですが、少しだけ実行時と異なる点があります。

まず、コンパイル時には契約違反ハンドラというものがありません。このため、enforcequick enforceは同じ意味になります。そして、違反後継続しない(プログラム終了する)セマンティクスにおけるプログラム終了は、コンパイル時においてはコンパイルエラーを意味します。

また、契約違反の定義も変わっており、定数式において契約違反とみなされるのは

  • 契約条件式がfalseに評価された
  • 契約条件式が定数式で実行できなかった

の2つの場合です。特に、後者はコンパイル時特有の違反となります。

これらのことを踏まえて先ほどの表を書き直すと次のようになります

セマンティクス チェック 違反時
ignore しない 何もしない
observe する 診断出力
enforce する コンパイルエラー
quick enforce する コンパイルエラー

ignoreセマンティクスは実行時と同様に定数式でも何もしないセマンティクスとなります。ただし、指定された契約条件式がコンパイルされないわけではありません(定数式中で評価されないだけ)。

observeセマンティクスも実行時と同様に定数式では違反を検出したらそれについての診断メッセージを発行して実行を継続します。定数式においては未定義動作の心配がないため安全ではあります。また、診断メッセージは警告として扱われます。

enforcequick enforceは前述のようにコンパイル時には同じ意味になり、契約違反を検出するとコンパイルエラーを発生させてコンパイルを停止させます。この時、コンパイルエラーメッセージの一環としてどのアサーションで違反が起きたかなどの診断情報が出力されるはずです。

このコンパイル時の評価セマンティクスの選択に関しても実行時同様実装定義となりますが、特筆すべき点として、同じ契約アサーションが実行時とコンパイル時で異なるセマンティクスによって評価されることが許可されています。対応して、コンパイラは実行時とコンパイル時で異なるセマンティクス指定フラグを提供することができます。

例えば、コンパイル時はenforceセマンティクスで評価し(バグをなるべく検出)、実行時はignoreセマンティクスで評価し実行する(オーバヘッド最小化)、という選択は合理的なものとなりえます。

契約アサーションとコンセプト/SFINAE

定数式における契約違反の定義に契約条件式が定数式で実行できない場合というのが含まれているのは、式が定数式で実行できるかどうかによってSFINAEすることを防止するためです。

C++26 Contractsの設計原則の中には「コンセプトは契約を認識しない」というものがあり、契約アサーションの存在が(コンセプトだけではなく)コンパイル時のプロパティに影響を与えないように設計されています。

そのため、契約アサーションの存在あるいはその評価セマンティクスの違いによって

  • コンセプトが満たされるかどうか
  • SFINAEの結果
  • オーバーロード解決の結果
  • if constexprの分岐結果
  • noexcept演算子の結果

が変化することはありません。

ある関数に契約アサーションを追加してもその周辺でのコンパイル時のプロパティは追加前と変化せず、評価セマンティクスの選択が変わった時でも変化せず、契約アサーションを取り除いても変化することはありません。

ここでは詳細は説明しませんが、これを徹底するために定数初期化ができるかどうかの判定および定数初期化式の実行に際して少し変わったことをしています(P2900R14 3.5.12 Constant Evaluation で詳しく説明されています)。

契約違反ハンドラ

契約アサーションがどのように評価されるのかは評価セマンティクスによって定義され、制御されることが分かりました。次は、評価された後、特にobserveenforceの2つのセマンティクスにおいて契約違反が起きた場合にどう振舞うかの部分を見ていきます。

この評価後のセマンティクスは、契約違反ハンドラ(contract-violation handlerというものによって定義されています。契約違反ハンドラはグローバルに単一かつ共通の契約違反ハンドリング機構であり、契約違反時のセマンティクスをプログラム中で統一的に制御します。

以下この記事では契約違反ハンドラの事を単に違反ハンドラと呼びます。また、前述のように違反ハンドラが考慮されるのは実行時のみなので、ここで説明することは基本的に実行時の振る舞いについてです。

違反ハンドラの呼び出し

実行時の契約アサーション評価において契約違反が検出された場合、そのセマンティクスによって行われることは次のようになっていました

  • ignore : 契約違反は検出されない(のでそのまま継続)
  • observe : 違反ハンドラを呼び出す
  • enforce : 違反ハンドラを呼び出す
  • quick enforce : プログラム終了

observeenforceセマンティクスでは契約違反時に違反ハンドラを呼び出して処理を委譲します。違反ハンドラはグローバルにインストールされた専用の関数であり、::handle_contract_violation()という名前で次のようなシグネチャを持ちます

// 違反ハンドラのシグネチャ(グローバル名前空間で定義)
extern "C++" {
  void handle_contract_violation(const std::contracts::contract_violation&) noexcept(/*実装定義*/);
}

このシグネチャはあくまで説明のためのものです。また、この関数はグローバルモジュールに属し、C++言語リンケージを持ちます。

この関数の動作の詳細(定義)は実装定義とされますが、引数に提供されたstd::contracts::contract_violation型オブジェクトの持つ契約違反に関する情報を診断情報として出力する実装をデフォルトとすることが推奨されています。

違反ハンドラはかなり特殊な関数であり、::handle_contract_violation()という名前を持つことが規定されてはいるもののユーザーがアクセス可能ではないことが規定されています。仮にこの名前でアクセス可能だったとしても、std::contracts::contract_violation型のオブジェクトを構築する方法が提供されていないため、結局呼び出すことはできません。

observeenforceセマンティクスによる契約アサーションの評価によって契約違反が発生した場合、実装はその契約違反に関する情報を保持するstd::contracts::contract_violation型オブジェクトを何らかの手段で作成し、それを渡して違反ハンドラを呼び出します。このstd::contracts::contract_violation型オブジェクトの作成方法は未規定(実装定義)ですが、少なくともnewを使用して動的メモリ確保を行わない方法によるものであることは指定されています。

デフォルトの違反ハンドラはこのように受け取ったstd::contracts::contract_violation型オブジェクトから契約違反に関する情報を取得して、何らかフォーマットされた診断情報を標準出力などに出力してから正常に終了(リターン)します(するはずです)。違反ハンドラがリターンした後でどうするかは前述のようにセマンティクスによってことなります

  • observe : 違反を起こした契約アサーションの直後から元の実行パスを再開する
  • enforce : プログラムを終了する

このような過程から明らかかもしれませんが、違反ハンドラはシグナルハンドラのようなものではなく、通常の制御フローの延長で呼び出されるものです。あるスレッドで契約アサーションが評価されて違反ハンドラが呼ばれた場合、そのスレッドの実行は違反ハンドラ内に移り、observeセマンティクスの場合は違反ハンドラが正常にリターンすると違反が起きた契約注釈の直後から元のプログラムが再開されます。

違反ハンドラは契約違反時の処理を一元的に担い、管理することを目的としています。そのため、プログラム中のどの場所で契約違反が発生したとしても同じ違反ハンドラ呼び出されます。

違反ハンドラの置換

違反ハンドラはユーザーによって上書きして、ユーザー定義の任意の違反ハンドラをインストールする事ができます。ただし、これがサポートされるかは実装定義です。

サポートされる場合、違反ハンドラのインストールはグローバルnew/delete演算子の置換と同じ方法によって行います。つまり、::handle_contract_violation()関数を同じシグネチャでユーザー定義することで行います。以下、違反ハンドラのインストールの事を置換と言います

// 違反ハンドラのシグネチャ(グローバル名前空間で定義)
void handle_contract_violation(const std::contracts::contract_violation&) noexcept(/*自由*/);

noexcept指定に関しては自由度があり、noexcept(true)noexcept(false)のどちらを指定しても置換することができます。属性の指定([[noreturn]]など)も自由なほか、違反ハンドラ自体に事前条件と事後条件を指定することもできます。

// 有効な違反ハンドラ宣言の例

void handle_contract_violation(const std::contracts::contract_violation&) noexcept {
  ...
}

[[noreturn]]
void handle_contract_violation(const std::contracts::contract_violation&) noexcept(false) {
  ...
}

void handle_contract_violation(const std::contracts::contract_violation&)
  pre(...)
  post(...)
{
  ...
}
// 無効な宣言の例

// 戻り値は返せない
int handle_contract_violation(const std::contracts::contract_violation&) {
  ...
}

// 引数型はconst参照固定
void handle_contract_violation(std::contracts::contract_violation&) {
  ...
}

// 引数型はconst std::contracts::contract_violation&でなければならない
void handle_contract_violation(const int&) {
  ...
}

// これもダメ(多分
void handle_contract_violation(const auto&) {
  ...
}

// inlineであってはならない
inline void handle_contract_violation(const std::contracts::contract_violation&) {
  ...
}

// C++リンケージを持つ必要がある 
extern "C" inline void handle_contract_violation(const std::contracts::contract_violation&) {
  ...
}

正しいシグネチャで置換した違反ハンドラはリンク時にデフォルトの違反ハンドラからの置換が行われ、実行時にはデフォルトの違反ハンドラと同様にそのプログラム中で一貫して使用されます。

そして、置換するユーザー定義の違反ハンドラ内では通常のC++コードによって違反時処理を記述することができます。どのようにログ出力するのかや何か追加の処理をするのかなどを自由に記述できます。

observeenforceセマンティクスではプログラムを終了するか継続するかについて、違反ハンドラが正常にリターン刷るかどうかによって条件付けされていました。

  • observe : 違反ハンドラが正常にリターンした場合、違反を起こした契約アサーションの直後から元の実行パスを再開する
    • 違反ハンドラがリターンしなければ、元の実行パスの再開はされない
  • enforce : 違反ハンドラが正常にリターンした場合、プログラムを終了する
    • 違反ハンドラがリターンしなければ、プログラムは終了されない

ユーザー定義の違反ハンドラでは「違反ハンドラがリターンしなければ」の場合を明示的に選択することができます。

すなわち、ユーザー定義違反ハンドラの動作によって、observe/enforceセマンティクスにおけるプログラム継続/終了という最後の動作をカスタマイズすることができます。具体的には、例外を投げて契約違反を起こした処理から脱出したり、あるいは違反ハンドラ内で無限ループに入れておくことで契約違反を起こした処理を続行せずにプログラム全体の動作を継続させる、といった実装を取ることができます。

#include <contracts>

void handle_contract_violation(const std::contracts::contract_violation& info) noexcept(false) {
  throw make_my_contract_violation_exception(info); // 独自の例外を送出してプログラムのより上位の部分でハンドルする
}

void handle_contract_violation(const std::contracts::contract_violation& info) noexcept {
  for(;;) std::this_thread::yield();  // 無限ループに入れることで元の処理を続行させず、プログラムを終了もさせない
}

contract_violationクラスも含めて、違反ハンドラ関連のユーティリティ(std::contracts名前空間のもの)は<contracts>ヘッダで定義されています。契約アサーションを使用するだけならこのヘッダをインクルードする必要はありませんが、違反ハンドラをユーザー定義しようとするとヘッダのインクルードが必要になります。

ユーザー定義違反ハンドラから例外を用いて脱出する場合、enforceセマンティクスではプログラムの終了は行われなくなります。例外を用いずとも、違反ハンドラをリターンしなければenforceセマンティクスにおいてはプログラムの終了はされなくなります。

このような実装において、デフォルトの違反ハンドラの行う診断メッセージ発行を再現したい場合があるでしょう。その場合のために、デフォルトの違反ハンドラを呼び出すためのライブラリ関数std::contracts::invoke_default_contract_violation_handler()が用意されています。

#include <contracts>

void handle_contract_violation(const std::contracts::contract_violation& info) noexcept(false) {
  // ログ出力してから例外送出
  std::contracts::invoke_default_contract_violation_handler(info);
  throw make_my_contract_violation_exception(info);
}

std::contracts::invoke_default_contract_violation_handler()に違反ハンドラ引数のcontract_violationオブジェクトを渡して呼び出すことで、違反ハンドラが置換されている場合でもデフォルトの違反ハンドラを呼び出すことができます。

違反ハンドラに提供されるcontract_violationオブジェクトは、違反ハンドラの置換に関わらず同じ方法で作成され供給されます(これをカスタマイズする方法は提供されません)。その生存期間については未規定ですが、少なくとも違反ハンドラが実行を完了するまでの間は生存していることが保証され、またこれはcontract_violationオブジェクトを介してアクセス可能な全てのオブジェクトについても保証されます。

上記のように例外で脱出する場合、contract_violationオブジェクトの参照を保存してもいつまで正しく使用できるかは分からないので注意しましょう。上記の例の場合、make_my_contract_violation_exception関数/クラスコンストラクタ内で情報を取り出すだけにとどめておくのが無難です。

このような違反ハンドラの置換は共有ライブラリが動的リンクされている場合でも動作するはずではありますが、どのようにサポートされるかはnew/delete同様に実装定義となります。特に、共有ライブラリと利用側とで異なるユーザー定義違反ハンドラの置換が行われている場合、おそらくODR違反で未定義動作になります。

std::contracts::contract_violation

違反ハンドラの引数であるstd::contracts::contract_violationクラスは<contracts>ヘッダで定義されているライブラリクラスです。違反ハンドラではこのクラスを介して契約違反に関連した情報を取得することができます。

まず初めにですが、このクラスはユーザーによって構築することができません。デフォルトコンストラクタもムーブコンストラクタも代入演算子もすべて利用できません。基本的に実装が作成して違反ハンドラに渡してきた参照を介して利用するのみです。この性質のため、このクラスオブジェクトを使用する場所は実行時の違反ハンドラ内部に限られます。

std::contracts::contract_violationクラスはおおむね次のようになっています

namespace std::contracts {
  class contract_violation {
    // no user-accessible constructor
  public:
    // コピー/ムーブ禁止
    contract_violation(const contract_violation&) = delete;
    contract_violation& operator=(const contract_violation&) = delete;

    // デストラクタはvirtualであってもよい
    /*実装定義*/ ~contract_violation();

    // 契約違反を起こした述語のテキスト表現を取得する
    const char* comment() const noexcept;

    // 契約違反の種類を取得する
    contracts::detection_mode detection_mode() const noexcept;
    
    // 現在の呼び出しの違反ハンドラの終了後に終了するかどうかを取得する
    bool is_terminating() const noexcept;
    
    // 違反ハンドラを呼び出した契約アサーションの種類を取得する
    assertion_kind kind() const noexcept;
    
    // 契約違反を起こした契約アサーションのソースコード情報を取得する
    source_location location() const noexcept;
    
    // 契約違反を起こした契約アサーションの評価セマンティクスを取得する
    evaluation_semantic semantic() const noexcept;
  };

  // デフォルトの違反ハンドラを呼び出す
  void invoke_default_contract_violation_handler(const contract_violation&);
}

detection_modeassertion_kindevaluation_semanticは独自の列挙型であり、その定義は次のようになっています

namespace std::contracts {

  // 契約アサーションの種類を表す
  enum class assertion_kind : /*未規定*/ {
    pre = 1,
    post = 2,
    assert = 3  // contract_assertに対応
  };

  // 評価セマンティクスの種類を表す
  enum class evaluation_semantic : /*未規定*/ {
    ignore = 1,
    observe = 2,
    enforce = 3,
    quick_enforce = 4
  };

  // 契約違反の種類を表す
  enum class detection_mode : /*未規定*/ {
    predicate_false = 1,
    evaluation_exception = 2
  };
}

コメントにも簡単に記載していますが、std::contracts::contract_violationクラスの各メンバ関数は次のようなものです

  • .comment() : 契約違反を起こした契約条件式を文字列化したものが取得できる
  • .detection_mode() : 違反ハンドラを呼び出した契約違反の種類を表す列挙値が取得できる
  • .is_terminating() : 現在の違反ハンドラを正常にリターンしたらプログラムが終了されるかどうかが取得できる
    • 標準の4つの評価セマンティクスの中では、enforceセマンティクスの場合のみtrueを返す
  • .kind() : 契約違反を起こした契約アサーションの種類を表す列挙値が取得できる
    • pre()/post()/contract_assert()の3種類に対応している
  • .location() : 契約違反を起こした契約アサーションソースコード情報が取得できる
    • 可能な場合、契約違反を起こしたのが事前条件であるならば、返されるソースコード情報は関数呼び出しを行った場所に関するものになる
  • .semantic() : 契約違反を起こした契約アサーションの評価セマンティクスの種類を表す列挙値が取得できる

自明かもしれませんが、これらのメンバ関数によって得られるのは現在の違反ハンドラ(を呼び出した契約違反)に関するものです。ある違反ハンドラの呼び出し時のcontract_violationオブジェクトが、その呼び出しとは無関係な別の契約違反に関する情報を持つことはありません。

#include <contracts>
#include <print>

// ユーザー定義違反ハンドラにおけるcontract_violationの利用例
void handle_contract_violation(const std::contracts::contract_violation& info) noexcept(false) {
  using namespace std::contracts;

  const auto loc = info.location();

  std::println("{} In function '{}'", loc.file_name(), loc.function_name());
  std::print("{}:{}:{}: contruct violation occured", loc.file_name(), loc.line(), loc.column());
  std::println(" [assertion={}, semantic={}]", info.kind(), info.semantic());
  std::println("{}", info.comment());

  if (info.is_terminating()) {
    throw make_my_contract_violation_exception(info);
  }
}

.comment()/.location()関数の返す値は正確には実装定義とされており、上記で説明したような値を返すことは推奨事項となっています。これは、セキュリティが重要なプログラムやリバースエンジニアリング防止を意図するプログラムにおいて、リリースバイナリにオリジナルソースに関する情報を残さないようにすることをサポートするためです。その場合、空文字やデフォルト構築されたsource_locationオブジェクトが返されます。

なお、contract_violationクラスも含めた<contracts>ヘッダ全体はフリースタンディング環境でも利用可能です。ここまで明確にはしていなかったかもしれませんが、Contracts機能そのものが実行環境を問わずに利用可能であることが強く意図されています。

契約条件式の評価中の例外

契約違反の種類を表す列挙型contracts::detection_modeにしれっとevaluation_exceptionという値があるのに気づいたかもしれません。ここまで触れていませんでしたが、実行時の契約違反の定義は次のようになっています

  • 契約条件式がfalseを返した
  • 契約条件式の評価中に例外が送出された

すなわち、違反ハンドラは契約条件式がfalseを返す以外にも、契約条件式が例外を送出した場合にもその例外をハンドリングする形で呼び出されます。

bool condition() noexcept(false);

void f() pre(condition());

int main() {
  // condition()が例外を送出すると契約違反が発生する
  // observe/enforセマンティクスの場合、違反ハンドラが呼び出される
  // quick enforceセマンティクスの場合、プログラムが終了される
  f();
}

quick enforceセマンティクスの場合でも契約条件式からの例外送出は契約違反として扱われ、即座にプログラムが終了されます。quick enforceセマンティクスでは例えば、暗黙のnoexceptコンテキスト中で契約条件式を評価することで例外送出時に直ちにstd::terminateにつなげることでtry-catchを省略するという実装が許可されています。

observe/enforceセマンティクスで評価された場合に違反ハンドラでハンドルされた例外は違反ハンドラによって処理されたものとして扱われるため、違反ハンドラが正常にリターンした後に再スローされることはありません。例外オブジェクトは違反ハンドラ終了とともに破棄され、observeセマンティクスの場合は通常通りに実行を継続します。

ユーザー定義違反ハンドラ内では、契約違反を起こした例外オブジェクトをstd::current_exception()などによって取得することができます。

#include <contracts>
#include <exception>

void handle_contract_violation(const std::contracts::contract_violation& info) noexcept(false) {
  using namespace std::contracts;

  if (info.detection_mode() == detection_mode::evaluation_exception) {
    // 独自にハンドルする
    my::handle(std::current_exception());

    // あるいは再スローする
    std::rethrow_exception();
  }
}

contract_violationクラスの.detection_mode()による契約違反原因の識別はこのために提供されています。

違反ハンドラからの例外送出とnoexcept

少なくとも、ユーザー定義の違反ハンドラはほぼ自由に例外を送出することができます。この違反ハンドラの送出する例外の扱いは次のようになります

  • 違反ハンドラが送出した例外は契約アサーションが指定されている関数本体から送出されたものとして扱われる
  • 関数がnoexcept指定されている場合、通常通りstd::terminateで終了する

事前条件と事後条件でも、そこからの例外はその関数内から投げられたものとして扱われるため、その関数がnoexcept指定されているとstd::terminateで終了します。

これにより、契約アサーションが存在する場合は常にそこから例外が送出される可能性が生じることになります。これについては標準化の過程でも議論の的ではありましたが、関数にnoexceptを付加するかどうかについてはLakos Ruleに従っておくことをお勧めしておきます(すなわち、何らかの契約を持つ関数は無条件noexceptすべきではありません)。

一応、contract_assertは現在のところ文であるため、noexcept(contract_assert(false))のようなクエリはできません。

契約アサーション評価の疑似コード

ここまで説明してきた契約アサーションの評価から違反ハンドラのリターンによるプログラム終了/続行の過程に関しては、通常のC++を用いた疑似コードで記述することができるので、参考にここに載せておきます。

// 評価セマンティクスの取得
evaluation_semantic _semantic = __current_semantic();

if (evaluation_semantic::ignore == _semantic) {
  // ignoreセマンティクスの場合
  // なにもしない
} else if (evaluation_semantic::observe == _semantic
        || evaluation_semantic::enforce == _semantic
        || evaluation_semantic::quick_enforce == _semantic)
{
  // 契約チェックを行うセマンティクスの場合

  if consteval {
    // 定数式における契約チェック
    ...
  } else {
    // 契約条件式の結果を保存する変数
    bool _violation;
    // 違反ハンドラを呼び出すべきかを表す変数
    bool _handled = false; // Violation handler has been invoked.

    // 契約条件式のチェックと違反ハンドラの呼び出し(必要な場合)
    try {
      // 契約条件式の評価
      _violation = __check_predicate(X);
    } catch (...) {
      // 契約条件式の評価が例外を送出した場合
      if (evaluation_semantic::quick_enforce == _semantic) {
        // quick enforceセマンティクスの終了処理
        std::terminate(); // implementation−defined program termination
      } else {
        // 例外送出による契約違反ハンドリング
        _violation = true;

        // 契約違反ハンドラの呼び出し
        __handle_contract_violation(_semantic, detection_mode::evaluation_exception);

        _handled = true;
      }
    }

    if (_violation && evaluation_semantic::quick_enforce == _semantic) {
      // quick enforceセマンティクスの終了処理
      __builtin_trap(); // implementation−defined program termination
    }
    if (_violation && !_handled) {
      // 契約違反ハンドラの呼び出し
      __handle_contract_violation(_semantic, detection_mode::predicate_false);
    }
    if (_violation && evaluation_semantic::enforce == _semantic) {
      // enforceセマンティクスの終了処理
      std::abort(); // implementation−defined program termination
    }
  }
} else {
  // その他実装定義のセマンティクスの処理
}

実際に契約アサーションごとにこのようなコードが挿入されるわけではなく、細部をどう実装するかについても実装の自由度がありますが、起こることを理解するのには役立つと思われます。

特殊な関数等に対する契約アサーションの扱い

ここまででC++26 Contractsの基本的な解説は完了しました。ここまでの例はほぼすべて普通の関数で例示してきているため、普通ではない関数と契約アサーションがどのように相互作用するのかをここで見ていきます。ここの章での契約アサーションとは、ほぼ事前条件と事後条件の事です。contract_assertは文が書ける場所には書くことができて、その外側の関数がなんであるかに関わらず使用することができます。

仮想関数

C++26 Contractsでは仮想関数に対する契約アサーションの指定は禁止されています。

元になった提案であるP2900ではかなり直前のリビジョンまで残っていたのですが、継承時契約アサーションの継承とその評価方法について合意を得ることができず、C++29以降の機能として検討していくためにC++26では禁止されました。

ラムダ式

ラムダ式に対しても契約アサーションを指定できます。指定する場所も通常の関数同様本体の直前、requiresの後です。

[](const int x) -> int
  pre (x != 1)
  post(r : r == x && r != 2)
{
  contract_assert(x != 3);

  return x;
};

このように指定された事前条件と事後条件は、クロージャ型の関数呼び出し演算子operator())に指定されたものとして扱われます。そのほかのルールも通常の関数とほぼ同様になります。

ラムダ式特有の注意点は、契約アサーションからラムダ式外部の変数を参照しているときでも、それが暗黙キャプチャをトリガーしないことです。したがって、契約アサーションだけで使用されており、ラムダ式内の他の部分で使用されていない外部変数の使用はコンパイルエラーとなります。

static int i = 0;

void test() {
  auto f1 = [=] pre(i > 0) {  // ok、グローバル変数の参照(キャプチャ不要)
  };

  int i = 1;

  auto f2 = [=] pre(i > 0) { // ng、iを暗黙キャプチャできない
  };

  auto f3 = [i] pre(i > 0) { // ok、iは明示的にキャプチャされている
  };

  auto f4 = [=] {
    contract_assert(i > 0); // ng、iを暗黙キャプチャできない
  };

  auto f5 = [=] {
    contract_assert(i > 0); // ok、iは契約アサーション以外で暗黙キャプチャされている
    (void)i;
  };

  auto f6 = [=]
    pre([]{
        bool x = true;
        return [=]{ return x; }(); // ok、xは暗黙キャプチャされている
      }()
    ){
  };
}

ラムダ式の契約アサーションから外部の変数を参照しキャプチャが必要な場合は、明示的にキャプチャするか、ラムダ式の他のところで参照するようにしましょう。

これは、契約アサーションの存在がクロージャ型のコンパイル時プロパティに影響を与えないようにするためです。

コンストラクタ/デストラクタ

コンストラクタ/デストラクタに対しても契約アサーション(事前条件と事後条件)を指定できます。ただし、通常の関数とは異なり、コンストラクタ/デストラクタはクラスのメンバの初期化と破棄を担うものであるため、評価のタイミング等について注意すべき部分があります。

特に、コンストラクタの事前条件およびデストラクタの事後条件の評価タイミングは次のようになります

  1. コンストラクタの事前条件 : コンストラクタ引数の初期化後、すべての非静的メンバが初期化される前
    • 関数tryブロックやメンバ初期化子リストを含めた関数本体の評価前
  2. デストラクタの事後条件 : すべてのサブオブジェクト(基底クラスおよび非静的メンバ)が破棄された後

したがって、これらの場所の契約アサーションからそのクラスのメンバにアクセスすると未定義動作となります。特に、派生クラスの一部としてコンストラクタ/デストラクタが実行されている場合、派生クラスオブジェクトは初期化前/破棄後でありいずれにせよ生存期間外にあるため、dynamic_casttypeid等動的型に依存する処理も未定義義動作となります。

これらの場所の契約アサーションにおけるメンバアクセスリスクを軽減するために、これらの場所では非静的メンバ名を直接使用できないようになっています。どうしてもアクセスしたい場合はthis->を明示することでアクセス可能ですが、この場合でも安全に使用可能なのはそのアドレスくらいです。

struct X {
  int i = 0;

  X()
    pre (i == 0)          // ng、メンバ変数名を使用できない
    pre (this->i == 0)    // UB、メンバ変数は初期化前
    pre (check(&this->i)) // ok、メンバ変数のアドレスは使用可能
    post (i == 0)         // ok、コンストラクタ事後条件は通常通り
  {}

  void f()
    pre (i == 0)    // OK
    post (i == 0);  // OK

  ~X()
    pre (i == 0)              // ok、デストラクタ事前条件は通常通り
    post (i == 0)             // ng、メンバ変数名を使用できない
    post (this->i == 0)       // UB、メンバ変数は破棄済
    post (check(&this->i));   // ok、メンバ変数のアドレスは使用可能
};

これ以外の部分は通常の関数の契約アサーションと同じです。

default/deleteな関数

ほとんどの場合コンストラクタやデストラクタが該当しますが、最初の宣言でdefault指定されている関数に対する事前条件/事後条件の指定は禁止されています。

struct X {
  X() pre (true) = default; // ng、defaultな関数にpre()を指定できない
};

struct Y {
  Y() pre (true); // ok
};


Y::Y() pre (true) = default;  // ok、最初の宣言ではない(pre(true)は省略可能

これは特殊メンバ関数トリビアル性と契約アサーションの評価で矛盾が生じることを回避するためです。とはいえ、最初の宣言でなければ指定できてしまうようですが・・・

これと同様に、delete指定されている関数宣言に対する事前条件/事後条件の指定も禁止されています。

struct X {
  X() pre (true) = delete; // ng
};

こちらはコンストラクタやデストラクタだけでなく通常の関数でもです。

コルーチン

コルーチンにも契約アサーション(事前条件と事後条件)を指定することができます。ただし、それはコルーチンそのもの(サスペンド時やその解除時、コルーチンの終了時など)に対するものではなく、ランプ関数と呼ばれるコルーチンを起動する関数に対する契約になります。

コルーチンのランプ関数とは、コルーチンになっている関数を呼び出した際にまず呼び出される、コルーチンを起動するための関数です。このランプ関数はコルーチンステートを初期化してコルーチン起動して最初のサスペンド地点まで進めてから、その関数の戻り値型として表示されているコルーチン型(コルーチンハンドルを保持している型)のオブジェクトを返して終了します。

コルーチンに対する事前条件と事後条件はこのランプ関数に対する事前条件と事後条件となります。したがって、コルーチンに対する事前条件と事後条件はユーザーが記述したコルーチン処理のco_yieldco_returnに適用されるわけではありません。

// sequenceはコルーチンだとする
std::generator<int> sequence(int from, int to)
  pre (from <= to);

// このようなコルーチンとその契約は次のように書き換えた関数に対する契約アサーションとほぼ等しい

// コルーチン実装詳細
std::generator<int> sequence_impl(int from, int to);

// 非コルーチン、疑似ランプ関数
std::generator<int> sequence_ramp(int from, int to)
  pre (from <= to)
{
  return sequence_impl(std::move(from), std::move(to));
}

この例のコルーチンsequence()には事前条件が指定されていますが、それは通常の関数によって書きかえた後のsequence_ramp()の様な関数に対して同じ契約アサーションを指定している場合と同じように扱われます(あくまでこれはイメージです)。

コルーチンに対する事前条件と事後条件の評価の順序は次のようになります

  • 事前条件 : 関数引数の初期化後、コルーチンステートの初期化処理の前
    • 特に、関数引数がコルーチンステートに格納される前
  • 事後条件 : コルーチン戻り値のreturn
    • ランプ関数のreturn
    • コルーチン状態内のあらゆるものの破棄との間での順序は不定

コルーチンの事後条件では、コルーチン関数の戻り値(co_yieldした値ではなく)に対して事後条件を指定することができます

awaitable<int> cancelable_session(int id)
  pre (0 < id)
  post (r: is_cancelable(r)); // rはawaitable<int>オブジェクト

標準のコルーチン型(std::generatorstd::task)はいずれもこのような状態をチェックする関数が無いのでできることは無いですが、ユーザー定義のコルーチン型では有用な場合もあるでしょう。

コルーチンではコルーチンステートの初期化時にその関数引数をすべて、値の場合はconst修飾を無視して、コルーチンステート内にムーブして格納します。このため、事後条件から関数引数を参照することが安全ではなく、禁止されます。

std::execution::task<int> coro(const std::string str)
  pre(str.empty() == false)   // ok、事前条件からの関数引数の使用は安全
  post(str.empty() == false); // ng、コルーチン事後条件において関数引数を使用できない

contract_assert()にはこれらのような制限がなく(引数の使用もコルーチンステート内の物を参照するので無問題)、コルーチン内部で使用できコルーチンの制御フローの一部としてアサーションを行うことができるのですが、契約条件式で使用できる式について少し制限があり、契約条件式でawait式/yield式を使用できません。

std::generator<int> f() {
  contract_assert(((co_yield 1), true)); // ng
}

stdex::task<void> g() {
  contract_assert(0 < (co_await query_database())); // ng

  ...
}

ただしこれは、contract_assert()が置かれている直接のコルーチンに対して影響を与えるawait式/yield式が禁止されているだけで、そうでない場合は使用可能です。例えば、コルーチンになっているラムダ式の即時呼び出しを契約条件式内で呼ぶ場合などです

contract_assert(([]()-> std::generator<int> {
  co_yield 1; // ok
}(), true));

これらの式の存在が関数をコルーチンにするため、これはcontract_assert()に対する制限というよりはawait式/yield式に対する制限ではあります。この制限は、契約アサーションの評価セマンティクスの選択がコルーチンの動作を変更しないようにするためのものです。

関数ポインタ

C++26 Contractsでは関数ポインタ/メンバ関数ポインタに対する契約アサーション(事前条件と事後条件)の指定はサポートされていません。

typedef int (*fpt)(int) post (r: r != 0); // ng

int f(int x)
  post (r: r != 0);

int (*fp)(int) post (r: r != 0) = f;  // ng
int (X::*fptr)(int) post (r: r != 0) = &X::f; // ng

契約アサーションの指定はその型に影響を与えないため、どのようにアサーションをポインタに適用し、どのようにそれを伝播するのかなどの設計の検討がほとんど進んでいないためです。これも、C++29以降の機能として予定されています(が、おそらく現在の関数ポインタにそのまま指定できるようにはならないでしょう)。

ただし、契約アサーションを持つ関数を関数ポインタに入れることはでき、関数ポインタ経由の呼び出し時にも契約アサーションは評価されます。

int f(int x)
  pre(0 < x)
  post (r: r != 0);

int (*fp)(int) = f; // OK

fp(-1); // 契約違反が発生する

これはメンバ関数ポインタやstd::function等ラッパを介した場合でも同様で、契約アサーションを持つ関数がそれらに格納されている場合、呼び出し時に契約アサーションは評価されます。

main()

main()関数にも事前条件と事後条件を指定することができます。

int main(int argc, char *argv[])
  pre(2 < argc)
  post(r: r != 0)
{
  ...

  return 10;
}

評価等のルールにも特別なところはなく、通常の関数と同じように扱われます。

この例のようにプログラム引数に対して使用する以外にも、main()呼び出し前(あるいは終了前)のプログラムのより大域的な状態を検査するのにも使用できます。

C++26の他の機能との相互作用

未定義動作とObservable Checkpoints

C++26ではObservable Checkpointsという概念が導入されています。これはUBによるタイムトラベル最適化(コード上の因果関係を逆転するような最適化)を防止するためのチェックポイントであり、プログラムのある地点で発生したUBがObservable Checkpointsを超えてプログラムをUBにすることを防止するものです。

std::observable()およびI/O関数の呼び出しがObservable Checkpointsとして扱われますが、契約アサーションもObservable Checkpointsを導入します。

より正確には契約アサーションに関連して次のイベントがObservable Checkpointsとして扱われます

  • 契約アサーション評価に際して、契約条件式の評価が開始されたこと
  • observeセマンティクスで評価された契約アサーションから呼び出された違反ハンドラが正常にリターンしたこと

これによって、契約アサーション以降で発生したUBが契約アサーションを超えて波及し、契約アサーションそのものを消し去ったりその評価をスキップすることが禁止されます。

volatile int i = 0;

void f(int *p) {
  if (p != nullptr) // #1
  {
    ++i;
  }

  contract_assert(0 <= *p); // p == nullptrの場合UB
}

この例では、#1の後のcontract_assertignoreセマンティクス以外で評価される場合、(Observable Checkpointsがないとすると)タイムトラベル最適化により#1のチェックが削除されるなどの事が発生しえます。

contract_assertの契約条件式評価開始地点がObservable Checkpointsになっていることで、契約アサーションの条件式にUBが含まれていたとしても、その契約アサーションよりも前にそのUBが波及するのを阻止します。

void g(int *p) {
  contract_assert(p != nullptr); // #2

  ++(*p); // p == nullptrの場合UB
}

この例では、#2contract_assertobserveセマンティクスで評価されている場合、違反ハンドラの呼び出しがリターンした後でも実行が継続されることでその後の*pが実行されUBになります。、(Observable Checkpointsがないとすると)タイムトラベル最適化により#2の契約アサーションそのものが削除される可能性があります。

observeセマンティクスによって呼び出された違反ハンドラのリターンがObservable Checkpointsになっていることで、契約アサーション以降のUBが契約アサーションそのものを削除することを阻止します。このことはobserveセマンティクスが契約違反発生後も実行継続する際のUBの影響を緩和します。

標準ライブラリの堅牢化モード(Standard Library Hardening)

C++26では標準ライブラリに対して堅牢化モードというものが定義され、標準ライブラリの一部の事前条件が実行時にチェックされ、違反していたらプログラムが終了されるようになります。

ここまで似たような話を聞いてきたと思います。この堅牢化モードにはC++26 Contractsが使用され、Contractsの枠組みの中で標準ライブラリの実行時検査が行われます。

int main() {
  std::vector vec = {1, 2, 3};

  int& r = vec[4];  // 堅牢化モードでは契約違反が発生し、プログラムが終了される
}

堅牢化モードにおける評価セマンティクスはenforce/quick enforceのどちらかが使用され、非堅牢化モードではignoreセマンティクスが使用されます(observeセマンティクスは使用されません)。

リフレクション

C++26 Contractsの設計原則として契約アサーションの存在が他のもののコンパイル時プロパティに影響を与えないというものがあることもあり、関数に指定された契約アサーション(事前条件と事後条件)をリフレクション(^^)を使用して取得する方法はありません。これはリフレクションを用いて関数をコピーする際にコピーできないプロパティがあるということでもあります。

一応検討はされているようですが、どうなるかはまだよく見えていません。

標準化の話

C++29以降の展望

C++ Contractsは当然これで終わりではありません。もともと、C++20で議論紛糾の末に標準からいったん削除された後、合意可能な最小の共通仕様(minimal viable product(MVP))としてのContractsとして設計されたものがC++26 Contractsになっています。そのため、これをベースの仕様として発展させていくことが初めから考慮されています。

ここで見てきた中でも

  • 仮想関数に対する契約
  • 関数ポインタに対する契約
  • 違反ハンドラをユーザーが呼び出す方法の提供

などはC++29以降に導入する予定です。他にも、契約アサーションへのラベル指定機能(P3400R1 Specifying Contract Assertion Properties with Labels)などの機能拡張も検討中です。

さらに進んで、ContractsをC++における未定義動作/エラー性動作のハンドリングに使用するという構想も検討されています。

関連して、違反ハンドラをより活用しようとするアイデアもあります

実装定義について

C++26 Contractsの仕様及びここでの説明においては実装定義(Implementation defined)あるいは推奨(Recommended practice)という言葉が目に付いたかもしれません。動作を突き詰めていくとその最後の部分は実装定義とされていることは実際に多く、これはC++26 Contractsの元の提案P2900に対して度々投げかけられた批判でもありました。

ただ、この実装定義は標準が何も規定していないとか保証していないということでは全くないことを誤解しないでください。Contractsの理念や目的は提案文書等を通じて説明されているため、標準がその動作やセマンティクスを実装任せにしているというよく見られる批判はことContractsには当たりません。

ではなぜこれほど実装定義な項目が多いのかというと、それはContracts機能をC++が使用可能な場所の全てとは言わないまでもなるべく多くの環境で使用できるようにするためです。

C++の実行環境は、指先サイズのマイコンからスーパーコンピューターまで多様なハードウェアに及んでおり、そこで動作するプログラムに対する要件も様々なものがあります。ある環境では実行リソースが潤沢である程度リッチな機能を実行できる一方で、別の環境では厳しくリソースが制限され標準出力すら持たないことがあります。またある環境ではプログラムの実行停止を可能な限り回避することが求められることもあれば、セキュリティの目的のために実行時エラーが検出されたら最速でプログラムを終了する必要があることもあります。

C++26 Contractsの動作、とくに契約アサーションのセマンティクスの細部を細かく規定すると、それによってContractsを使用できなくなる実装が発生します。上記例に挙げたように相反する要件がある場合もあります。そういった実装の事を顧みずに仕様を決めてしまえばContracts機能の普及が妨げられ、使えない環境では永遠に使うことができなくなります。特に、Contracts機能はC++プログラムの正確性を向上させるために重要な機能であり、強く待ち望まれている機能でもあるため、標準仕様としてのそのような振る舞いはC++に対する失望を招きます。

C++26 Contracts仕様にある実装定義とは、こうした多様な実装・環境・要件をなるべく受け止めるための抽象化層です。無計画に実装定義としているわけではなく、Contractsが全体としてどういう機能であるかということはしかっりと説明・規定された上で、最後の最も実装に近い部分においての振る舞いの部分を実装定義としておくことで様々な要件を吸収しようとしています。C++26 Contracts仕様の実装定義はかなり慎重に検討され配置されています。

C++26 Contractsの設計について興味のある人へ

C++26 Contractsの仕様そのものの直接の提案はP2900R14ですが、これに至るまでの間に、それこそP2900R0以前にも、いくつもの関連提案があります。すべてを列挙することは困難ではあるものの、それらの提案とその議論の過程にはC++26 Contractsの設計がなぜこのようになったのかについての重要な情報が残されています。

このような議論の過程については、C++26 Contractsを主導した人々によってP2899R1という文書にまとめられています。この文書にはC++26 Contracts成立の過程が詳しく記されています。興味がある人は見てみるとよいでしょう(169Pありますが)。

参考文献

この記事のMarkdownソース

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

文書の一覧

全部で99本あります。

もくじ

N5010 WG21 agenda: 16-21 June 2025, Sofia Bulgaria

2025年6月にソフィア(ブルガリア)で行われる会議のアジェンダ

P0085R1 Oo... adding a coherent character sequence to begin octal-literals

8進リテラルの新しいプリフィックスの提案。

現在の8進リテラルの構文では、0始まりの数値リテラルが8進リテラルとして認識されます。

01234; // 8進リテラル
0;     // 8進リテラル
08;    // ng、8は8進数値として無効

ただ、この構文は初学者にとって間違えやすいことが指摘されています。例えば、10進リテラルの整数値を幅をそろえるなどの目的で先頭に0パディングしてしまったときでもエラーにはならず、8進リテラルとして解釈されることで整数値が変化します。

 12340; // 10進リテラル
012340; // 8進リテラル

この提案はこのような構文を修正するために、8進リテラルの新しいプリフィックスとして0o/0Oを追加するとともに、現在の8進リテラルプリフィックスを非推奨にするものです。

// この提案の8進リテラル
0o1234;
0O1234;

この構文はHaskellPythonなどを始めとする他言語で採用されているものと同様で、現在8進数値リテラルデファクトスタンダードな構文となりつつあります。また、16進リテラルや2進リテラルプリフィックスと整合しています。

0b00101010; // 2進リテラル
0o52;       // 8進リテラル
0x2A;       // 16進リテラル

Cの8進リテラルC++の現在のものと同様の構文を取っていますが、これはすでに非推奨化されており、廃止予定とされています。C++でもこれに倣って、まず非推奨化してコンパイラの警告を促し、将来的に廃止することを目指します。

この修正は基本的に後方互換性を維持するものですが、一つ非互換な変更となる部分があります。それはstd::format()で整数値を8進出力した時のフォーマット結果で、#oオプションによって基数プリフィックスを表示させるようにしたときの結果が構文と一貫しなくなるというものです。

std::println("{:o}", 0o77);  // 8進出力
std::println("{:#o}", 0o77); // 基数プリフィックス付き8進出力
                             // `#O`はない
std::println("{:#x}", 0x3f); // 基数プリフィックス付き16進出力(小文字)
std::println("{:#X}", 0x3f); // 基数プリフィックス付き16進出力(大文字)
77
077
0x3f
0X3F

特にこの提案の後で、#Oオプションが無いこともあり、この出力は16進の場合(#Xだとプリフィックス0Xになる)と一貫しなくなります。

この提案では、オプションはそのままとして#oオプションの出力結果のプリフィックス0oに変更することを推奨しています(提案にはしていません)。これは破壊的変更になりますが、Githubの検索では利用頻度が低いので問題ない可能性があるとしています。また、代替のオプションを4つ提示しています。

P0149R2 Generalised member pointers

P0149R3 Generalised member pointers

メンバポインタの表現可能な範囲を拡張する提案。

以前の記事を参照

R2での変更は

  • 以前は言及のみだった->*と単項*演算子についての変更を提案するようになった
  • 提案する文言の修正

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

  • EWGにおける明確化のための投票を受けての修正
    • E1->E2(*(E1)).E2と等価であり、これと同様にE1->*E2(*(E1)).*E2と常に等価
  • 提案する文言の修正

などです。

このリビジョンでは、->*と単項*および->演算子についても一般化メンバポインタに対応するようにしています。

  • *: 「クラス型T1の型T2の配列メンバへのポインタ」型の式に対して*が適用された場合、「T1T2型メンバへのポインタ」型の値になる
    • E1.*(*E2)という式は*(E1.*E2)と等価である必要がある
      • E1T1型の式、E2T2型配列メンバへのポインタ型の式、式全体の型はT2
  • ->: 式E1, E2に対してE1->E2のように->が適用された場合で、↑の新しい*演算子が使用される場合、既存の場合と同様に(*(E1)).E2と等価
    • この適用範囲は、E1がメンバへのポインタである場合と、E1が配列型メンバへのポインタである場合、の両方に及ぶ
  • ->*: 式E1, E2に対してE1->*E2のように->*が適用された場合で、新しい.*演算子が使用される場合、既存の場合と同様に(*(E1)).*E2と等価
    • この適用範囲は、E1がメンバポインタのポインタである場合と、E1が配列型メンバへのポインタである場合、の両方に及ぶ

->*を用いて、->*.*を用いて定義されるというところは既存のものと変わりませんが。*.*が一般化メンバポインタに対応するのに合わせて動作範囲が拡張されています。

*->のサンプルコード

struct A { int is[42]; };
constexpr int (A::*isp)[42] = &A::is; // A::is(Aのint型配列メンバ)へのメンバポインタisp

A a;
constexpr int& is0_1 = *(a.*isp); // OK, C++98(constexprを除いて、以下同様)
constexpr int& is0_2 = a.*(*isp); // NG、この提案ではok
static_assert(&is0_1 == &is0_2);  // NG、この提案ではok
// どちらも、a.is[0]を参照している

struct B { int i; };
struct C { B b; };
constexpr B C::*bp = &C::b; // C::bへのメンバポインタbp

C c;
constexpr int& i_1 = &(c.*bp)->i;   // OK, C++98
constexpr int& i_2 = c.*((&bp)->i); // NG、この提案ではok
//                   c.*((*(&bp)).i) : CのBメンバへのポインタ(*(&bp))に、B::iを.で適用 => Cのint型メンバポインタが得られる
//                   c.*cip          : それをcipとると、c.*cip はCのint型メンバへのポインタをcに適用し、c.b.iを参照する
static_assert(&i_1 == &i_2);        // NG、この提案ではok
// どちらも、c.b.iを参照している

struct C { int i; };
struct D { C cs[42]; };
constexpr C (D::*csp)[42] = &D::cs; // D::cs(DのC型配列メンバ)へのメンバポインタ

D d;
constexpr int& cs0i_1 = (d.*csp)->i;  // OK, C++98
constexpr int& cs0i_2 = d.*(csp->i);  // NG、この提案ではok
//                      d.*((*csp).i) : DのC型配列メンバ(csp)に*を適用 => d.cs[0]を指すDのC型メンバポインタが得られる
//                                    : DのC型メンバポインタ(*csp)にC::iを.で適用 => Dのint型メンバポインタが得られる
//                      d.*dip        : それをdipとすると、d.*dip はDのint型メンバへのポインタをdに適用し、d.cs[0].iを参照する
static_assert(&cs0i_1 == &cs0i_2);    // NG、この提案ではok
// どちらも、d.cs[0].iを参照している

*演算子は配列メンバポインタに適用されると、その配列メンバを保持しているクラス型のメンバポインタ(型は配列の要素型)、を返すようになります。->演算子はその動作と.の新しい動作を利用して、メンバのメンバへのポインタをそのクラスの直接のメンバポインタへ変換するようになります。

->*のサンプルコード

struct A { int i; };
struct B { A a{}; };
constexpr A B::*ap = &B::a;   // B::aへのメンバポインタap
constexpr int A::*ip = &A::i; // A::iへのメンバポインタip

B b;
constexpr int& i_1 = (b.*ap).*ip;   // OK, C++98
constexpr int& i_2 = b.*(ap.*ip);   // NG、この提案ではok
//                       ap.*ip     : BのA型メンバへのポインタapに、Aのint型メンバへのポインタipを.*で適用 => Bのint型メンバへのポインタが得られる
//                   b.*bip         : それをbipとすると、b.*bipはBのint型メンバへのポインタをbに適用し、b.a.iを参照する
static_assert(&i_1 == &i_2);        // NG、この提案ではok
// どちらも、b.a.iを参照している

constexpr int& i_3 = (&(b.*ap))->*ip; // OK, C++98
constexpr int& i_4 = b.*((&ap)->*ip); // NG、この提案ではok
//                      (*(&ap).*ip)  : B::aへのメンバポインタ(*(&ap))に、Aのint型メンバへのポインタipを.*で適用 => Bのint型メンバへのポインタが得られる
//                   b.*bip           : それをbipとすると、b.*bipはBのint型メンバへのポインタをbに適用し、b.a.iを参照する
static_assert(&i_3 == &i_4);          // NG、この提案ではok
// どちらも、b.a.iを参照している
struct C { int i; };
struct D { C cs[42]; };
constexpr int C::*ip = &C::i;       // C::iへのメンバポインタ
constexpr C (D::*csp)[42] = &D::cs; // D::cs(DのC型配列メンバ)へのメンバポインタ

D d;
constexpr int& cs0i_1 = (d.*csp)->*ip; // OK, C++98
constexpr int& cs0i_2 = d.*(csp->*ip); // NG、この提案ではok
//                        ((*csp).*ip) : DのC型配列メンバ(csp)に*を適用 => d.cs[0]を指すDのC型メンバポインタが得られる
//                                     : DのC型メンバポインタにCのint型メンバへのポインタipを.*で適用 => Dのint型メンバへのポインタが得られる
//                      d.*dip         : それをdipとすると、d.*dip はDのint型メンバへのポインタをdに適用し、d.cs[0].iを参照する
static_assert(&cs0i_1 == &cs0i_2);     // NG、この提案ではok
// どちらも、d.cs[0].iを参照している

この提案はC++29に向けて、CWGでレビュー中です。

P1144R13 std::is_trivially_relocatable

オブジェクトの再配置(relocation)という操作を定義し、それをサポートするための基盤を整える提案。

以前の記事を参照

このリビジョンでの変更は明確ではありませんが、リロケーションに関する別の提案であるP2786がC++26に採択されたことを受けて、P3236で提起された問題が解決されていないため、P3236に引き続いてP2786に対する異議申し立ての姿勢が明確にされています。

また、P3236R1が提出された後で、P2786R13までの間に新しく別の問題が追加されているとのことです

  • ARM64プラットフォームでは"vptr signing"を使用しており、これはmemcpyに対して安全ではない。しかし、P2786(WD)ではvptrを持つ型やそれを持つ型をメンバに含むような型に対してis_trivially_relocatabletrueを返す
    • このためARM64上ではis_trivially_relocatableに従ってリロケーションを行うことが安全ではない
  • P2786のリロケーション関連クラスアノテーションは驚くほど醜い。一方P1144の属性構文はそれほどではない

これらの解決のため、このリビジョンでの提案する文言はP2786マージ後のWDに対する差分になるように変更されているようです。

この提案は、EWG/LEWGのどちらにおいてもコンセンサスを得られなかったようで、リジェクトされています。

P1306R4 Expansion statements

コンパイル時にステートメントをループ生成することのできる、展開ステートメントの提案。

以前の記事を参照

このリビジョンでの変更は、文章と文言を書き直したことのみです。

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

P2079R8 Parallel Scheduler

ハードウェアの提供するコア数(スレッド数)に合わせた固定サイズのスレッドプールを提供するSchedulerの提案。

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

  • HagenbergでのLEWGレビューのフィードバックを適用
  • bulk_chunkedbulk_unchunkedの場合の図を追加
  • 提案する文言の改善

などです。

P2287R5 Designated-initializers for base classes

基底クラスに対して指示付初期化できるようにする提案。

以前の記事を参照

このリビジョンでの変更は、文言の改善のみです。

P2414R7 Pointer lifetime-end zap proposed solutions

Pointer lifetime-end zapと呼ばれる問題の解決策の提案。

以前の記事を参照

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

  • "discussion"の内容をP2434R4のものにリベース
  • angelic provenanceがない場合のpointer-zapのエルゴノミクスの限界を示すために、露出ポインタを使用したLIFOプッシュアルゴリズムの例を追加
  • usable_ptr<T>関連
    • 比較を<=>に変更
    • T&を返すoperator*のみをconstexprにする
    • operator==constexprではなくconstにする
    • ハッシュの規定からクラスDを削除
    • make_ptr_prospective()make_usable_ptr()にリネーム
    • uintptr_tがない実装でもas-ifルールによって実装できることを明記
    • 比較演算子との互換性を保つために、nullptr_tを取るコンストラクタでiptr(内部ポインタ)をゼロに初期化する

などです。

P2434R4 Nondeterministic pointer provenance

現在のC++のポインタ意味論をポインタのprovenanceモデルに対して整合させるための提案。

以前の記事を参照

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

  • N5008にリベース
  • 因果関係に反するポインタ値を禁止する
  • P3501R0について議論を追加

などです。

P2509R1 A proposal for a type trait to detect value-preserving conversions

算術型について、その値を保持する変換を検出するための型特性を追加する提案。

以前の記事を参照

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

  • スコープの縮小
  • 参考文献の更新

などです。

P2664R10 Proposal to extend std::simd with permutation API

std::simdに、permute操作のサポートを追加する提案。

以前の記事を参照

このリビジョンでの変更は、LEWGレビューを受けての文言更新のみです。

P2719R5 Type-aware allocation and deallocation functions

型を指定する形のnew/delete演算子カスタマイズ方法の提案。

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

  • EWGガイダンスに従って文言更新
    • type_identity型を構築する際、型から修飾子を削除する
    • グローバルスコープのtype aware演算子(この提案のもの)は、定数実行時に置換可能なグローバル割り当て関数として扱う(定数式で使用可能)
    • type awareなnew/new[]delete/delete[]のスコープ間選択がill-fomredになるようにする
    • 現在暗黙な引数をすべて必須にする
  • Introductionの例で正しいシグネチャを使用するように修正
  • [expr.new]のオーバーロード解決例を更新
    • 既存の例を更新し、type awareな演算子(この提案のもの)を追加
    • placement newにおけるstd::align_val_t引数の動作を示す新しい例を追加
  • std::type_identity<T>ではなくstd::type_identity<U>を使用するように文言を更新
  • 文言の変更を反映するように例を更新

などです。

P2902R2 constexpr 'Parallel' Algorithms

並列アルゴリズムを定数式で使用できるようにする提案。

以前の記事を参照

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

  • 定数評価中に並列アルゴリズムが例外を送出した場合でも、実行時の意味論が順守されることを明確化
  • 前のリビジョンでのリベース時のミスを修正

などです。

P2927R3 Observing exceptions stored in exception_ptr

std::exception_ptrを再スローせずに例外オブジェクトの取得を試みる関数の提案。

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

  • void exception_ptr_cast(const exception_ptr&&a) = deleteを追加した

などです。

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

P2956R1 Add saturating library support to std::simd

P0543で提案されている整数型の飽和演算関数群にstd::simdオーバーロードを追加する提案。

以前の記事を参照

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

  • 実装経験の追加
  • 文言の改善

などです。

P2970R0 Partial application of concepts in template arguments

コンセプトテンプレートパラメータで、部分適用されたコンセプトを受け入れるようにする提案。

C++26では、コンセプトテンプレートパラメータによってコンセプトそのものをテンプレート引数に渡すことができるようになっています。しかし、渡せるのはその引数を埋める前のコンセプトそのものであり、何かしらの引数が部分適用されたコンセプトを渡すことはできません。

// Cを満たす要素型によるrangeであることを制約するコンセプト
template <typename R, template <typename> concept C>
concept range_of = std::ranges::range<R> && C<std::ranges::range_value_t<R>>;

auto f(range_of<std::integral> auto&&);     // ok
auto f(range_of<std::same_as<int>> auto&&); // ng

コンセプトテンプレートパラメータCには引数適用前のコンセプトを渡すことしかできません。

これに対処する一つの方法としては、range_ofコンセプトの定義をこれを考慮して変更することができます。

template <typename R, template <typename...> concept C, typename... Args>
concept range_of = std::ranges::range<R> && C<std::ranges::range_value_t<R>, Args...>;

void f(range_of<std::same_as, int> auto&&);

Cに渡すテンプレート引数を別に受け取るようにして、内部で適用するようにしています。しかし、これはコンセプトの定義でテクニックが必要になるとともに、引数の意味が明確ではなく、要素型に対する制約を複数取れるようにrange_ofを拡張する方向性を閉ざしてしまいます。

別の方法として、コンセプトテンプレートパラメータとその引数を別の型を経由して渡すようにする方法も考えられます。

template <template <typename...> concept C, typename... Args>
struct packed_concept {
  template <typename T>
  static constexpr bool apply = C<T, Args...>;
};

template <typename R, typename... PackedConcept>
concept range_of = std::ranges::range<R> && (PackedConcept::template apply<std::ranges::range_value_t<R>> && ...);

void f(range_of<packed_concept<std::convertible_to, int>, packed_concept<std::regular>> auto&&);

こちらの場合は要素型に対する制約の合成がサポートされているものの、先ほどの方法と比較しても可読性が低下しており、包摂関係の成立を妨げるようになっています。

結局、最初の例にあったような部分適用されたコンセプトをそのまま渡すことができれば一番使いやすくなるとして、ここではそれを提案しています。ただし、部分適用されたコンセプトを渡す構文は、conceptの指定が必要になります。

// コンセプトテンプレートパラメータを受け取る何らかのテンプレートとする
some_template_name <
  std::regular,           // コンセプトを渡す(C++26
  concept regular<>,      // 0引数が部分適用されたコンセプトを渡す(この提案
  invocable<int>,         // コンセプトの結果のbool値が渡される(診断されるはず
  concept invocable<int>  // 1引数が部分適用されたコンセプトを渡す(この提案
>;

このように、部分適用されたコンセプトを渡す際は渡す側でconceptの指定が必要になります。

これは、可変長引数を取るコンセプトではbool値を渡そうとしているのか部分適用されたコンセプトを渡そうとしているのかが曖昧となり、無理やり推定しようとすると既存のコードを壊してしまう可能性があるほか、将来の機能としてのユニバーサルテンプレートパラメータが来た場合に推定が曖昧になることが予想される、などの理由によってこのようになっています。

最初のrange_ofの例はコンセプト定義の変更を必要とせず、conceptを利用側で追加することで有効になります。

// Cを満たす要素型によるrangeであることを制約するコンセプト
template <typename R, template <typename> concept C>
concept range_of = std::ranges::range<R> && C<std::ranges::range_value_t<R>>;

auto f(range_of<std::integral> auto&&);     // ok
auto f(range_of<std::same_as<int>> auto&&); // ng
auto f(range_of<concept std::same_as<int>> auto&&); // ok

この部分適用コンセプトテンプレートパラメータは、そのコンセプトをラップして引数を部分適用するコンセプトをその場でインラインで定義してそれを使用するかのように動作します。

// 即席コンセプト
template<typename T>
concept invented_same_as = std::same_as<T, int>;

// これは
auto f(range_of<concept std::same_as<int>> auto&&);

// こう書きかえられるのと等しい
auto f(range_of<invented_same_as> auto&&);

これによって、コンセプトの包摂関係は部分適用されたコンセプトを渡した時でも保たれるようになります。

この書き換え例からも分かるように、部分適用されたコンセプトは第二引数以降が部分適用されたものとして扱われます。これは、テンプレートパラメータ宣言時などにコンセプトの第一引数指定を省略できる機能と一貫したものです。

// これら3つの例の制約は全て等しい

template<typename T>
void f() requires invocable<T, int>;

template<invocable<int> T>  // Tが第一引数に自動的に補われる
void f();

void f(invocable<int> auto&& f);  // decltype((f))が第一引数に自動的に補われる
// このように使用したとすると
auto f(range_of<concept invocable<int>> auto&&);

// このような即席コンセプトを定義して
template<typename T>
concept invented_invocable = invocable<T, int>;

// こう書きかえられるのと等しい
auto f(range_of<invented_invocable> auto&&);

このために、この部分適用されたテンプレートを渡す機能はコンセプトに限られており、変数テンプレートなどでは利用できません(ほかの種類のテンプレートの場合はこのような補完で有用なものが確立されていないため)。

また、2つ以上のテンプレートパラメータを残した状態で部分適用することはできません。

提案されている文言からの例

template <typename T, template <typename> concept... Concepts>
concept all_of = (Concepts<T> && ...);

template <typename, auto>
concept A = true;

template <typename T, typename>
concept C = true;

template <typename... T>
concept D = true;

template <typename>
concept E = true;

void f(all_of<concept C<0>, concept C<int>, concept D<int>> auto); // ok
void f(all_of<concept E<int>> auto);      // error: Eは単一のテンプレート引数しか取らない(これ以上引数を適用できない)
void f(all_of<concept C<int, int>> auto); // error: 即席コンセプト内の制約式はC<T, int, int>となるが、これは有効な式ではない

この機能はP2841R7の早期のリビジョンでは含まれて提案されていたものの、C++26を目指すために提案のスコープを絞ったことやEWGが重視していなかったこともあり、最終的なP2841R7ではドロップされていたものです。

P2996R12 Reflection for C++26

値ベースの静的リフレクションの提案。

以前の記事を参照

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

  • コア言語の文言の変更
    • スプライスされた関数呼び出しとオーバーロード解決の相互作用を明確化
      • CWG Issue 2701の修正を統合
    • requires式によって導入されたローカルパラメータのリフレクションを禁止
    • “naming class”を“designating class”に変更し、スプライス式について定義する
    • reflect_valuevalue_ofreflect_constantconstant_ofに置換
  • ライブラリの文言変更
    • access_context::current()の仕様を改善(例を含む
    • rがビットフィールドの場合、size_or(r)は定数ではなくなった
    • extractを使用して、配列からポインタを取得できるようにした

などです。

P3008R5 Atomic floating-point min/max

浮動小数点数型のstd::atomicにおけるfetch_max()/fetch_min()の問題を解消する提案。

以前の記事を参照

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

  • P3348がWDにマージされたことを受けて、P3348に依存しないように文言を修正
  • 不足していたkey->operationのマッピングを追加
  • atomic/atomic_ref<floating-point>::fetch_min/maxの仕様を明確化
  • typoの修正
  • [math.syn]の変更を文言セクションの先頭に移動

などです。

P3037R6 constexpr std::shared_ptr and friends

std::shared_ptrを定数式でも使えるようにする提案。

以前の記事を参照

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

  • アトミック部分特殊化の実装に関する議論を追加
  • スマートポインタアダプタ(out_ptrなど)の実装に関する議論を追加
  • スマートポインタアダプタのvoid**変換演算子からconstexprを外す
  • HagenbergでのLEWG投票結果を追加

などです。

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

P3086R4 Proxy: A Pointer-Semantics-Based Polymorphism Library

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

以前の記事を参照

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

  • Proxyライブラリ4.0の実装に合わせて文言を更新
    • facade_aware_overload_tを追加
    • ProOverload要件を追加
    • proxyクラステンプレートおよびaccess_proxy, proxy_invoke, proxy_reflect関数テンプレートの制約を改訂

などです。

P3096R9 Function Parameter Reflection in Reflection for C++26

C++26に向けた静的リフレクションに対して、関数仮引数に対するリフレクションを追加する提案。

以前の記事を参照

このリビジョンでの変更は、CWGのフィードバックを受けての文言修正のみです。

P3100R2 Implicit contract assertions

UB(及びEB)を契約違反として扱うようにする提案。

以前の記事を参照

このリビジョンでの変更は明示的ではないですが、SG21においてP3100R1の方向性が受け入れられたこととContracts提案(P2900R14)がC++26にマージされたことを受けて全面的に書き直されています。特に、コア言語UBホワイトペーパー(P3656R1)の方針確立を受けてその作業に貢献しそこに採択されることを目標としています。

とはいえ基本的な方向性に変更はないようですが、以前のリビジョンからの主要な変更は

  • assumeセマンティクスを暗黙的な契約アサーションでのみ許可する
  • detection_mode列挙型への変更の削除
    • エラーカテゴリ(検出されたUB種別)の識別には列挙値の代わりにラベルを使用する

などのようです。

P3111R6 Atomic Reduction Operations

std::atomicにアトミックリダクション操作を追加する提案。

以前の記事を参照

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

  • PPC64LEにおける実装に関する推奨事項を追加
  • [atomics.order]に"atomic modify-write operation"の定義を追加
    • これを受けて、定義名を"atomic reduction operation"から"atomic modify-write operation"に変更
  • P3008の文言の組み込み方法を変更(WDマージ済み

などです。

P3125R4 constexpr pointer tagging

タグ付きポインタをサポートするためのライブラリ機能の提案。

以前の記事を参照

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

  • LEWGの要求による変更を追加
    • nullptr_tコンストラクタの削除
    • void*サポートを追加
    • pointer_tag_pairの最初のテンプレート引数がpointeeではなくpointerになった
    • ポインタ内の利用可能なビットを調査するためのpointer_tag_traitを追加
    • pointer_tag_pairにタプルプロトコルサポートを追加

どのリビジョンからかはわかりませんが、この提案はポインタ値の下位ビットに安全かつポータブルにアクセスするための機能を提供しようとしていますが、上位ビットへのアクセスを標準化しようとはしていません(移植性が無く、OSやCPUにおける利用を妨げるため)。pointer_tag_pair型は、あるポインタ値に対してオリジナルのポインタ値と埋め込んだタグ値のペアです。

P3149R10 async_scope -- Creating scopes for non-sequential concurrency

P2300のExecutorライブラリについて、並列数が実行時に決まる場合の並行処理のハンドリングを安全に行うための機能を提供する提案。

以前の記事を参照

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

  • いくつかのフィードバックを適用
  • “is [boolean]”を“returns [boolean]”に置換
  • nest-dataが必要な動作を行うためには推論補助が必要であることを確認
  • impls-for<nest_t>::get-stateを更新し、条件付きnoexceptを付加
    • 他のsenderアルゴリズムget-stateが例外を送出するかに応じてそのset_error動作を変更する必要がある
  • async_scope_tokenを更新し、t.disassociate()noexceptであることを必須とする
  • simple_counting_scope::try_associate()counting_scope::try_associate()の両方をnoexceptにする
    • impls-for<nest_t>::get-statenoexceptは、指定されたsenderが左辺値の場合指定されたトークンのtry_associate()noexceptであるかどうかに依存する
  • メンバ名の末尾アンダースコアの付与に関する記述を一貫させる
  • empty_envenv<>に変更

などです。

P3164R4 Early Diagnostics for Sender Expressions

提案中のExecutorライブラリにおいて、senderチェーンのエラーを早期に報告するようにする提案。

以前の記事を参照

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

  • sizeof...(Env)が1より大きい場合get_completion_signatures<Sndr, Env...>()をill-formedとする
  • senderの(非依存)完了シグネチャを宣言するために、ネストした::completion_signaturesエイリアスを使用するオプションを削除

などです。

この提案はP3557(定数式での例外送出を使用したエラー報告)によって置き換えられることになったため、ここでストップされています。

P3179R8 C++ parallel range algorithms

RangeアルゴリズムExecutionPolicyに対応させる提案。

以前の記事を参照

このリビジョンでの変更は、LWGレビューを受けての文言修正のみです。

P3284R4 write_env and unstoppable Sender Adaptors

receiverの環境に値を書き込むためのwrite_envと、それを用いたunstoppableアルゴリズムの提案。

以前の記事を参照

このリビジョンでの変更は明確ではないですが、文言の調整のみのようです。

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

P3293R2 Splicing a base class subobject

リフレクション機能において、基底クラスのサブオブジェクトへアクセスする簡単な方法を提供する提案。

以前の記事を参照

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

  • P3547R1(リフレクションにおけるアクセスコンテキストの考慮)を考慮して文言を調整
  • has_inaccessible_subobjects()を追加
    • has_inaccessible_subobjects(r, ctx)は、ctxのコンテキストでクラスのリフレクションrのサブオブジェクトにアクセス可能かをbool値で返す

などです。

P3310R6 Solving issues introduced by relaxed template template parameter matching

P0552R0の影響を緩和するための提案。

以前の記事を参照

このリビジョンでの変更は、文言の更新です。

P3347R2 Invalid/Prospective Pointer Operations

無効化されたポインタに対する一部の演算を明示的に許可する提案。

以前の記事を参照

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

  • Hagenberg会議のレビューを受けての修正
    • pointer-arithmetic要件を削除
      • この要件はコンパイラがサイズを知らない不完全型には適用できない
      • 後で復帰する可能性があるものの、完全型に限定されて適用される
    • angelic provenanceの参照をすべて削除
    • prospective pointersの参照をすべて削除
      • P2434R2の今後の進展次第では、これらの参照は再び復帰する可能性がある

などです。

P3375R3 Reproducible floating-point results

計算結果の再現性の保証された浮動小数点数型の提案。

以前の記事を参照

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

  • 貢献者リストを更新
  • abstractを更新
  • banking discussionのセクションを更新
  • Cの浮動小数点数拡張に関するTSについての議論を追加
  • 可能なアプローチのリストに正しく丸めを行う関数を追加
  • 正しく丸めを行う関数に焦点を当てて、スコープの拡大を抑制
  • open questionsを更新
  • 参考文献を更新

などです。

P3385R5 Attributes reflection

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

以前の記事を参照

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

  • in-placeスプライス構文を削除
  • 関連メタ関数を追加
  • 実装フィードバックを拡張

などです。

P3394R3 Annotations for Reflection

C++任意のエンティティ(宣言)に対して静的リフレクションのためのアノテーションを付加できるようにする提案。

以前の記事を参照

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

  • P2996R12へのリベース
  • プロセス効率化のために注釈を削除

などです。

P3395R4 Fix encoding issues and add a formatter for std::error_code

std::error_codeをフォーマット可能にする提案。

以前の記事を参照

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

  • R3についてのLEWG投票結果を追加
  • 文言の誤字修正
  • デバッグ出力の文言を修正
  • std::error_codeの値についてのフォーマットオプションを提供しない理由を明確化
    • カテゴリ情報なしでは用途が限られるためと、{fmt}においてもその要望はなかったため

などです。

P3402R3 A Safety Profile Verifying Initialization

クラスのすべてのサブオブジェクトが初期化されていることを保証するプロファイルの提案。

以前の記事を参照

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

  • no.reassignルールを削除
  • std::verified_castを議論の対象レベルへ格下げ(提案しない
  • 不一致の問題をIFNDRとして修正
  • 許容される入力セットを修正
  • restrict.returnsルールを修正
  • POD型ではなくトリビアル型を使用するようにテキストを更新
  • 提案する文言を追加

などです。

P3411R2 any_view

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

以前の記事を参照

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

  • constexprサポートを追加
  • 提案する文言を追加
  • ムーブオンリーをデフォルトとする
  • 2つ目のリファレンス実装を追加
  • その他修正

などです。

P3412R2 String interpolation

std::format/std::print向けの引数となるフォーマット文字列と対象引数列の組を生成する、文字列補完リテラルの提案。

以前の記事を参照

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

  • 関数のどの引数がフォーマット文字列であるかを示す無視できない属性を追加し、xリテラルの必要性を回避する
  • マクロではないことを明確にするため、__FORMAT__関数の名前を__format__に変更
  • マクロ展開を式の抽出後に行うようにする
    • これにより、fリテラル処理の様々なステップを翻訳フェーズに分離できるようになる
    • エディタ等での可視化の堅牢性が向上
  • ユーザー定義リテラルfリテラルと連携しないように明確化
  • printf形式フォーマットに関する章を追加
  • プリプロセッサのフェーズで可能な(文言)表現戦略に関するテキストを追加

などです。

無視できない属性とは、[[cpp_format_string(N)]]のようなもので、fリテラルN番目の関数引数に指定されている場合にプリプロセッサによって展開されている__format__()関数呼び出しをインライン展開することを表すものです。

このために、__format__()関数呼び出しを展開する必要があるかをオーバーロード解決フェーズで確定させようとしています。

// print()の宣言
template<typename... Args>
void [[cpp_fmt_string(1)]] print(format_string<Args...> fmt, Args&&...args);

template<typename... Args>
void [[cpp_fmt_string(2)]] print(ostream&, format_string<Args...> fmt, Args&&... args);
// In user code
std::print(f"Value {1}");                           // #1
std::print(std::cerr, f"Wrong value {2}");          // #2
std::print("String value: {}", f"Some value {3}");  // #3

このような呼び出しが行われている場合、コンパイラstd::print()オーバーロードセットを(fリテラルの引数位置Nに対して)[[cpp_fmt_string(N)]]に一致するものとそうでないものの2つに分割し、それぞれでオーバーロード解決を行います。どちらか片方のセットだけで解決が成功すればそれが呼び出し結果となります。

#1の場合、fリテラルが1番目にあるので、[[cpp_fmt_string(1)]]とそうでないグループに分割され、前者のグループでは__format__()呼び出しをインライン展開した結果を引数としてオーバーロード解決が行われ、マッチするためこれが採用されます(後者のグループではインライン展開せずにオーバーロード解決が行われるものの引数が不足しているため失敗する)。

#2の場合、fリテラルが2番目にあるので、[[cpp_fmt_string(2)]]とそうでないグループに分割され、前者のグループでは__format__()呼び出しをインライン展開した結果を引数としてオーバーロード解決が行われ、マッチするためこれが採用されます(後者のグループではインライン展開せずにオーバーロード解決が行われるものの、std::cerrをフォーマット文字列として使用できずオーバーロード解決は失敗する。

#3の場合、fリテラルが2番目にあるので、[[cpp_fmt_string(2)]]とそうでないグループに分割され、[[cpp_fmt_string(1)]]オーバーロードは展開しないグループに、[[cpp_fmt_string(2)]]オーバーロードは展開するグループに属します。展開するグループでは文字列リテラルからostream&への暗黙変換ができないためオーバーロード解決は失敗し、展開しないグループでは1つ目の引数がフォーマット文字列に、2つ目の引数(fリテラルの展開後__format__()呼び出し)がフォーマット対象引数としてマッチするため、1つ目のオーバーロードが選択されます。その後、実行時に__format__()呼び出し経由でstd::format()が呼び出されることでfリテラルの文字列が出力されます。

P3439R2 Chained comparisons: Safe, correct, efficient

誤って書かれることの多い、連鎖比較を意図通りに動作するようにする提案。

以前の記事を参照

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

  • 実際のコードベースの調査で連鎖比較を期待するバグコードが発見されたことを受けて、これを明記して再提案した
    • R1はバグが稀であることと実装経験の乏しさによってリジェクトされていた

などです。

P3442R2 [[invalidate_dereferencing]] attribute

関数に渡されるポインタが無効化されることをコンパイラに通知するための属性、[[invalidate_dereferencing]]の提案。

以前の記事を参照

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

  • SG23での投票結果を追加
  • [basic.compound] p4に関して、記憶域外ポインタについての議論を追加
  • オブジェクトの無効化についてコア言語の観点からより明確な説明を追加

などです。

P3480R5 std::simd is a range

std::simdrangeにする提案。

以前の記事を参照

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

  • iterator_categoryinput_iterator_tagに変更し、iterator_conceptrandom_access_iterator_tagに変更
  • コンストラクタ内のintsimd-size-typeに変更
  • constexprfriendの順序を修正
  • default_sentinel_tを使用したoperator<=>を削除
  • #if LEWG_WANTS_CONVERSIONを削除するものの、変換コンストラクタはそのままにする
  • operator*offset_に対する事前条件を修正

などです。

P3516R2 Uninitialized algorithms for relocation

未初期化メモリに対するリロケーションアルゴリズムのライブラリ機能の提案。

以前の記事を参照

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

  • relocate-atstd::relocate_at()として提案の一部にする
  • std::ranges::の関数にstd::execution_policyオーバーロードを追加
  • P3179R8で追加済みのコンセプトを削除

などです。

P3552R2 Add a Coroutine Task Type

std::executionに対応したコルーチンlazy型の提案。

以前の記事を参照

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

  • 仕様からdecay_tの使用を削除
  • 利用できない場合はexception_ptrを回避するように文言を変更
  • 引数の用途をより適切に反映するために、ContextEnvironmentに変更
  • 説明専用のマクロは斜体を使用する
  • 機能テストマクロを追加

などです。

P3556R1 Input files are source files

規格中の入力C++ソースファイルに関する用語を整理する提案。

以前の記事を参照

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

  • 代替表現を追加
    • "source text"の明確な定義を追加
    • フェーズ1終了時のsource textの内容に関する規範的な保証
  • SG16のフィードバックを適用

などです。

P3557R2 High-Quality Sender Diagnostics with Constexpr Exceptions

std::executionにおける、エラーメッセージ出力を改善する提案。

以前の記事を参照

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

  • nice-to-havesの機能を別の提案に分離
  • ネストされた型エイリアスによるsenderの完了シグネチャ指定のサポートを削除
  • 説明専用だったdependent-sender-errorクラスをstd::execution::dependent_sender_errorに昇格
  • basic-sender::check-typesとそのすべてのカスタマイズをconstexprからconstevalに変更
  • 文言レビューを簡単にするために、P3164R4の提案文言を組み込む

などです。

P3560R1 Error Handling in Reflection

静的リフレクション機能におけるエラーハンドリング方法として例外を採用すべきとする提案。

以前の記事を参照

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

  • std::meta::exceptionstd::exceptionから派生するように変更
    • char const* what()アクセサが追加
  • P2996R12にリベース

などです。

P3565R1 Virtual floating-point values

C++における浮動小数点数演算のセマンティクスについての提案。

以前の記事を参照

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

  • FMAの例と解説を追加
  • FLT_EVAL_METHODとの関係について説明
  • CWG2752への影響について説明
  • 実装要件について説明

などです。

P3566R1 You shall not pass char* - Safety concerns working with unbounded null-terminated strings

その長さが静的に既知ではない文字列(const char*)を受け取るstd::string/std::string_viewのコンストラクタを置き換える提案。

以前の記事を参照

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

  • SafeStringViewUnSafeStringViewのコンセプトを導入
  • 既存コードへの影響について追加
  • nullptrは空文字として扱う

などです。

P3570R1 optional variants in sender/receiver

並行キューのasync_pop()の最適な戻り値型を探る提案。

以前の記事を参照

このリビジョンでの変更はよくわかりません。

P3588R1 Allow static data members in local and unnamed classes

ローカルクラスでstaticメンバ変数を宣言できるようにする提案。

以前の記事を参照

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

  • 目標をC++29へ変更
  • 初期化順序に関する議論を拡張し、設計例を3つ提示
  • 実装経験に関する議論を拡張し、実装におけるバグに関する議論も追加
  • complete-class contextsを含む順序付けに関する議論を追加
  • 静的変数宣言が重複する可能性があることを考慮して文言を更新

などです。

P3589R2 C++ Profiles: The Framework

フレームワークとしてのプロファイル機能の概説文書。

以前の記事を参照

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

  • インクルードされたソースファイルを除外するメカニズムを追加
  • プロファイルが教育目的にも役立つことを明確化

などです。

P3617R0 std::meta::reflect_constant_{array,string}

定数文字列・配列のリフレクションを返すAPIの提案。

この提案は以前にP3491で提案されていたdefine_static_string()/define_static_array()の拡張となる機能の提案です。

// P3491で提案されているAPIの一部

template <ranges::input_range R> // only if the value_type is char or char8_t
consteval auto define_static_string(R&& r) -> ranges::range_value_t<R> const*;

template <ranges::input_range R>
consteval auto define_static_array(R&& r) -> span<ranges::range_value_t<R> const>;

まず、define_static_string(r)は文字範囲rから静的文字列を作成しその先頭ポインタを返す関数です。しかし、ポインタを返すことによってC++20からのNTTP文字列のパターンで使用できない問題が指摘されました

// NTTPで使用可能な固定長文字列型
template <size_t N>
struct FixedString {
  char data[N] = {};

  constexpr FixedString(char const(&str)[N]) {
    std::ranges::copy(str, str+N, data);
  }
};

template <FixedString S>
struct Test { };

これの使用例と問題の例はつぎのものです

using A = Test<"foo">;  // ok、文字列リテラルはそのまま渡せる
using B = [: substitute(^^Test, {reflect_constant("foo"sv)}) :];  // ng、string_viewはNTTPとして使用できない
using C = Test<define_static_string("foo")>;  // ng、define_static_string()の戻り値からだと長さが取得できない
using D = [: substitute(^^Test, {reflect_constant(define_static_string("foo"))}) :];  // ng、同じく長さが取得できない

substitute(template_refr, refle...)はテンプレートのリフレクションtemplate_refrのテンプレート引数に対してrefle...を順番に引数として与えてインスタンス化したもののリフレクションを返すもので、reflect_constant(arg)argによる定数のリフレクションを返すものです。

ここでは、substitute()の戻り値をスプライス[: refl :])することでクラス型(ここではすべてTest<"...">型)として定義しようとています。

しかし、現状ではA以外は全てエラーとなります。define_static_string()の問題点は文字列ポインタ(const char*)を返すためにFixedStringのCTADに失敗(文字列配列char[N]ではないため)することにあります。ここで重要なことは、substituteスプライスを使用しているように、リフレクションによってこのTest型の実体を生成する、ような生成コードを書けないことにあります。

次に、define_static_array(r)は範囲rから静的配列を作成しその参照を返す関数です。

template <class T>
auto f(const T& var) -> void {
  template for (constexpr auto M : define_static_array(nsdms(^^T))) {
    do_something_with(var.[:M:]);
  }
}

nsdms(class_refl)はクラス型のリフレクションclass_reflから非静的メンバ変数のリフレクションの配列(std::vector<std::meta::info>)を得るものです(おそらくP2996R12のnonstatic_data_members_of()の略記)。展開ステートメントはその処理の都合上nsdms()の返すstd::vectorを直接イテレーションできないため、define_static_array()を通して静的配列に変換して展開しています(このような使い方がP3491のモチベーションです)。

このコードでは、f()内部でTの非静的メンバ変数全てによってdo_something_with()を呼び出していくコードを生成します。

しかし、別の状況では1ステートメント内で全てのメンバ(nsdms(^^T)の戻り値)を処理したい場合があります。展開ステートメントはそのループの結果としてステートメントを生成していくため、このような用途に使用できません。この場合、C++26からの仕様である構造化束縛のパック導入とconstexpr構造化束縛を使用するとシンプルに書くことができます。

// 提案文書より、配列の構造体クラスの例
template <class T>
struct SoaVector {
  // 配列ストレージ
  Pointers pointers_;
  ...
  
  // Pointers型の非静的メンバ(=各配列)のリフレクションを列挙
  // これは`span<const info>`型になる
  static constexpr auto ptr_mems = define_static_array(nsdms(^^Pointers));
  ...

  auto operator[](size_t idx) const -> T {
    // 構造化束縛でのパック導入とconstexpr構造化束縛
    constexpr auto [...M] = [: ptr_mems :]; // ng、spanをパックに展開できない

    // メンバ配列からidx番目の要素を引き当てて配列で返す
    // 畳み込み式によって一文で展開する
    return T{pointers_.[:M:][idx]...};
  }
};

しかし、define_static_array()はリフレクションの配列の参照(span<const info>)を返してしまい、これはパックに変換できないため、この用途には使用できません。必要なのはリフレクションの配列のリフレクション(型としてはinfoになる)を返す何かですが、P3491にはそれはありません。

この提案は、P3491をベースとしつつこれら2つの用途に沿うようなAPIを追加するものです。

// P3491で提案されているAPIの一部

template <ranges::input_range R> // only if the value_type is char or char8_t
consteval auto define_static_string(R&& r) -> ranges::range_value_t<R> const*;

template <ranges::input_range R>
consteval auto define_static_array(R&& r) -> span<ranges::range_value_t<R> const>;

// この提案のAPI

template <ranges::input_range R>
consteval info reflect_constant_string(R&& r);

template <ranges::input_range R>
consteval info reflect_constant_array(R&& r);

どちらも、受け取った範囲rから生成した定数文字列/配列のリフレクションを返すものです。これらの関数を用いると先ほど問題として挙げた例をシンプルに解消することができます。

固定長文字列クラス型のNTTPをリフレクションによって生成する例

using E = Test<[:reflect_constant_string("foo"):]>; // ok
using F = [:substitute(^^Test, {reflect_constant_string("foo")}):]; // ok

reflect_constant_string()は定数文字列へのリフレクションを返すため、それを用いてTest型を生成するリフレクションコードを記述することができます。

SoaVectorの添え字演算子の実装例

// 提案文書より、配列の構造体クラスの例
template <class T>
struct SoaVector {
  // 配列ストレージ
  Pointers pointers_;
  ...
  
  // Pointers型の非静的メンバ(=各配列)のリフレクションを列挙
  // これは`std::array<info, N>`型になる
  static constexpr auto ptr_mems = reflect_constant_array(nsdms(^^Pointers)); // 👈
  ...

  auto operator[](size_t idx) const -> T {
    // 構造化束縛でのパック導入とconstexpr構造化束縛
    constexpr auto [...M] = [: ptr_mems :]; // ok、std::arrayはタプルプロトコルによってパックへ変換できる

    // メンバ配列からidx番目の要素を引き当てて配列で返す
    // 畳み込み式によって一文で展開する
    return T{pointers_.[:M:][idx]...};
  }
};

define_static_array()がそうだったように、この関数もコンパイラの特別なサポートを必要とせずに手書きすることができます。しかも、define_static_array()の実装を少し移動するだけでよく、文言の変更的にも少なく済みます。

template <typename T, T... Vs>
inline constexpr T __fixed_array[sizeof...(Vs)]{Vs...};

template <ranges::input_range R>
consteval auto reflect_constant_array(R&& r) -> meta::info {
  auto args = vector<meta::info>{
    ^^ranges::range_value_t<R>};
  for (auto&& elem : r) {
    args.push_back(meta::reflect_constant(elem));
  }
  return substitute(^^__fixed_array, args);
}

template <ranges::input_range R>
consteval auto define_static_array(R&& r)
    -> span<ranges::range_value_t<R> const>
{
  using T = ranges::range_value_t<R>;

  // produce the array
  auto array = reflect_constant_array(r);

  // turn the array into a span
  return span<T const>(
      extract<T const*>(array),
      extent(type_of(array)));
}

したがってC++26に必須の機能というわけではありません。それでも、このようなものは人々が再発明することが想定できることや、P3491の文言をベースに小さな変更で済むこと、コンパイラ支援によって__fixed_arrayのようなバック配列を(文字列リテラルinitializer_listのように)共通化することができるなどの理由によりC++26の機能として提案しています。

この提案はEWGのレビューで承認され、P3491R2にマージされています。そして、P3491R2は6月全体会議で承認されC++26にマージされています。

P3631R0 Cleaning up the trivial relocation APIs in C++26

リロケーション関連のライブラリAPIを整理する提案。

P2786R13ではリロケーションに関する言語規定と最小のライブラリAPIが導入され、P3516では未初期化メモリに対するリロケーションアルゴリズムが提案されています。P2786はすでにC++26にマージ済みで、P3516は(この提案が書かれた時点で)LEWGの設計承認を獲得しているようです。

ただし、P3516の導入によってP2786で導入されたライブラリAPIには不要になるものがあるため整理の必要が認識されていました。この提案はその整理を行うものです。

この提案無しの現状のリロケーション関連ライブラリAPIは次のようになっています

// P2786 APIs (in draft)
template <class T>
T* trivially_relocate(T* first, T* last, T* result); // freestanding

template <class T>
constexpr T* relocate(T* first, T* last, T* result); // freestanding

// P3516 APIs (design approved)
template<class T>
 requires relocatable-from<T, T>
  constexpr T* relocate_at(T* dest, T* source)
    noexcept(is_nothrow_relocatable_v<T>);

template<class NoThrowForwardIterator1, class NoThrowForwardIterator2>
  constexpr NoThrowForwardIterator2
    uninitialized_relocate(NoThrowForwardIterator1 first,
                           NoThrowForwardIterator1 last,
                           NoThrowForwardIterator2 result); // freestanding

template<class NoThrowForwardIterator1, class Size, class NoThrowForwardIterator2>
  constexpr pair<NoThrowForwardIterator1, NoThrowForwardIterator2>
    uninitialized_relocate_n(NoThrowForwardIterator1 first,
                             Size n,
                             NoThrowForwardIterator2 result); // freestanding

template<class NoThrowBidirectionalIterator1, class NoThrowBidirectionalIterator2>
  constexpr NoThrowBidirectionalIterator2
    uninitialized_relocate_backward(NoThrowBidirectionalIterator1 first,
                                    NoThrowBidirectionalIterator1 last,
                                    NoThrowBidirectionalIterator2 result); // freestanding

// execution_policyを取るオーバーロード
...

namespace ranges {
  // range版オーバーロード
  ...
}

この提案ではこのうちstd::relocate()を削除しようとしています。

この関数はP3516で提案されている未初期化メモリに対するアルゴリズムの機能制限版であり、P3516が提出されるよりも前に発明され残されていたものです。両者の違いはstd::relocate()が入力範囲と出力範囲のオーバーラップチェックを行い、前方/後方のどちらにリロケーションすべきかを判定する点にありますが、P3516のアルゴリズムではリロケーションすべき方向の決定を_backwardオーバーロードで指定しており、このAPIは既存の未初期化メモリに対するアルゴリズムと一貫しています。

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

P3649R0 A principled approach to safety profiles

初期のプロファイル機能については安全性プロファイルに注力すべきとする提案。

形式手法における安全性とは、プログラム全体にわたって特定の不変条件が常に満たされることを保証することを言います。このことを安全性不変条件とここでは呼んでいます。この安全性は、静的解析(コンパイル時の検査)と実行時検査を組み合わせることで達成することができ、どちらの検査も100%の安全性を保証できない場合があります(静的解析で安全性を証明できないため拒否せざるを得ない構文や、実行時検査コストが許容できない場合など)。

Rustではこれに対して、Safe RustとUnsafe Rustの2つの言語を内包することで対処しています。Safe Rustは特定の安全性不変条件が保証されているため、Safe Rustで書かれたプログラムはその保証によって排除されるバグが混入していないことが保証されます。Unsafe RustはSafe Rustに対してそれが提供する安全性不変条件が必ずしも満たされないような機能を追加したもので、この部分はCやC++と同等程度の保証だけを提供しています。ただし、Unsafe Rustはunsafeキーワードによってマーキングが必要なオプトインとなっています。

このアプローチはセキュリティが重要なプログラムの記述において効果的であることが実証されており、プログラムの大部分をSafe Rustで記述し必要な部分だけUnsafe Rustを使用することで、Unsafe Rustの部分のバグだけが問題となります。Unsafe Rust部分にバグがなければ、Safe Rustの保証によってそのプログラムにはある特定の種類のバグがないことが保証されます。

P3589R1などで提唱されているプロファイル機能はC++コードにおける特定のチェックを強制するフレームワークを提案しています。これを用いれば、特定の安全性不変条件の保証を強制する事が可能です(RustのSafeがデフォルトにたいして、Unsafeがデフォルトになってしまうものの)。

P3081R2では安全性を主眼に置いたその具体的なプロファイルの初期セットが提案されていますが、そこでは従来のイディオムやテクニックの延長にあるようなヒューリスティックやスタイル規則などを強制しようとするもので、ここで述べている安全性不変条件が満たされることを保証する類のものではりません。

これに対してこの提案は、P3589R1のフレームワークをベースとするプロファイル機能の初期セットにおいては、安全性プロファイル(特定の安全性不変条件を保証するもの)の標準化にのみ焦点をあてる事を提案するものです。

具体的には、全ての標準プロファイルPは関連する不変条件を持ちます。プロファイルPを有効化することで、この不変条件がPの適用領域内で常に満たされることが保証されます。これは静的解析と実行時検査の組み合わせによって達成され、その不変条件を保証できないような操作は禁止(コンパイルエラー)されます。

これは、C++にある種のSafeブロックを導入するものです。これによって、Pの不変条件が成り立たないのはプロファイルPが有効化されていないコードのみになります。最初はこのような安全の島は小さいですが、時間の経過とともに多くのコードでプロファイルが有効化されることで増加していき、やがて安全の大陸になっていくでしょう。このアプローチはデフォルトが逆ではあるもののRustのアプローチと同じであり、その有効性は実証されています。

安全性プロファイルが安全性を保証するためには、対応する不変条件がほぼ満たされることを強制するのではなく必ず満たされることを強制する必要があります。不変条件を満たすことのできないコードは拒否されるか、実行時検査を伴わなければなりません。これが達成されない場合、安全性不変条件は単なる安全性ヒューリスティックにすぎません。すなわち、不変条件の保証においてFN(false negatives)は許容されません。

安全性ヒューリスティックは現在のC++がすでに行っていることでもあり、これが不十分であるためC++は安全性・セキュリティに関する規制圧力に晒されています。安全性ヒューリスティックのアプローチを継続することはC++を安全な言語にすることはなく、具体的な保証のないヒューリスティックが得られるだけの機能のために既存コードを更新する人は多くないでしょう。

提案では初期化プロファイル(全ての変数の初期化を強制する)プロファイルの例を紹介しています。そのなかで、ユーザー定義の関数などあるプロファイルにおける不変条件を保証できないものに対するアノテーションとして[[profiles::prohibit_in(P)]]の必要性を提案しています。

// ユーザー定義malloc()-like関数
[[profiles::prohibit_in(initialization)]] void* my_malloc(std::size_t);

[[profiles::prohibit_in(P)]]の意味は、「これ(関数やクラスなど)はPの不変条件を破る可能性があるためPによって禁止される必要がある」ことを意味するアノテーションです。対して、[[profiles::suppress(P)]](P3589にすでにある)は「これは不変条件を保証できないためPによって禁止されているものの、この特定のコンテキストでは保証可能であるため一時的に無効化する」ことを意味するもので、異なるものです。

// この関数は初期化済みの領域を返すため、initializationプロファイルに従っている
void* zeroed_malloc(std::size_t size) {
  // すぐ後で初期化するため、initializedプロファイルを抑制する
  [[profiles::suppress(initialized)]] auto ptr = std::malloc(size);
  std::memset(ptr, 0, size);

  return ptr;
}

P3655R1 zstring_view

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

以前の記事を参照

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

  • Githubでの使用に関する数値情報を更新
  • SG16のフィードバックを適用
  • string-like引数を取る関数の競合するオーバーロードについて、特定のオーバーロードの優先順位付けまたは選択するための方法を追加
  • 議論を容易にするために、コンストラクタにナンバリングを追加
  • char_traitsとNULに関するアンケートを追加

などです。

P3658R1 Adjust identifier following new Unicode recommendations

識別子に使用可能な文字の範囲を広げる提案。

以前の記事を参照

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

  • CWG 2843を同時解決しようとしていた部分を削除
  • ǃ!ではないことを明確にするために例の表に注記を追加し、Q&Aのspoofingに関する議論を拡張して既に導入済みの緩和策を提示

などです。

P3663R1 Future-proof submdspan-mapping

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

以前の記事を参照

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

  • canonical-iceに、入力がIndexType型の値として表現可能であるというMandatesとPreconditionsを追加
  • subtract-iceを修正し、constant_wrapper演算を適用する
    • まずcanonical-iceを適用し、その後結果のconstant_wrapperで減算を実行する
  • pairスライスに対しては、get()ではなく構造化束縛を利用する
  • submdspanは特定のsubmdspan_mapping(input.mapping(), slices...)の呼び出しが的確であることを実際には制約できない
    • これは、構造化束縛宣言が適格であることを制約する方法が無いため
    • 代わりに、submdspanの制約をsubmdspan_mapping(input.mapping(), full_extent, ..., full_extent)が適格であることに変更し、submdspan_mapping(input.mapping(), slices...)が適格であることだけをMandateするようにする
      • この制約はmapping-sliceable-with-full-extentsという説明専用コンセプトで表現される
  • スライス可能なマッピングに関する要件についてのセクションを更新
    • sliceable layout mapping要件と、標準マッピングに関する例外を追加
  • <mdspan>submdspan_canonicalize_slices()を追加
  • 文言を含まないAbstractとその他セクションを更新し、これらの変更を実際に提案していることを明記
  • submdspan_canonicalize_slices()が説明専用ではない理由を説明するためのセクションを追加
  • P2781 (std::constant_wrapper) の参照をR8に更新
  • 標準レイアウトマッピングは、正規化されたスライス型だけではなくすべての有効なスライス型を受け入れる可能性があることを説明する注記を追加
    • 正規化によるパフォーマンスへの懸念に対処するため
  • 正規化済スライス型に関する要求事項記述時の重複を避けるため、check-static-bounds()を定義

などです。

P3668R1 Defaulting Postfix Increment and Decrement Operations

後置インクリメント/デクリメント演算子default定義できるようにする提案。

以前の記事を参照

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

  • デフォルト関数ですでに実行可能な機能についての説明を追加
  • テンプレートのデフォルト化によってボイラープレートをさらに削減できる可能性について説明を追加
  • 文言のtypo修正

などです。

P3669R1 Non-Blocking Support for std::execution

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

以前の記事を参照

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

  • concurrent-op-stateを削除
  • try_scheduleを追加

などです。

P3670R1 Pack Indexing for Template Names

テンプレートパックに対するインデックスアクセスを許可する提案。

以前の記事を参照

このリビジョンでの変更は例を修正したことのみです。

P3676R0 Enhanced inline Keyword with Configurable Inlining Levels

様々なレベルのinline指定を可能にする提案。

関数に対してinlineを指定してもその関数をインライン展開してほしいという要望をコンパイラに表明するだけで、インライン化が必ず行われるわけではありません。そのため、パフォーマンスを重視するプログラムやライブラリでは関数のインライン化を強制するためにALWAYS_INLINEマクロのようなものを良く使用します。これは、各コンパイラ固有の機能などをマクロでラップして疑似的にポータブルにしたものです。

また逆に、インライン展開無効化するために同様のアプローチが取られる場合もあります。

この提案は、現在のinline指定ではサポートできていないこれらのユースケースを標準で提供しようとするものです。

提案ではinline指定構文を拡張して整数を渡せるようにすることで、プログラマが望むインライン展開のレベルをコンパイラに伝達できるようにします。提案されているレベルは3種類です

  • inline(0): インライン展開しない
  • inline(1): 現在のinlineと同等(ヒントとしてのinline指定)
  • inline(2): 常にインライン展開する
inline void f1() { ... }    // 現在のinlineと同等
inline(1) void f2() { ... } // 現在のinlineと同等
inline(2) void f3() { ... } // 常にインライン展開する
inline(0) void f4() { ... } // インライン展開しない

inline(expr)にはnoexceptexplicitbool値を渡す場合と同じく式によって指定することができます。これによってライブラリの利用ユーザーがインライン展開のレベルを選択できるようになり、インライン展開のレベルを調整するためにマクロによる条件付きコンパイルに頼る必要がなくなります。

template <int Mode>
inline(Mode) int heavy_function(int x) {
  return complex_calculation(x);
}

int forced = heavy_function<2>(10);     // strong request to always inline
int optional = heavy_function<1>(10);   // normal inline hint
int none = heavy_function<0>(10);       // request no inlining

提案ではさらに、これら3段階のインライン展開レベルを指定する定数を標準ライブラリで提供しておくことも提案しています。名前は、std::noinline, std::normal_inline, std::always_inlineの3つで、これによって単なる整数値よりも意図が明瞭になります。

inline(std::always_inline) int add(int a, int b) {
  return a + b;
}

inline(std::noinline) int slow_function(int x) {
  // 一定程度複雑なロジックはインライン化しないようにしたい
  return complex_calculation(x);
}

この提案の後でも現在のinline指定はそのまま使用可能であり、inline(1)と同等として扱われることで既存コードの動作は変更されません。

さらに、モジュールからはマクロをエクスポートできないことによって、ALWAYS_INLINEのようなマクロを使用している既存のライブラリをモジュール対応させようとする場合に問題となることが知られていますが、この提案のソリューションは完全にマクロフリーになるためこの問題を解消でき、より高パフォーマンスのライブラリをモジュール化しやすくなります。

この提案の著者の方はGlazeという高パフォーマンスJSONライブラリの開発者の方で、GlazeライブラリにおいてALWAYS_INLINEを使用するとパフォーマンスを10~30%向上させられるもののコンパイル時間が増大する(MSVCで最大10倍)ことを経験しており、この提案の機能によってライブラリ利用者がパフォーマンスとビルド時間(+バイナリサイズ)のどちらを選択するかをコンパイル時のオプションとして提供できるようになる、と述べています。

P3677R0 Preserving LC_CTYPE at program start for UTF-8 locales

プログラム起動時のデフォルトのロケール設定で環境のUTF-8エンコーディングを保持できるようにする提案。

C言語ロケールのデフォルトはsetlocale(LC_ALL, "C")が呼び出された場合と同じ振る舞いをすることが規定されており、C(およびC++)のプログラムは開始時にこれが呼び出されたかのように動作します。"C"ロケールエンコーディングについては規定されていませんが、多くの場合はASCIIエンコーディングが使用されます。

ロケールは多言語ローカライズに関する側面(プログラムが想定する環境の言語・文化圏)と文字エンコーディングに関する側面(プログラムが想定する環境のエンコーディング)が融合される形で設計されてしまっており、"C"ロケールは主にローカライズに関して何も指定しない(プログラムをローカライズフリーにする)デフォルトのモードを志向したものです。"C"ロケールはC標準内ではほぼ何も規定されておらず(ctypeの関数の動作のみを規定している)、主にPOSIX規格で定義されています。ただし、"C"ロケールエンコーディングの側面についてはPOSIXでも規定されていません。

しかし、"C"ロケールエンコーディングとしてはASCIIがほぼ実装のデファクトとなっているため、"C"ロケールを指定するとローカライズに関してデフォルトを指定するだけではなく、エンコーディングをASCIIに設定することになってしまいます。これによって、現代のほとんどのシステムの環境エンコーディングUTF-8であるにもかかわらず、C/C++のプログラムのエンコーディングはASCIIに設定されてしまい、これが文字化けの原因にもなっています。

このような問題は既によく知られており、ローカライズに関しては"C"ロケールを使用し、エンコーディングとしてUTF-8を使用するロケール指定C.UTF-8ロケールが一部のlinuxディストリビューションGlibcで提供されるようになっています。これを使用するとロケールローカライズに関してを指定せずに(ローカライズフリーを保ったまま)環境エンコーディングだけを指定できます(en_US.UTF-8などの指定はローカライズも指定してしまう)。ただし、Cの標準では"C"ロケールがデフォルトとして規定されたままになっているため完全には問題が解決されていません。

この提案は、このCの標準ライブラリが規定するデフォルトのロケールに、可能であればC.UTF-8ロケールを使用することを提案するものです。これによって、C/C++のプログラムが起動時に環境のUTF-8エンコーディングを保持できるようになります。

提案では、次の条件を満たす場合にロケールフリーなUTF-8ロケールC.UTF-8ロケール相当)をデフォルトのロケールとして使用するようにします

  1. C UTF-8 ロケールが存在する
  2. 環境に関連付けられたロケールsetlocale(LC_CTYPE, ""))もUTF-8ロケールである

曖昧な書き方になっているのは、C.UTF-8ロケールが必ずしも存在しない場合があるためです。また、2のチェックによって環境のエンコーディングを保持するようにデフォルトを設定するようにしています。

とはいえこの提案による影響があるのはC.UTF-8ロケールが存在する環境のみです。また設定されても影響を受けるのは一部の変換関数(mbtowc()fputws()など)と環境エンコーディングクエリのみが影響を受け、ctypeの関数の動作には影響しません。Windowsの場合はWindowsAPIがローカライズエンコーディングを分離して扱っていることやデフォルトのエンコーディングUTF-8ではないことなどから影響を受けません。

この提案は正確にはC標準(Cライブラリ)に対してのもので、C++にも影響があることからSG16/SG22に対しても提出されているものです。

P3678R0 Arbitrary attributes in define_aggregate

リフレクションAPIdefine_aggregate()において、非静的メンバ変数への属性適用方法を改善する提案。

define_aggregate()は集成体型の定義を生成できる関数です。この関数は不完全クラス型のリフレクションと、その非静的メンバ変数のプロパティを指定するオプション列を受け取って、集成体型を定義します。

template<typename T> struct S;
constexpr auto s_int_refl = define_aggregate(^^S<int>, {
  data_member_spec(^^int, {.name="i", .alignment=64}),
  data_member_spec(^^int, {.name=u8"こんにち"}),
});

// S<int>はこのような定義と等価に定義されている
template<> struct S<int> {
  alignas(64) int i;
              int こんにち;
};

この関数の呼び出しは定義行い、戻り値はそのクラス型のリフレクションです。

define_aggregate()は現在のところ非静的メンバを定義する能力しかなく、data_member_spec()という関数を通してdata_member_options構造体によってメンバ変数の定義を調整します。data_member_optionsは次のような構造体です

struct data_member_options {
  struct name_type {
    template <typename T>
      requires constructible_from<u8string, T>
    consteval name_type(T &&);

    template <typename T>
      requires constructible_from<string, T>
    consteval name_type(T &&);
  };

  optional<name_type> name;
  optional<int> alignment;
  optional<int> bit_width;
  bool no_unique_address = false;
};

現時点では属性のリフレクションを取得できないため、ここでは属性によるプロパティの指定を個別のbool値によって指定しています(alignmentno_unique_address)。しかし属性として有効なのはこの2つだけではなく、今後も増える可能性があります。また、P3385R4(標準属性のリフレクションを取得可能にする提案)がC++26に間に合いそうな進行状況です。

この提案では、define_aggregate()において非静的メンバ変数を定義する際に属性の指定については属性リフレクションの範囲によって指定できるようにする事を提案しています。このために、data_member_options構造体のメンバを調整します。

struct data_member_options {
  struct name_type {
    template <typename T> requires constructible_from<u8string, T>
      consteval name_type(T &&);

    template <typename T> requires constructible_from<string, T>
      consteval name_type(T &&);
  };

  optional<name_type> name;
  optional<int> alignment;
  optional<int> bit_width;

  // 追加・変更
  //bool no_unique_address = false;
  [[deprecated]] bool no_unique_address = false;
  vector<info> attributes;
};

define_aggregate()ではこのattributesに指定された属性のリフレクションから定義する非静的メンバ変数の宣言に属性を付与するようにします。これにより、[[no_unique_address]]だけではなく任意の属性を一貫した構文によって指定できるようになります。

ただしアライメント指定に関しては、alignasがそのほかの属性と異なるものでありこのリフレクションを取得する拡張は提案されていないため、現状維持しています。

提案文書より、この提案の後のdefine_aggregate()使用例

struct User;
constexpr auto r = define_aggregate(^^User, {
  data_member_spec(^^string, {
    .name = "uuidV4",
    .attributes = { ^^[[deprecated("Use UUIDV5 instead")]], ^^[[maybe_unused]] }
  }),
  data_member_spec(^^string, {
    .name = "uuidV5"
  }),
});

// ↑はこのような構造体定義と等価な定義を生成する
struct User {
  [[deprecated("Use UUIDV5 instead"), maybe_unused]] string uuidV4;
  string uuidV5;
};

この提案はSG7のレビューでP3385にマージする事に合意され、そちらにマージされたようです。

P3679R0 SFINAEable constexpr exceptions

コンセプトの制約評価時の例外送出が、その制約を満たさないこととして扱われるようにする提案。

C++26ではリフレクションにおけるエラー報告メカニズムのために、定数式における例外送出とそのcatchがサポートされるようになります。そこでは、定数式中で送出され、キャッチされなかった例外はコンパイルエラーを起こします。

定数式中での例外送出は、定数評価中に呼び出された任意の式・関数内でthrowによって行われる可能性があり、コンセプトの制約式の評価中にも例外送出が発生する可能性があります。この場合、例外による脱出がコンセプトの制約式まで到達するとコンパイルエラーになります。

template <typename... Ts>
constexpr bool throw_reason(std::format_string<Ts...> f, Ts &&... args) {
  throw exception{std::format(f, std::forward<Ts>(args)...)}; // std::format()が定数式で使用可能であるとする
}

template <typename T>
concept fits_into_four_bytes = 
  sizeof(T) <= 4 ||
  throw_reason("provided type `{}` is larger than 4 bytes!", display_string_of(^^T));

int overload(special_type value); // #1、special_type専用オーバーロード
int overload(fits_into_four_bytes auto value);  // #2、4バイト以下の型に制約されたオーバーロード
int overload(auto &&);  // #3、最も制約の緩い(というか無い)オーバーロード

int main() {
    overload(special_type{}); // ok、#1が選択される
    overload(42);             // ok、#2が選択される
    overload(new int);        // ng、ポインタ型は8バイトであり、`throw_reason()`を制約式として評価することで例外が送出される
}

この提案は、制約式の評価中に例外が送出された場合、その例外送出はその制約を満たさないこととして扱われるようにすることを提案するものです。これによって上記の最後の例はコンパイルエラーにならずに残りのオーバーロードの候補を探しに行くようになります。

int main() {
    overload(special_type{}); // ok、#1が選択される
    overload(42);             // ok、#2が選択される
    overload(new int);        // ok、#3が選択される
}

そして、この場合に最も外側の制約式まで達した例外のメッセージ(.what())をコンパイラの警告/エラーメッセージとして出力するようにすることで、コンセプトの制約チェック時のエラーメッセージをかなり改善することができます。

P3681R0 char_traits: Stop the bleeding

std::char_traitsを使用しないようにしていく提案。

std::char_traitsは標準ライブラリの文字列型(std::stringstd::string_viewなど)で使用される文字型に関する特性を文字列型に対して調整するためのクラスです。基本的には2つ目のテンプレートパラメータで渡して使用しますが、ユーザーがこれをカスタマイズすることはかなり稀だと思われます。

char_traitsには使用機会が乏しい以外にもいくつかの問題があります

  • マルチバイトエンコーディングの不適切な処理
    • char_traitsの全ての要件は基本的に単一の値単位での操作に特化して規定されており、comparecopyなどのシーケンスを操作するメンバー関数であっても例外ではない。これにより、マルチバイト文字列の変換に使用すると正しく動作しない可能性がある
    • シフト状態エンコーディングや非UTFマルチバイトエンコーディングの場合、比較も不正確になる可能性がある
  • コードユニットと無関係な機能の提供
    • assignlengthmovecopyのデフォルト以外の実装のユースケースを想定するのが困難
    • not_eofeofなどの機能はiostream関連機能にのみ有用であり、バイト読み取りとエンコーディングの懸念を混同している
      • これらのデフォルト以外の実装は不明
  • 肥大化(ブロート)の促進
    • char_traitsが存在することでマングル名が肥大化する場合がある
      • 例えばMSVC ABIの場合、std::unordered_map<std::zstring_view, std::zstring_view>では無い場合と比べてマングル名が25%大きくなる
      • Itanium ABIの場合は重複する型名を圧縮するため、それほど問題にならない
    • char_traitsに有用性がなければ、これは正当化できない
  • テキスト関連概念の教育の悪化
    • 現代のエンコーディングとの互換性がなく、テキストエンコーディングを混同する設計になっていることで、char_traitsは不適切なテキスト処理を助長している
    • 値のシーケンスを操作するAPIを公開するように設計されているstd::string等のAPIと整合していない

これらのことからこの提案は、今後char_traitsを使用しないようにすることを提案しています。特に、提案中のzstring_viewにおいて使用しないようにすることを推奨しています。また、今後のアクションとして次の事を提示しています

  1. 新しい文字列関連型でchar_traitsを使用しない
    • ただし、既存文字列型からの変換時にはchar_traitsをどう扱うかで問題がある
  2. char_traitsを不要にする
    • char_traitsの唯一の価値は、null終端文字列の長さを計算する機能
    • ただしそれも、char_traits<T>::length(str)がそのための最も直観的な方法ではない
    • 全ての文字型について、strlen()constexprオーバーロードが必要
  3. char_traitsの不適切なユースケースを置き換える機能を追加する
    • char_traitsが使用されることが比較的多いユースケースとして、文字列の大文字小文字を区別しない比較がある
      • 前述のように、これはマルチバイト文字列で機能しない
    • このようなユースケースは正当であり、現在の標準ライブラリではこれが簡単にできないことが問題であるため、正しく機能するより簡易な方法を提供する必要がある
  4. ユーザー提供のTraits引数の非推奨化
    • std::basic_string<CharT, Traits, Alloc>において、Traits引数がデフォルトと異なる場合を非推奨とする
      • 例えばstd::basic_string<char, MyTrait<char>>は非推奨
    • これをすべてのTraits引数を持つ文字列型に対して行う

より長期的には、ユーザー提供のTraits引数を与えられた文字列型をill-formedとすることを挙げていますが、後方互換性の懸念から削除はできないだろうとしています。この提案ではあくまで、char_traitsを不要なものとして認識して使用しないようにしていくことを推奨しているもので、削除しようとはしていません。

P3682R0 Remove std::execution::split

std::execution::splitアルゴリズムを削除する提案。

std::execution::splitC++26で導入されたstd::executionの一部であるsenderアルゴリズムで、senderによる処理グラフを分岐させるためのものです。sendersplitに渡すと、そのsenderを複数回呼び出し可能にしたsender(multi-shot sender)が得られます。これをwhel_allなどに渡すことで処理のフォークのようなことが可能になります。

std::execution::splitはシングルショットのsnderをマルチショットのsenderに変換するためのものですが、実際にはマルチショットのsenderに適用すると恒等変換にはならず、入力のsenderがマルチショットかどうかによらず同じ変換を行い、共有状態に関連付けられたsenderを返します。このsplitsenderstartすると、次のようなことが起こります

  1. 共有状態が完了状態を保持しているかどうかを調べ、完了の場合はその状態を用いてsplitsenderの完了とする
  2. そうではない場合、共有状態が進行中の操作を保持しているかを調べ、その場合はその完了を待機した後その結果をもってsplitsenderの完了する
  3. そうではない場合、splitの入力senderを用意したreceiverと接続して開始する
  4. 3の処理が完了したら、その結果を共有状態に格納し、2で待機していたすべての処理を起動する

このような動作のために、splitにはいくつか問題があります。

  1. メモリの動的確保を行う
    • splitsender初期化時に、共有状態の作成のために動的な確保を行う
      • 次のものを保持する
        • 入力sender
        • 入力senderをコピーして作成されたすべてのsender
        • これらのいずれかがconnectされて生成されるoperation_state
    • これはsender作成時に行われるため、接続したreceiverの環境のアロケータによってカスタマイズできない
  2. 共同所有権
    • splitに関連付けられた状態は共有され、これの保護のために参照カウント等の仕組みが必要になる
      • これは本来std::executionの提供するstructured concurrencyによって回避できるはずのもの
    • 共有状態はsplitsenderが接続された後のoperation_stateの生存期間と紐づけられる
      • これにより、途中にsplitがあるsenderチェーン全体の生存期間が終了するまで維持されている
  3. 貪欲な実行
    • 通常senderチェーン遅延評価されるが、splitは見ようによってはそうではない
    • splitsenderを最初に起動するものから見ると遅延実行だが、2回目以降に起動するものから見ると既に起動済みであり、遅延実行ではない
  4. 名前
    • 非常に短い名前だが、それに反して非常に複雑で難解な操作になっている

これらの問題から、この提案はstd::execution::splitを削除することを提案しています。提案には、splitとほぼ同等に振舞いつつ上記のような問題を回避する実装が掲載されています。

// splitによる処理のフォークの記述例

// フォーク対象の処理
std::execution::sender auto shared = /* ... */; 
// split sender
std::execution::sender auto split = std::execution::split(std::move(shared)); 

// フォーク後の処理
std::execution::sender auto a = split | /* ... */; 
std::execution::sender auto b = split | /* ... */; 
std::execution::sender auto c = std::move(split) | /* ... */; 

// 実行(どう実行されるかはscheduler次第)
(void)std::this_thread::sync_wait( 
  std::execution::when_all( 
    std::move(a), 
    std::move(b), 
    std::move(c))); 

このコードには先ほど述べたような問題(共有状態の存在やそのための動的確保など)があります。

// splitを使わない処理のフォークの記述例

// フォーク対象の処理
std::execution::sender auto shared = /* ... */;

(void)std::this_thread::sync_wait( 
  std::move(shared) | std::execution::let_value([](auto&&... values) {
    // let_valueアルゴリズム内で、先行操作を共有するフォーク後の処理を定義する
    std::execution::sender auto a = std::execution::just(std::ref(values)...) 
      | /* ... */; 
    std::execution::sender auto b = std::execution::just(std::ref(values)...) 
      | /* ... */; 
    std::execution::sender auto c = std::execution::just(std::ref(values)...) 
      | /* ... */; 
    
    // let_valueアルゴリズム内で実行
    return std::execution::when_all( 
      std::move(a), 
      std::move(b), 
      std::move(c)); 
  }));

こちらのコードはC++26のstd::executionで提供される他のアルゴリズムのみを使用しており、splitを使用しないで同等の処理を実現しています。このコードでは共有状態が存在せず、動的確保も発生しません。

この提案は、2025年6月の全体会議で承認され、C++26から削除されています。

P3685R0 Rename async_scope_token

std::execution::async_scope_tokenstd::execution::scope_tokenにリネームする提案。

async_scope_tokenはP3149R9で提案されているもので、非同期スコープの非所有ハンドルを表現するコンセプトです。この命名については次のような異論があるようです

  1. P2300のエンティティには他にasync_プリフィックスを持つものがない
    • std::execution内のものは非同期対応が基本であるため、名前によってそれを区別する必要がない
  2. P3149では"async scope"という概念が繰り返し登場するが、関連する他のものはasyncを使用せずscopeを優先的に使用している(counting_scopeなど)

この提案は、これらの理由によりasync_scope_tokenscope_token命名変更しようとするものです。

この提案はLEWGのレビューで受け入れられ、P3149に直接マージされたようです。

P3686R0 Allow named modules to export macros

名前付きモジュールからマクロをエクスポートできるようにする提案。

ユーザーが定義した(module宣言によって定義された)モジュールの事を名前付きモジュールと呼びますが、名前付きモジュールからはマクロをエクスポートする方法が用意されていません。これは意図的にそう設計されたものです。

しかし、この提案では名前付きモジュールからマクロをエクスポートすることができるとメリットがある場合について報告しています。

  1. 異なるモジュールそれぞれでインクルードされているヘッダの処理の効率化
    • 少なくとも現在のclangの実装では、あるライブラリのヘッダを異なる複数のモジュールでインクルードしているとき、ヘッダの処理がそれぞれのモジュールビルドにおいて行われるため効率が落ちる
    • サードパーティーライブラリへの依存が多い場合、モジュール化することでビルドが遅くなる要因となる
    • マクロをエクスポートできるようにすることで、処理済みのヘッダを検知しスキップできるようになる
  2. 既存のヘッダベースライブラリからの移行を容易にする
    • 既存のライブラリをモジュールへ移行する際、マクロはその定義を別のファイルに移す必要があり、これによってライブラリの構造が変化する
    • マクロをエクスポートできれば、この手間は不要になる

これらのメリットを得るために、この提案は名前付きモジュールからマクロをエクスポートできるようにすることを提案しています。

マクロのエクスポートのためには、エクスポートしたいマクロを次のようなアノテーションで囲みます

export module A;
#pragma modules "export-macro-begin"
// この中にあるマクロがエクスポートされる
#define VALUE 43
#pragma modules "export-macro-end"

全てのマクロをエクスポートしたい場合は次のようにします

export module A;
#pragma modules "export-macro-all"
#define VALUE 43

モジュールからマクロをインポートするには、import宣言に[[try_import_macros]]を付与します

import A [[try_import_macros]];
static_assert(VALUE == 43); // OK

これがない場合はマクロは一切インポートされません。

import A;
static_assert(VALUE == 43); // FAIL

筆者の方はこのような機能をclangをフォークしたプロダクトで実装しており、既に3年間の実装・出荷の経験があるようです。この機能をclangのメインストリームへパッチを送ろうとした際に、提案にすることを勧められたためこの提案を提出したようです。

この提案はEWGのレビューにおいて関心を得られず、追及は停止されています。

P3687R0 Final Adjustments to C++26 Reflection

P2996のリフレクション機能に対する修正の提案。

ここではP2996R12の静的リフレクション機能に対して2つの小さい変更が提案されています。

  1. splice template argumentの削除
  2. using宣言をリフレクションできるようにする

1. Splice template argumentの削除

splice template argumentとは、スプライス式の結果をテンプレートパラメータとして直接埋め込むための機能です。

template <class P1, auto P2, template<typename> class P3>
struct mytemplate;

auto R1 = ^^int;
auto R2 = std::meta::reflect_constant(26);
auto R3 = ^^std::optional;

mytemplate<[:R1:], [:R2:], [:R3:]>;  // ok
// mytemplate<int, 26, std::optional> と等価

R1, R2, R3がこの例以外のものであっても、展開先のテンプレートパラメータの形式に沿った展開結果になる限り、splice template argumentsは有効となります。

一見なんてことの無い機能ですが、現在の規定によればこれはテンプレートパラメータに渡した時点でスプライスを展開するのではなく、テンプレートパラメータ名とスプライス自体を同等に扱って、テンプレート内部までスプライスのまま置換していくような指定がされているようです。

例えば次のようなコードにおいて

template <template <typename> class TCls>
auto tfn1() {
  return TCls<int>{};
}

template <std::meta::info R>
auto tfn2() {
  return tfn1<[:R:]>();
}

tfn2()においてtfn1<[:R:]>が有効であるためには、依存スプライステンプレート引数[:R:]tfn1で置換可能でなければなりません。P2996R12の規定 に従えばこれは、tfn1内の依存名TCls<int>をsplice-specialization-specifier[:R:]<int>に置換し、テンプレート引数名TClsをsplice-specifier[:R:]に置換する、ことを意味しています。

これによって、テンプレート仮引数名とそれに対して与えられる実引数であるsplice template argumentの高度な結合状態が生じます。

問題なのはこれをどのように実装すべきかということで、Clangの実装ではsplice template argumentをテンプレート名と同一視して扱うことで実装していました。これはP2996R7時点では適合実装でしたが、R12至る中でCWGの見解としてsplice-specifierは名前ではないという認識が明確になったことで変更されており、その後Clangの実装はこれに追随できておらず、適切な代替実装戦略を見いだせていません。

これは単なる文言表現上の問題とも言えますが、実際に実装においてこれが問題となっています。また、CWGのレビューではこのsplice template argumentに十分な時間を割かれていないとのことで、この提案ではsplice template argumentを削除することを提案しています。

では、splice template argumentを削除した場合、テンプレートパラメータにスプライス式を直接渡すことはできなくなるのかというとそうではなく、型スプライス(型名のリフレクションに対するスプライス)はtypename [:r:]として型テンプレートパラメータに渡すことができ、式スプライス(式のリフレクションに対するスプライス)はそのまま非型テンプレートパラメータに渡すことができます。

constexpr auto r1 = ^^int;
constexpr auto r2 = std::meta::reflect_value(42);

std::array<typename [:r1:], [:r2:]> arr;  // ok、std::array<int, 42> arr;と等価

冒頭のコードもsubstitute()メタ関数を使用することでほぼ同等に表現できます

template <class P1, auto P2, template<typename> class P3>
struct mytemplate;

auto R1 = ^^int;
auto R2 = std::meta::reflect_constant(26);
auto R3 = ^^std::optional;

[:substitute(^^mytemplate, {R1, R2, R3}):];  // ok
// mytemplate<int, 26, std::optional> と等価

したがって、P2996からsplice template argumentを削除しても大きな表現力の低下はなく、CWGがレビューすべき分量を減らすことができます。また、C++29以降で改めて導入することも可能です(現在直接のスプライス指定が式スプライスとして解釈されてNTTPにマッチすることによって、完全に後方互換を保った状態にはなりません)。

2. using宣言をリフレクションできるようにする

P2996R12の仕様では、クラス内で基底クラスのメンバをアクセス可能にするためのusing宣言をリフレクションできません。これはusing宣言が名前の導入を行うもので、型や値などのエンティティを導入するものではないためで、リフレクションの対象が存在しないとみなされるためです。

struct Base { int member = 0; };
struct Derived : private Base { using Base::member; };

Derived d;
auto p1 = d.member;  // ok
auto p2 = d.[:^^Derived::member:];   // ng
#include <meta>

struct Base {
  protected: int member;
  friend void fn();
};

struct Derived : private Base {
  using Base::member;
};

void fn() {
  constexpr auto ctx = std::meta::access_context::unprivileged();

  // すべてこのコンテキストからアクセス不可と判定される
  static_assert(!is_accessible(^^Base::member, ctx));
  static_assert(!is_accessible(^^Derived::member, ctx));
  static_assert(!is_accessible(^^Base::member, ctx.via(^^Derived)));
  static_assert(!is_accessible(^^Derived::member, ctx.via(^^Derived)));
}

auto p = &Derived::member;  // ok、問題なくアクセスできる

これらの例においては、^^Derived::memberという式はBase::memberエンティティを表しています。これはusing宣言は名前探索時にそれが参照する名前に自動的に置き換えられるためです。

最初の例では、d.[:^^Derived::member:]は不適格となります。なぜなら、これは全体としてd.Base::memberと記述しようとしているのと同じになるからです。

2つ目の例では、式is_accessible(^^Derived::member, ctx.via(^^Derived))はクラスメンバBase::memberDerived内で名前付けられている場合にグローバルスコープのある位置からアクセスできるかを問うものになっています。これはアクセス不可能であり、実際にDerived::Base::memberという記述が不適格であることからも分かります。

特に、現在の仕様では次の2点のクエリを行う方法がありません

  1. Base::memberというusing宣言がDerivedに存在すること
  2. Base::memberDerivedのスコープ内でアクセス可能な名前であること

つまりusing宣言自体とusing宣言によって可視になっている名前についてをリフレクションによってクエリする方法がありません。

この提案では、これらの点を解消するために、using宣言をリフレクションできるようにすることを提案しています。

このためにここでは次のようなことを提案しています

  1. using宣言に関する文言を変更し、これがエンティティを導入するようにする
    • これは、P2996における型エイリアスに関する変更に類似したもの
  2. using宣言に対応するエンティティプロキシと呼ばれるメンバを導入する
    • エンティティプロキシは、"underlying entity"(基底エンティティ)を持ち、それはusing宣言子で指名されている名前に対応するエンティティ
    • これはusing宣言にリフレクション対象を導入するための論理的なものであり、既存の動作を変更する意図はない
  3. そのオペランドが一意のエンティティプロキシを指定するリフレクション(^^Cls::id)はそのエンティティプロキシを表すリフレクションを返す
    • エンティティプロキシの基底エンティティを透過的に表さない
    • using宣言自体をリフレクションできるようになる
  4. エンティティプロキシはクラス/名前空間のメンバであるため、members_of()の返すリフレクションはエンティティプロキシを含めるようにする
  5. そのほかの既存のメタ関数もエンティティプロキシのプロパティをクエリできるように更新し、エンティティプロキシを識別するためのis_entity_proxy()メタ関数を追加する
  6. std::meta::dealias()はエンティティプロキシのリフレクションをその基底エンティティのリフレクションにマップするようにする

提案では、この2つめの変更についてはC++26に導入することを推奨しています。例えば

struct A { protected: int m; };
struct B : private A { using A::m; };

constexpr auto rm = ^^B::m;

このコードにおいてrm

  • AのメンバであるA::mを表す
  • Bのメンバであり、その基底エンティティがA::mであるメンバBを表す
  • 不適格

のいずれであるかを判断する必要があります。このとき、rmBのメンバを表すようにしたい場合C++26でそうするか、C++29でそうするためにこのクエリの式全体を不適格にする必要があり、いずれにしてもC++26でその変更を行う必要があります。

P3688R0 ASCII character utilities

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

charの値として表現されている文字ががASCIIコードにおいてどのカテゴリの文字に該当するのか、あるいは大文字と小文字の変換などのASCII文字処理関数は<cctype><locale>で提供されています。しかし、これらの関数はどこかしら使いづらいところがあります。

例えば<cctype>の関数群には次のような問題があります

  • ユニコード文字型(char8_t, char16_t, char32_t)がサポートされない
  • constexprではないため定数式で利用できない
  • charwchar_tの間で関数名が異なり、ダックタイピングが行えない
    • std::isalnumstd::iswalnumなど
  • charが符号付整数型の表現を持つ場合、入力値はunsigned charで表現できるかEOFでなければならない
    • 容易に未定義動作を引き起こす
    • charエンコーディングUTF-8の場合、これらの関数は非ASCII範囲外のコード単位の入力に対して未定義動作となる
  • EOF入力も処理してしまうことで、ゼロオーバーヘッド原則を破っている
  • 文字判定系関数の戻り値型はintであり、非ゼロの戻り値が判定がtrueであることを表す
    • boolの方が寛容的
  • 一部の関数は現在のCロケールを使用するため、そのチェックのオーバーヘッドが存在する

<locale>にある関数はこれらの問題の多くを解決するものの、ロケール依存であるという点が問題となります。

この提案は、既存の<cctype>にある文字の判定・変換関数をベースとしたより使いやすいライブラリ関数を提供しようとするものです。

提案する関数は次のものです

// <ascii>ヘッダで定義
namespace std {
  constexpr bool is_ascii(character-type c) noexcept;

  constexpr bool is_ascii_digit(character-type c, int base = 10);
  constexpr bool is_ascii_bit(character-type c) noexcept;
  constexpr bool is_ascii_octal_digit(character-type c) noexcept;
  constexpr bool is_ascii_hex_digit(character-type c) noexcept;

  constexpr bool is_ascii_lower(character-type c) noexcept;
  constexpr bool is_ascii_upper(character-type c) noexcept;
  constexpr bool is_ascii_alpha(character-type c) noexcept;
  constexpr bool is_ascii_alphanumeric(character-type c) noexcept;

  constexpr bool is_ascii_punctuation(character-type c) noexcept;
  constexpr bool is_ascii_graphical(character-type c) noexcept;
  constexpr bool is_ascii_printable(character-type c) noexcept;

  constexpr bool is_ascii_horizontal_whitespace(character-type c) noexcept;
  constexpr bool is_ascii_whitespace(character-type c) noexcept;

  constexpr bool is_ascii_control(character-type c) noexcept;

  constexpr character-type ascii_to_lower(character-type c) noexcept;
  constexpr character-type ascii_to_upper(character-type c) noexcept;

  constexpr character-type ascii_case_insensitive_compare(character-type a,
                                                          character-type b) noexcept;
  constexpr bool ascii_case_insensitive_equals(character-type a,
                                               character-type b) noexcept;
}

これらの関数はすべてconstexprかつロケール非依存であり、全ての文字型(charからchar8_tまで)に対するオーバーロードが提供されています。また、これらの関数はASCII文字に対して動作するものであるため、関数名にasciiを含むようにされています。

このうち、次の5つの関数は新しく追加されたものです

  • is_ascii()
    • 文字がASCII範囲内の文字であるかを判定する
  • is_ascii_bit()
    • 文字が二進数を表せる数字であるか('0'or'1')を判定する
  • is_ascii_octal_digit()
    • 文字が八進数を表せる数字であるか('0'~'7')を判定する
  • ascii_case_insensitive_compare()
    • 大文字と小文字を区別しない比較を行う
  • ascii_case_insensitive_equals()
    • 大文字と小文字を区別しない等値比較を行う

これらの関数はいずれもそれほど実装は難しくありません。しかし、ASCII文字の処理は非常に一般的なユースケースであることと標準ライブラリの既存の関数が使いづらいことなどから、標準ライブラリで使いやすいこれらの関数を提供する事に価値がある、としています。

P3689R0 Convenience functions for Random number generation

乱数生成を手軽に行えるようにするための関数群を提供する提案。

<random>では多数の乱数生成器や分布生成器が用意されており、ユーザーはその用途に応じて最適なものを選択して使用することができます。しかし、乱数生成器や分布生成器の選択はユーザーにとって難しい場合があり、また乱数生成器と分布生成器を組み合わせて使用するためのコードも冗長になりがちです。

例えば、1から6までの整数をランダムに生成する(さいころを振る)コードは次のようになります

std::random_device rd;
const auto seed = rd();
std::mt19937 engine{seed};
std::uniform_int_distribution<int> distribution(1, 6);
auto num = distribution(engine);

わざと冗長に書いてる所もありますが、基本的なやるべきことは変わりません。この例の様なかなり単純かつありふれたタスクにおいても、このような冗長なコードを書く必要があります。

また、乱数を正しく扱うことにも知識が必要ですが、乱数生成器と分布生成器の違いやその性質、シードの取得方法など、C++の乱数APIを正しく使用するためにも多くの知識が必要です。初学者にとって、このことはAPIを容易に誤使用する原因となります。

この提案は、乱数を使用する95%のユースケースを1行で満たせるような、乱数取得関数を追加することを提案するものです。

この提案のAPIでは、先ほどのさいころの例は次のように単純化されます

auto num = std::random(1, 6);

std::random()関数はいくつかの基本的な用途に対応したオーバーロードが用意されています

// (1a)
template<typename T, typename Engine = std::convenience_random_engine>
  requires std::floating_point< T > || std::integral< T >
T random(const T & lb, const T & ub);

// (1b)
template<typename T, typename Engine = std::convenience_random_engine>
  requires std::floating_point<T> || std::is_same_v<T, bool>
T random();

// (2)
template<typename T, typename Engine = std::convenience_random_engine, std::floating_point P>
  requires std::is_same_v<T, bool>
T random(P p = P(0.5))

// (3a)
template<std::ranges::random_access_range Range, typename Engine = std::convenience_random_engine>
std::ranges::range_reference_t<Range> random(Range && range);

// (3b)
template<typename T, typename Engine = std::convenience_random_engine>
T random(std::initializer_list<T> il);

// (3c)
template<std::random_access_iterator It, typename Engine = std::convenience_random_engine>
std::iter_reference_t<It> random(It first, It last);
  • (1a) 範囲[lb, ub)の一様分布によってT型の乱数を生成する
    • 整数型の場合、乱数範囲は[lb, ub]
  • (1b) lb = T(0), ub = T(1)で固定された(1a)
  • (2) 確率pbool値を生成する
  • (3a)(3b) 指定範囲からランダムに要素を1つ選択して返す

どの関数でも、使用する乱数エンジンを一番後ろのテンプレートパラメータでカスタマイズすることができ、std::convenience_random_engineはこれらの関数が使用するデフォルトの乱数エンジンの型エイリアスです。

正規分布に従った乱数生成を行う関数として、randn()も提案されています。

template<std::floating_point T, typename Engine = std::convenience_random_engine>
T randn(const T & mu = T(0), const T & sigma = T(1));

この関数は平均mu標準偏差sigma正規分布に従ったT型の乱数を生成します。

これらの関数は次のような共通した性質があります

  • スレッドセーフ
  • 異なるスレッドは同じ乱数を生成しない
    • スレッドごとに異なるシードを使用する
  • ある1つのスレッドにおける関数の使用は、同じ乱数を生成する
  • 生成される乱数はコンパイルごとに変化する可能性がある
  • 入力型によって出力型が決定される

この性質からわかるように、これらの関数はシード値や乱数エンジンを内部状態として持っており、なおかつそれはスレッドローカルに保持されています。そのような状態に対してシード値を設定する関数も用意されています。

// (S1)
template<typename Engine = std::convenience_random_engine, typename Seed>
void seed(const Seed & s);

// (S2)
template<typename Engine = std::convenience_random_engine, typename Seed>
void seed();
  • (S1) 呼び出されたスレッドのRNG状態に、指定したシード値sを設定する
  • (S2) 呼び出されたスレッドのRNG状態に、std::random_device{}()をシード値として設定する

提案にはこれらの関数の実装例が掲載されています。

P3690R0 Consistency fix: Make simd reductions SIMD-generic

std::simd専用となっている一部の操作をジェネリックにする提案。

C++26で導入されたstd::simdは通常のスカラ数値型を扱うのと同じようにSIMDベクトル型を扱うコードを書けるようにするものです。例えば、次のようなコードはスカラ型(整数型や浮動小数点数型、複素数型)とそれらを要素とするstd::simd型の両方でインスタンス化することができます

template<class T>
auto f(const T& x, const T& y, const T& z) {
  return x + std::sqrt(y) * std::pow(z);
}

std::simdによって可能になるこのようなコードの事をSIMDジェネリックSIMD-generic)と呼びます。これは、std::simdがほぼすべての演算子オーバーロードを備えており、なおかつ周辺の関数についてもオーバーロードされていることによって可能になっています。

ほぼstd::simd固有の操作についても同様にスカラ型に対するオーバーロードが用意されていることで、SIMDジェネリックなコードを書くことができます。例えば、bool値のリダクション操作は次のように書くことができます

template<class T>
bool all_lt(const T& x, const T& y) {
  return std::datapar::all_of(x < y);
}

しかし、一部の操作についてはスカラ型のオーバーロードが提供されていないことによってstd::simd専用となっており、SIMDジェネリックなコードを書くことができません。例えば、次のようなコードはstd::simd型では動作しますが、スカラ型では動作しません

template<class T>
auto calc_contribution(const T& x, const T& y) {
  return std::datapar::reduce(x * y);
}

これは、std::datapar::reduce()関数がstd::simd型に対するオーバーロードしか提供していないためです。特に、算術リダクション関数群において、同様にスカラ型のオーバーロードが提供されていません。

この提案は、これらの算術リダクション関数群にスカラ型のオーバーロードを追加することを提案しています。これによって、これらの関数を使用したコードもSIMDジェネリックに記述できるようになります。

対象は、std::dataparにあるreduce, reduce_min, reduce_maxの3つの関数です。これらのスカラ型オーバーロードは、単純に受け取った引数をそのまま返すだけになります。

P3691R0 Reconsider naming of the namespace for “std::simd”

std::simd名前空間構造を再考する提案。

std::simdおよび関連する関数などを配置する名前空間はP3287R2で検討された結果、std::datapar以下に配置されることになり、そのままC++26に導入されています。

namespace std::datapar {
  template<class T, class Abi = native-abi<T>>
  class basic_simd;

  template<class T, simd-size-type N = simd-size-v<T, native-abi<T>>>
  using simd = basic_simd<T, deduce-abi-t<T, N>>;

  template<size_t Bytes, class Abi = native-abi<integer-from<Bytes>>>
  class basic_simd_mask;

  template<class T, simd-size-type N = simd-size-v<T, native-abi<T>>>
  using simd_mask = basic_simd_mask<sizeof(T), deduce-abi-t<T, N>>;
}

SIMDクラス型はstd::datapar::simdから利用でき、マスク型も同様です。

しかし、dataparという名前空間名については少なからず異論があるようで、この提案はそれを再検討するものです。ただし、ここでは名前空間とその配置そのものは再検討せず、名前空間名とクラス名について検討しています。

dataparという名前はdata-parallelismの略語であり、今回新しく発明された名前でもあります。このなじみの無さに加えて、データ並列性はSIMDを包含する概念であるため、SIMDクラス型以外のものも含まれていると錯覚させてしまったり、datapar::simd<T>が奇妙に映るなどの批判がされています。

P3287R2で検討されていた際に有力だったもう一つの名前は、名前空間simdにしてstd::simd::simdstd::simd::simd_maskのように綴るものです。こちらは、simdという言葉が繰り返しになるのが忌避され、最終的にdataparが選ばれました。

この提案では、std::simd名前空間が本当に悪いものなのか(std::dataparが最良なのか)?について詳細に検討しています。その詳細な説明は省きますが、結果としてここでは次の事を提案(推奨)しています。

  1. 名前空間std::simdに変更する
  2. マスク型の名前を変更する
    • std::datapar::basic_simd_mask -> std::simd::basic_mask
    • std::datapar::simd_mask -> std::simd::mask

SIMD型はstd::simd::simd<T>になります。

さらに、このような命名規則のポリシーを確立するために、LEWGにおいて名前空間とその下のエンティティ名で同じワードが繰り返しになることを悪い習慣と見做す(あるいは完全に禁止する)かどうかを議論し投票する事を提案しています。

P3692R0 How to Avoid OOTA Without Really Trying

P3064R2を要約した文書、および現在の実装ではOOTAが起こらないことを明確化する提案。

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

P3064R2 How to Avoid OOTA Without Really Trying - WG21月次提案文書を眺める 2024年07月

P3064R2は以前に提出されていたものの結果的におよそ100Pに達する長大な文章になっており、その主張を理解することすら困難でした。それは委員会においても批判されたようで、この文書はP3064R2の内容を要約したものです。

その内容については変化がなく、メモリモデルの理論上起こりうるOOTAは現在のハードウェアやコンパイラに基づく実装においては起こらないことを説明しています。

また、提案としては、標準の中で実装がOOTAを起こさないようにすることを推奨している規定([atomics.order])に対して、このことを明記することを提案しています。

P3693R0 SG14: Low Latency/Games/Embedded/Financial Trading virtual Meeting Minutes 2025/04/09-2025/05/07

2025/04/09から2025/05/07の間に行われた、SG14のオンラインミーティングの議事録。

どのようなことを議論したのかが簡単に記載されています。

P3694R0 SG19: Machine Learning virtual Meeting Minutes to 2025/03/13-2025/05/08

2025年3月13日から5月8日に行われた、SG19のオンラインミーティングの議事録。

どのようなことを議論したのかが簡単に記載されています。

P3695R0 Deprecate implicit conversions between Unicode character types

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

ユニコード文字型(char8_t, char16_t, char32_t)の間では暗黙変換が可能であり、3つの型を相互に行き来することができます。しかし、それはあくまで値の変換でしかなく、文字表現を保持するものではありません。これによって、分かりづらいバグが発生する可能性があります。

constexpr bool contains_oe(std::u8string_view str) {
  for (char8_t c : str) {
    if (c == U'ö') // 👈 u8'ö'とすべきところをUを使用している
      return true;
  }

  return false;
}

static_assert(contains_oe(u8"ö")); // fails?!

このコードのc == U'ö'では左辺がchar8_t、右辺がchar32_tchar8_t -> char32_tの暗黙変換が行われてchar32_tの値として比較が行われています。これは値の比較としてはc == char32_t(0xf6)と同じ比較であり、0xf6UTF-8コード単位の値としては不正なのでこの比較は文字の比較としては常に失敗します。

constexpr bool contains_nbsp(std::u8string_view str) {
  for (char8_t c : str) {
    if (c == U'\N{NO-BREAK SPACE}') // 👈 u8を使用すべき
      return true;
  }

  return false;
}

static_assert(contains_nbsp(u8"\N{CYRILLIC CAPITAL LETTER EL WITH MIDDLE HOOK}")); // OK?!

CYRILLIC CAPITAL LETTER EL WITH MIDDLE HOOKすなわちԠ (U+0520)は、UTF-80xd4, 0xa0エンコードされており、NO-BREAK SPACE、U+00A0)はchar32_t(0xa0)の値になります。どちらもchar32_tに変換されると、Ԡの2文字目で一致するためこの関数はtrueを返します。これは当然、望ましい動作ではなく、誤判定です。

constexpr bool is_umlaut(char32_t c) {
  return c == U'ä' || c == U'ö' || c == U'ü';
}

...

constexpr std::u8string_view umlauts = u8"äöü";
static_assert(std::ranges::find_if(umlauts, is_umlaut) != umlauts.end()); // fails?!

ここではis_umlaut()の引数に渡す際に(すなわちアルゴリズム実装の中で)char8_t -> char32_tの暗黙変換が行われており、後は最初の例と同じ理由で比較に失敗します。

これらの例は全て、縮小変換によって情報が失われるわけではないにもかかわらずバグを誘発しています。これは、文字が単なる整数値以上の意味論を持っているためです。しかし当然、逆方向の変換(char32_t -> char8_t)も同様にバグの発生源となります(これは縮小変換としてコンパイラが警告します)。

この提案は、このようなバグを防止するためにユニコード文字型間での暗黙変換を非推奨とすることで、それが起きているときにコンパイラの警告を発するようにしようとするものです。

これらの例は筆者の方が実際に出会ったものであり、理論上のものではないとしています。また、clangにはすでにこの警告が実装されているようです。

上記の例のように、この警告に出会ったとしても意図的でない場合はUu8に修正するだけで対応でき、意図的な場合はstatic_castで明示することで対応できます。

なお、ユニコードUTF-8/16/32いずれもASCII互換なため、ASCII範囲内の文字であれば暗黙変換が起きたとしても比較は意図通りに行われます。しかし、ユニコードの詳細が漏れ出す、プログラマはそれを覚えておく必要がある、ルールが複雑になる(教えづらくなる)などの理由で区別せずに非推奨とすることを提案しています。

P3696R0 Discovering Header Units via Module Maps

どのヘッダがヘッダユニットとして利用可能かを記述するモジュールマップを導入する提案。

C++20で追加されたモジュールにおいては、通常のヘッダをモジュールとして扱うヘッダユニットという機能があります。import <header_name>;のようにヘッダをインポートすることでヘッダユニットとして使用できるほか、ヘッダのインクルードをヘッダユニットのインポートに置き換えることもできます。

しかし、全てのヘッダがヘッダユニットとして利用できるわけではなく、ヘッダユニットとして利用可能なヘッダはコンパイラ実装に依存します(ただし、標準ライブラリのヘッダはヘッダユニットとして利用できることが規定されています)。また、インクルードをインポートに置き換えることができる機能についても実装定義であり、どのヘッダでこれがいつ行われるかは不透明な部分があります。

ヘッダユニットとしてインポートされたものと通常のインクルードされたものが同じ翻訳単位から見えてしまうとODRの問題を引き起こすため、ヘッダユニット周りの仕様には移植可能で一貫した共通の認識を持つことが重要です。しかし、現状それは得られておらず、主にビルドシステムに委ねられているものの、決定権を持つものが不在になっています。

ヘッダユニットとして使用可能なヘッダは、通常次の条件を満たすものです

  • 翻訳単位に単独でインクルードできる
  • インクルード後に参照される定義と宣言は、それ以前のプリプロセッサの状態に依存しない
  • 意味を変えることなく複数回インクルードできる
  • 翻訳単位によって異なる定義が可能な#defineの直後に#includeを記述しない
  • ヘッダが自身の定義宣言をすべて所有している

ヘッダの利用者がこのような判定を行うのは困難であり、ヘッダがヘッダユニットとして利用可能かどうか、またいつ利用可能かを指定するのに最適な人物はヘッダを作成した人です。しかし、それを伝達する統一した方法はありません。

この提案は、どのヘッダがヘッダユニットとして利用可能かを記述するモジュールマップを導入することを提案するものです。モジュールマップは、コンパイラがヘッダユニットとして利用可能なヘッダを発見するために使用されます。

ヘッダユニットは次のような構造のテキストファイルです

module M {     // 全てのモジュールはグローバルモジュールとして扱われるのではなく、名前を付ける必要がある

  header "M.h" // ヘッダユニットとして利用可能なヘッダを指定
  export *     // このモジュールがインポートするモジュールはデフォルトでは再エクスポートされないため、必要なら明示的に指定する
}

モジュールマップはコマンドライン引数からコンパイラに渡すことも、対象のヘッダと同じディレクトリに置いておいてコンパイラ(ビルドシステム)に見つけてもらうこともできます。

この機能はclangモジュールにおけるヘッダユニット実装の拡張機能として10年の経験がありますが、重要なことはこのような仕組みを標準化することで全てのツールでヘッダユニットの取り扱いについて一貫した動作を得られるようにすることです。

上記のモジュールマップ例はclangモジュールにおけるものであり、それをそのまま標準に採用することは提案していません。例えば、ヘッダユニットのモジュールに名前を付ける必要があり、デフォルトでは再エクスポートをしないようになっていますが、標準仕様としてはそれが望ましくない可能性があります。そのため、現段階では特定のフォーマットを提案しているわけではありません。

P3697R0 Minor additions to C++26 standard library hardening

堅牢化された事前条件をさらに追加する提案。

この提案はP3471R4で導入された標準ライブラリの堅牢化モードの対象を拡大しようとするものです。

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

提案しているのは次のものです

  • basic_stacktrace
    • current(skip, max_depth)
    • operator[]
  • shared_ptr<T[N]>
    • operator[]
  • view_interface
    • front()
    • back()
  • counted_iterator
    • operator*
    • operator[]
    • counted_iterator(i, n)
    • operator++
    • operator+=
    • operator-=
    • iter_swap(counted_iterator)
    • iter_swap(counted_iterator, counted_iterator)
  • common_iterator
    • common_iterator(const common_iterator<I2, S2>&)
    • operator=
    • operator==(const common_iterator&, const common_iterator<I2, S2>&)
    • operator-(const common_iterator&, const common_iterator<I2, S2>&)
    • operator*
    • operator->
    • operator++
    • operator++(int)
    • iter_move(const common_iterator&)
    • iter_swap(const common_iterator&, const common_iterator<I2, S2>&)

これらの関数は堅牢化を適用するにあたっての条件

  • 事前条件に違反するとメモリ安全性の問題が発生する
  • 呼び出し元には、チェックを実行するために必要な情報が全てある
  • チェックは定数時間で実行でき、オーバーヘッドが小さい

をすべて満たしているものです。P3471では単に見落とされたものと思われます。

P3698R0 Cross-capacity comparisons for inplace_vector

std::inplace_vectorの比較がそのキャパシティ指定によらず可能になるようにする提案。

std::vectorでは==<などの全ての比較演算子が定義されており、その比較は要素の辞書式比較によって行われ、std::vectorのプロパティの一部であるキャパシティの値は比較に参加しません。

#include <cassert>
#include <vector>

int main() {
  std::vector<int> a{1, 2, 3};
  std::vector<int> b{1, 2, 3};

  a.reserve(10);
  b.reserve(100);

  assert(a == b); // ok、パスする
}

std::inplace_vectorではキャパシティをテンプレートパラメータで指定するのですが、現在の仕様だと異なるキャパシティを持つinplace_vector同士の比較ができません。

#include <cassert>
#include <inplace_vector>

int main() {
  std::inplace_vector<int, 10> x{1, 2, 3};
  std::inplace_vector<int, 100> y{1, 2, 3};

  assert(x == y);  // ng、xとyが同じ型ではないため対応する比較演算子が見つからない
}

これは、std::inplace_vectorの比較演算子が次のように定義されているためです

template<class T, size_t N>
class inplace_vector {
  ...

  friend constexpr bool operator==(const inplace_vector& x,
                                   const inplace_vector& y) = default;

  friend constexpr synth-three-way-result<T>
    operator<=>(const inplace_vector& x, const inplace_vector& y) = default;

  ...
};

これらの比較演算子はキャパシティをテンプレート化していないため、キャパシティが同じstd::inplace_vector同士でしか比較できません(正確にはdefaultで定義することを明示的に指定されていないのですが、定義の指定もされていないためデフォルト実装を意図しているようです)。

この提案は、std::inplace_vectorの比較演算子をキャパシティに依存しないように変更することを提案しています。具体的には、次のように比較演算子をテンプレート化します

template<class T, size_t N>
class inplace_vector {
  ...

  template <size_t M>
  friend constexpr bool operator==(const inplace_vector& x,
                                   const inplace_vector<T, M>& y);

  template <size_t M>
  friend constexpr synth-three-way-result<T>
    operator<=>(const inplace_vector& x, const inplace_vector<T, M>& y);

  ...
};

そのうえで、比較の定義を指定するようにします(デフォルト実装を指定しない)。ただし、キャパシティの値が比較に参加するようになるわけではなく、要素の辞書式比較によって比較が行われる点は変わりません。

これによって、std::inplace_vector<int, 10>std::inplace_vector<int, 100>のようにキャパシティが異なるinplace_vector同士でも比較が可能になります。

P3699R0 Rename conqueue_errc

P0260のconqueue_errcをリネームする提案。

conqueue_errcはP0260で提案されている並行キューにおける特定の操作の戻り値の列挙型です。これは元々、例外処理の際に使用される列挙型だったためこの名前になっています(特に、errという言葉が入っている)。しかし、並行キューの提案は現在では独自の例外型を使用しなくなっており、この列挙値もキューの操作の結果がどういう状態なのかを表すものになっており、エラー状態を表すものではなくなっています。

この提案は、conqueue_errcという名前をより操作の状態を表す様な適切な名前に変更しようとするものです。

提案では名前の候補として、queue_stateconqueue_stateの2つを提示しています。

P3700R0 Making Safe C++ happen

C++を安全な言語へと進化させていくために、その検討・進捗のための枠組みの提案。

近年のプログラミング言語に対する安全性・セキュリティの要求の高まりを受けて、C++においてもより安全な言語に進化させるための検討や提案などが活発になっています。プロファイルがその最たる例ですが、C++という歴史のある言語を安全なものに変貌させるというのは1つの機能や提案で達成できるものではなく、いくつもの機能や議論の先にあるものです。

この提案は、C++における安全性向上の取り組みを体系的に整理し、その進捗をマッピングするための枠組みを提案するものです。ただし、具体的な機能や対策を提案するものではありません。

提案では、P2687を出発点にして安全性とは何でここで対象にするのはどの種の安全性なのか、安全なC++に求められる要件、そのアプローチなどについて説明しています。これらはプロファイル関連の文書等でも言及されていたことですが、この提案で特筆すべきなのは、安全なC++に向けて実行すべきタスクの枠組みとしてグリッド(表)を提案していることです。

この表は、列に対処すべき対象の安全性の領域を置き、行に実施すべきアクションの種類と使用する特定のツールを置いたものです。列はP3081で提案されているプロファイルの分類にほぼ対応しています。

ここで提示されている行要素は次のようなものです

  1. 翻訳単位や関数を横断する情報の伝達
    • プログラムに埋め込まれた仮定を表現し、コンパイラに伝達するための注釈が必要
      • 特に、関数や翻訳単位を超えて、そのような情報をコンパイラが利用できることが重要
    • 具体的には、関数の境界に事前条件(preconditions)や事後条件(postconditions)、ポインターの寿命などをアノテーションする能力など
  2. 言語の一部を削除する
    • 安全性を確保するために、安全ではない構成要素を削除する
      • 組み込み環境での動的割り当ての回避や、モダンC++におけるユーザーコードでのnew/deleteや生配列の使用の回避など
    • 言語のサブセット化は、現在すでに多くの人々が常に行っている基本的な作業であり、安全性を高めるためには言語のサブセット化を定義する方法が必要
  3. 削除された必須機能の代替を提供する
    • 削除される構成要素の安全な代替を定義するため、または代替が何であるかを明確に示すために、新しい機能定義の必要性を認識する
  4. 実行時エラーの処理
    • 環境やソフトウェアの種類、あるいはその所有者の要望に応じて、実行時の安全性の障害を適切に処理するための方法は大きく変化する
      • テレメトリーデータの収集、既知の安全な状態への移行、またはクラッシュなど
    • それらの要望に基づいたハンドリングを可能にする仕組みが必要
  5. 安全性の命名
    • ユーザーが関連する設定、チェック、およびプロパティをグループ化し、分かりやすく一貫性のあるポータブルな方法で有効化できるようにする必要がある
    • そのために、関連する設定やチェック等をグループ化して名前を付ける方法が必要

このような表を現実のものとするためには、まず各列を実装可能にするために必要なツールをすべて整備することから始めるべき、としています。その後、各列を個別の論文として提案し、それらのツールを統合的に活用することで、安全性の特定の側面に的を絞ったアプローチを試みていくことを推奨しています。

P3701R0 Concepts for integer types, not integral types

純粋に整数型のみを表現するintegerコンセプトの提案。

std::integralコンセプトは整数型を表現するものですが、実際には文字型やbool型およびCV修飾付きの型を整数型として扱っています。

template <std::integral T>
T add_integers(T x, T y) { return x + y; }

int main() {
  add_integers(true, true);      // OK?!
  add_integers('a', 'b');        // OK?!
  add_integers<const int>(1, 2); // OK?!
}

これはstd::signed_integralstd::unsigned_integralでも同様であり、整数型を意図する場合は少なくともbool型と文字型は除外したいことが多いはずです。

この提案は、これらの型を含まない純粋な整数型を表すstd::integerコンセプトを追加しようとするものです。

namespace std {
  // C++20からあるもの
  template<class T>
  concept integral = ...;
  template<class T>
  concept signed_integral = ...;
  template<class T>
  concept unsigned_integral = ...;

  // この提案で追加されるもの
  template<class T>
  concept integer = ...;
  template<class T>
  concept signed_integer = ...;
  template<class T>
  concept unsigned_integer = ...;
}

std::integerコンセプトはstd::integralコンセプトに対して、char等の文字型やbool型を含まず、int等整数型のCV修飾も含みません。

template <std::integer T>
T add_integers(T x, T y) { return x + y; }

int main() {
  add_integers(true, true);      // ng
  add_integers('a', 'b');        // ng
  add_integers<const int>(1, 2); // ng
}

また、C++標準文書内で整数型を指定する場合にstd::integralから上記のような型を取り除いた型を指定する際に、"signed or unsigned integer type"のような言葉を使用しているところを、"integer"という言葉に置き換えることを提案しています。ここでの"integer"はstd::integerコンセプトに対応し、"integral"はstd::integralコンセプトに対応した言葉になります。

文言の変更は、既存の"integer"という言葉はすべて"integral"に置き換えられ、"signed or unsigned integer"という言葉が"integer"に置き換えられます。

std::integerコンセプトの表す整数型からCV修飾された整数型を除いているのは意図的なもので、文言で使用した時にあまり便利ではないこと(既存の"signed or unsigned integer"がCV修飾を含まない、デフォルトを含むようにしてしまうと多くの場所でCV修飾を除くという注記が必要になる)、関数テンプレートの推論時に推論されないこと、integerコンセプト使用時にユーザーがvolatile intをサポートすることを意図するケースが非常に稀であること、などの理由でstd::integerにはCV修飾を含まないようにしています。

P3702R0 Stricter requirements for document submissions (SD-7)

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

提案文書の形式はpdf、html、プレーンテキスト、マークダウンのいずれかである必要があるものの、その他はあまり詳細に指定されていません。特に、HTMLはJavascriptCSSを組み合わせることでかなりの自由度を持ちますが、それがどの程度許されるのかは指定されておらず、現在は紳士協定として可能な限りシンプルでインタラクティブにはしないという暗黙のルールがあるようです。

また、仮にクッキーを使用する場合、ユーザーの同意を取らないとEUGDPRに抵触する可能性があります。

この提案は、HTML形式の提案文書について最低限のルールを設けようとするものです。具体的には次の事を提案しています

  • クッキーやローカルストレージなど、法的な問題のある機能の使用を禁止する
  • ISOのAIガイダンスに従って、AI生成コンテンツの使用を禁止する
  • 一部のアクセシビリティ要件を満たす事を必須とする
  • 提案文書内のインタラクティブ性の範囲を制限する
    • 特に提案する文言の部分
  • 文書が自己完結していることを要求する
    • リンク切れになって判読不能になってはならず、内部参照は<a href=#id>のような内部参照であるべき
  • 文書のエンコーディング要件を現代化(UTF-8を全面的に採用する

これらのことをSD-7という提案文書についてのルールの様な文書に対して提案しています。

P3703R0 Constness and Locking

mutexlock()メンバ関数constメンバ関数にする提案。

クラスの値に対する並行アクセスを許可しそれを保護するために、クラスメンバにstd::mutexを持ち、メンバ関数でロックを取得してクラスの値にアクセスするようにする、というパターンはかなり一般的なものです。

その場合でも、クラスの値を読み取るだけで変更しないようなメンバ関数は通常通りconst修飾されます。また、メンバ関数const修飾しておくことでメンバ関数がスレッドセーフであることを表明する習慣があります。しかし、mutexlock()メンバ関数const関数ではないためconstメンバ関数からはロック取得できなくなり、その対策のためにmutexメンバをmutableで宣言する必要があります(これを、“Mutable comes with Mutex” M&M Rule と呼ぶらしい)。

この場合実際にはmutexメンバはそのクラスの値に寄与しておらず、並行アクセスからの保護のために必要になっているだけで、そのクラスの値のconst性とも関係がないはずです。にもかかわらず、mutableという回避機構を使用しなければならないことは初学者にとって混乱の元であり、mutableconst回避のためのハックの様な仕組みであるという誤解を招いています。

この提案は、mutexlock()メンバ関数const関数にすることで、クラスメンバとしてmutexを持つパターンにおけるmutableの必要性を失くそうとするものです。

現在 この提案
struct BankAccount {
  double getBalance() const {
    shared_lock lck(mtx);
    return balance;
  }

  void setBalance(double d) {
    unique_lock lck(mtx);
    balance = d;
  }

  mutable shared_mutex mtx;
  double balance;
};
struct BankAccount {
  double getBalance() const {
    shared_lock lck(mtx);
    return balance;
  }

  void setBalance(double d) {
    unique_lock lck(mtx);
    balance = d;
  }

  shared_mutex mtx;
  double balance;
};

ここではこの変更をstd::shared_mutexstd::mutexに適用することを提案しています。その際、ABI破壊を回避するために、lock()メンバ関数は既存のもの(非const)を維持したまま、新しくconst版を追加するようにすることを提案しています。

また、同様の考察からmutexの比較についても変更を提案しています。

C++20の宇宙船演算子<=>)導入に当たってはデフォルト比較がmutableメンバを無視するかどうかがかなり議論され、結局無視するべきではないという結論で合意されています。しかし、ことmutexの比較については上記で説明したようにmutexはクラスメンバとして保持されていても通常その値に寄与することは無いため、デフォルト比較に影響すべきではないはずです。

しかし現在は、全ての標準mutex型は比較演算子を定義していないため、クラスメンバとして保有されているとそのクラスのデフォルト比較実装を妨げます。そこで、mutex<=>演算子std::strong_ordering::equalを返すようにして定義しておくことで、クラスに保持された際のデフォルト比較を妨げないようにすることも提案しています。

この提案はSG1のレビューで支持を得られず、リジェクトされています。

P3704R0 What are profiles?

プロファイル機能(フレームワーク)についての解説書。

この文書は、プロファイル機能のアイデアや目指すところ、その必要性と標準化の理由などを簡単に説明したものです。

P3705R0 A Sentinel for Null-Terminated Strings

文字列範囲内の終端文字(\0)を見つけるとそこで終端を示す番兵型の提案。

文字列リテラルによる文字列はそのまま自然にrangeとして扱うことができ、Rangeアダプタを適用することができます。しかしこの時、rangeとしての文字列リテラルには終端のnull文字が含まれてしまい、範囲としての終端はnull文字のさらに後ろになります。

using namespace std::string_view_literals;

static_assert(std::string{std::from_range, "Brubeck" | std::views::take(5)} == "Brube"); // ✅
static_assert(std::string{std::from_range, "Dave" | std::views::take(5)} == "Dave");     // ❌
static_assert(std::string{std::from_range, "Dave" | std::views::take(5)} == "Dave\0"sv); // ✅

"Dave"という文字列リテラルは文字列としては4文字ですが、範囲としては終端のnull文字を加えた5文字になります。そのため、rangeとして扱われる文脈(Rangeアダプタやfrom_rangeコンストラクタなど)では5要素のrangeとなります。このため、2・3番目の例で構築されているstd::stringはお尻にnull文字を含む5文字の文字列として構築されています(std::stringの付加するnull文字はこれとは別にさらに付加されている)。

std::stringstd::string_viewの場合、文字列リテラルから構築した時でもお尻のnull文字を除いた部分を有効な文字列として構築されるため、このような問題は起こりません。ただしその場合、どちらのコンストラクタでも終端のnull文字を線形に探索して文字列長を確定しようとすることで、文字列の長さNに対してO(N)の時間計算量がかかります。

constexpr std::string take_five(char const* long_string) {
  std::string_view const long_string_view = long_string; // read all of long_string!
  return std::string{std::from_range, long_string_view | std::views::take(5)};
}

この提案は、文字列を範囲として扱った時にnull文字を範囲の終端として扱うための特殊な番兵型null_sentinelを提案するものです。この番兵型を用いて文字列範囲をsubrangeで構築することで終端判定を遅延評価することができます。

constexpr std::string take_five(char const* long_string) {
  std::ranges::subrange const long_string_range(long_string, std::null_sentinel); // 構築はO(1)
  return std::string{std::from_range, long_string_range | std::views::take(5)}; // null文字を含まずに先頭5文字から構築
}

さらに、このような典型的な使用例をカバーするためのnull_termCPOも提案しています。

constexpr std::string take_five(char const* long_string) {
  return std::string{std::from_range, std::null_term(long_string) | std::views::take(5)};
}

null_termCPOはnull_term(E)のように呼ばれたときに、ranges::subrange(E, null_sentinel)を返します。

null_sentinelは状態を持たずに比較演算子==)だけを定義しており、比較演算子イテレータの値がデフォルト構築された値と等しいかを調べます。このため、null_sentinelは実は文字列範囲だけではなく任意の型の範囲に対して、その値型(iter_value_t)のデフォルト構築された値を終端として扱うことができます。

namespace std {

  struct null_sentinel_t {
    // input_iterator用
    template<input_iterator I>
      requires (not forward_iterator<I>) && 
           default-initializable-and-equality-comparable-iter-value<I>
    friend constexpr bool operator==(I const& it, null_sentinel_t) {
      return *it == iter_value_t<I>{};
    }

    // forward_iterator以上のイテレータ用
    template<forward_iterator I>
      requires default-initializable-and-equality-comparable-iter-value<I>
    friend constexpr bool operator==(I it, null_sentinel_t) {
      return *it == iter_value_t<I>{};
    }
  };

  inline constexpr null_sentinel_t null_sentinel;
}

この定義例にあるように、この提案では設計の選択肢としてinput_iteratorでしかないイテレータを参照で受けるためのオーバーロードを定義することを提案しています。これは一般にinput_iteratorがコピー可能ではないためですが、標準ライブラリではそれとは関係なくイテレータは常に値渡しで受け取るようになっています。

P3706R0 Rename join and nest in async_scope proposal

P3149で提案されているasync_scope関連機能のうち、joinnestの名前を変更する提案。

joinについて

std::thread::join()という既存の関数が既に存在しているものの、 非同期スコープのjoin()とはかなり異なる振る舞いをします。特に、後者は現在のスレッドをブロックしません。

// 新しいスレッドを開始する
std::thread t1([]{ /* some work */ });

// 現在のスレッドをブロックしてt1の完了を待つ
t1.join();
namespace ex = std::execution;

my_thread_pool pool;
ex::counting_scope scope;

// 非同期スコープ内実行する処理(のsenderを作成
auto snd = ex::transfer_just(pool.get_scheduler())
         | ex::then([] { /* some work */ });

// sndをscopeに関連付け、処理を開始
ex::spawn(snd, scope.get_token());

// これは意味のない呼び出し、戻り値のsenderは捨てられている
scope.join();

// scope.join()の結果をsync_waitすることで、非同期操作の完了を待機(ブロックする
this_thread::sync_wait(scope.join());

counting_scope::joinは、非同期スコープに関連付けられたすべてのsender(処理)が終了した場合に完了を通知するsenderを返す関数で、それ自体は何かをするものではありません。

このため、現在のjoin()という関数名はその動作を表現しておらず、誤解を招くものになっています。この提案では、これを.when_completed()に変更することを提案しています。

提案が受け入れられた場合上記の最後の行は次のようになります

this_thread::sync_wait(scope.when_completed());

nestについて

P3149の非同期スコープにおけるnest()は、処理(sender)をスコープに関連付けるものの処理の実行はすぐに行わない(遅延される)操作です(ex::spawn()はすぐに実行しようとする)。確かに、非同期スコープにネストした(内部で実行される)処理を指定するものであるものの、これはネストした非同期処理(非同期処理内部で呼び出される非同期処理)を定義することを意味していません(スコープそのものは非同期処理ではない)。それは通常、let_valueなどを使用して定義されます。

こちらも名前が適切ではないため、提案ではこれをassociate(またはattach)に変更することを提案しています。

LEWGの投票では、2つ目のnestassociateに変更する提案だけがコンセンサスを得たようで、P3149に直接マージされています。

P3707R0 A std::is_always_exhaustive trait

型のレイアウトがパディングビットを持たないことを検出する型特性、std::is_always_exhaustiveの提案。

ある型がそのレイアウトにパディングを持たないことを静的に検査するためには、C++17で追加されたstd::has_unique_object_representations<T>を使用することができます。この型特性は型の値表現(その型のオブジェクトの値を保持するビット列)とオブジェクト表現(バイト列)が正確に一致する型に対してtrueを示すもので、パディングビットはオブジェクト表現に含まれるが値表現には含まれないため、パディング有無の判定に使用できます。

ただ、この型特性はパディングビット判定のために導入されたものではないため、パディングビット判定には不向きな振る舞いをします。それは特に、浮動小数点数型に対してfalseを返すことです。

std::has_unique_object_representations_v<T>の定義は、型Tの2つのオブジェクトの値が等しいならばそのバイト表現も等しい場合にtrueとなります。浮動小数点数型には値として等しくてもバイト表現が異なるものが存在するためfalseを返します。典型的な例はNaNです。

struct Int16 { // note: 16 bytes, not bits 
  int i0, i1, i2, i3; 
};

static_assert(std::is_trivially_copyable_v<Int16>); 
static_assert( 
  sizeof(Int16) == sizeof(std::declval<Int16>().i0) + 
                   sizeof(std::declval<Int16>().i1) + 
                   sizeof(std::declval<Int16>().i2) + 
                   sizeof(std::declval<Int16>().i3) 
); 

struct Float16 { // note: 16 bytes, not bits 
  float f0, f1, f2, f3; 
};

static_assert(std::is_trivially_copyable_v<Float16>); 
static_assert( 
  sizeof(Float16) == sizeof(std::declval<Float16>().f0) + 
                     sizeof(std::declval<Float16>().f1) + 
                     sizeof(std::declval<Float16>().f2) + 
                     sizeof(std::declval<Float16>().f3)
);

struct Chunk {
  alignas(16) Int16 i16; 
  alignas(16) Float16 f16;  // エラーにならない
};

static_assert(sizeof(Int16)==16); 
static_assert(sizeof(Float16)==16);

// ok 
static_assert(
  std::has_unique_object_representations_v<Int16> 
); 

// ng 
static_assert( 
  std::has_unique_object_representations_v<Float16> 
);

この例のFloat16型はstatic_assertの検査がパスすることからも分かるように、明らかにパディングを持っていません。しかしstd::has_unique_object_representations_v<Float16>falseを返すため、パディングビットの有無を検査する目的には使用できないことが分かります。

この提案は、型のパディング有無を直接検査することのできる型特性std::is_always_exhaustive<T>を追加しようとするものです。

// ng 
static_assert( 
  std::has_unique_object_representations_v<Float16> 
);

// ok
static_assert( 
  std::is_always_exhaustive_v<Float16> 
);

なお、命名mdspanで使用されている同名のプロパティ取得関数からです(型のオブジェクトのバイトとメンバの対応関係の全射性による)。

P3709R0 Reconsider parallel ranges::rotate_copy and ranges::reverse_copy

並行アルゴリズム版のranges::rotate_copyranges::reverse_copyの戻り値についての再設計の提案。

P3179R7ではRangeアルゴリズムの並行アルゴリズムオーバーロードについての提案が行われています。そこにおいて特筆すべき設計点として、別の範囲に出力するタイプのアルゴリズムの一部では出力先として出力イテレータではなく出力範囲を取るようになることです。

これは、並行アルゴリズムでは入出力のどちらか一方(あるいは両方)で境界が分かっていることが望ましい場合が多いためです。しかしこの時に、入力のサイズに比べて出力のサイズが足りない場合に、そのアルゴリズムの戻り値として何を返すかが問題になります。

単純に済む場合は、入力イテレータのうち最後に処理された位置の次の位置を指すものを返すことです。ranges::copyの場合次のようになります

std::vector input{1,2,3,4,5};
std::vector<int> output(5);         // output with sufficient size
std::vector<int> smaller_output(3); // output with insufficient size

auto res1 = std::ranges::copy(std::execution::par, input, output);

// after copy invocation res.in == input.end() is true
assert(res1.in == input.end());

auto res2 = std::ranges::copy(std::execution::par, input, smaller_output);

// after copy invocation res.in == input.begin() + 3 is true
// res.in point to element equal 4
assert(res2.in == input.begin() + 3);

これにより、出力サイズが十分な場合は非並行アルゴリズムと同じ動作が得られ、不十分な場合は処理(コピー)されなかった入力の先頭への位置が得られます。

基本的には全ての並行アルゴリズムがこれに従っていますが少し変わった結果を返すアルゴリズムがあり、それがranges::rotate_copyranges::reverse_copyです。これらでは、非並行版と同じ戻り値型を使用してこの原則に従うことで、その他の出力を行うタイプの並行アルゴリズムとは一貫性がありますが、少し問題があります。

  • ranges::rotate_copy
    • middleによって指定された位置で分割された2つの部分範囲を持ち、処理が終了したときの最後のイテレータは入力のlastにはならない
      • 戻り値は、[middle, last - 1][first, middle]のどちらかになる
  • ranges::reverse_copy
    • 逆方向に実行されるため、処理が終了したときの最後のイテレータは入力のlastにはならない(入力が空ではない場合)

どちらの場合でも、そのまま処理が終了した位置の次を返すと決して入力範囲の終端にはならず、出力範囲のサイズが十分な場合にも非並行版の動作と一致しないことが問題になります。

このことが問題となるのは、次の3つの要因があるためです

  • 非並行版ではどちらも、入力範囲の最後lastを返す
  • 並行/非並行Rangeアルゴリズムの戻り値型は同一であるものの、出力サイズが十分な場合に並行版は入力に対して異なるイテレータを返す場合がある
  • 将来的に出力として範囲を取る設計の非並行アルゴリズムの導入が想定されており、この場合処理の終了地点と入力終端の両方を返すことができる

P3179での設計目標は並行版のアルゴリズムの戻り値型を可能な限り対応する非並行版と同じに保つことでしたが、これらのアルゴリズムでは別のものを返す必要があります。そのため、この提案ではこれらのアルゴリズムの戻り値についての設計変更を提案しています。

  • ranges::rotate_copy
    • 戻り値型を専用のin_in_out_resultin1, in2, outの3つのイテレータを返す型)のエイリアスに変更する
      • 出力が2つの部分範囲についての情報を含めるようにする
      • in1: [middle, last]内の停止位置
        • 出力範囲サイズが十分な場合はlast
        • 出力範囲サイズが十分でない場合は処理の停止位置(最後に処理した要素の次
      • in2: [first, middle]内の停止位置
        • 出力範囲サイズが十分な場合はmiddle
        • 出力範囲サイズが十分でない場合は処理の停止位置(最後に処理した要素の次
      • out: 変わらず(最後の出力位置の次
  • ranges::reverse_copy
    • 戻り値型を専用のin_in_out_resultin1, in2, outの3つのイテレータを返す型)のエイリアスに変更する
      • 出力が2つの部分範囲についての情報を含めるようにする
      • in1: 常にlast
      • in2: 処理の停止位置(最後に処理した要素
        • これは逆順に見た時の終了位置、最後にコピーした要素の位置
      • out: 変わらず(最後の出力位置の次

これはRangeアルゴリズムの戻り値についての基本原則である「計算した情報をなるべくすべて返す」ということと、上記のこれらのアルゴリズムにおける問題点を考慮し、なおかつ通常の非並行アルゴリズムから移行したときに戻り値の意味が変わることをコンパイルエラーとして報告できるようにすることを意図しています(特に、reverse_copyで戻り値型が変更されるのはその側面が強い)。

現在の非並行版ではin_out_resultという2要素の型を使用しており、メンバ変数名もその数も異なるため、autoで受けて使用していたり構造化束縛していたりしてもエラーになります。

P3710R0 zstring_view: a string_view with guaranteed null termination

null終端が保証されたstd::string_viewであるstd::zstring_viewの提案。

モチベーションなどは以前のP3655R0と共通するのでそちらを参照

これはP3655R0とは独立して出された同じものについての提案であるようです。基本的なアイデアもインターフェースもほとんど同じものですが、こちらの方がコンストラクタが詳細に検討されているほか、筆者の方による実装と展開の経験について触れられています。

この提案はP3655R2にマージされ、そちらに統一して検討していくことになったようです。

P3711R0 Safer StringViewLike Functions for Replacing char* strings

char*文字列の使用を排除するためのユーティリティ関数の提案。

この提案はP3566の方針を進めて、境界の無い文字列の使用を回避するために、std::string_viewメンバ関数の一部に対応するフリー関数を提案するものです。特に、P3566R1で提案されている[Safe|Unsafe]StringViewLikeコンセプトを使用することで安全な文字列操作とそうでないものを区別しようとしています。

SafeStringViewLikeコンセプトは境界が明確なstd::string_view-likeな型(P3566で提案されているような暗黙的にstd::string_viewに変換可能な型)を表し、UnsafeStringViewLikeはコンセプトは境界が明確なstd::string_view-likeな型(char*など)を表します。

この提案での安全な関数とは、その関数の引数によって定義される有界な範囲内でその処理を実行できること、と定義します。安全ではない操作に対しては、P3566に倣ってstd::unsafe_lengthタグの明示的な指定が必要になります。

ここで提案されているのは次のもので、全てフリー関数テンプレートとして提案されています。

  • starts_with(str, prefix): std::string_view::starts_with()と同等のもの
    • 最初の引数が有界であれば、安全
    • 最初の引数が有界でなくても、2番目の引数が有界であれば、安全
    • 両方の引数が有界でない場合、安全ではない
  • ends_with(str, suffix): std::string_view::ends_with()
    • 最初の引数が有界であれば、安全
    • 最初の引数が有界でなければ、安全ではない
  • join(strs...): 文字列を連結する
    • 全ての引数が有界であれば、安全
    • 1つでも有界出ないものがあれば、安全ではない
  • is_null_or_empty(char_ptr): 文字列ポインタがnullptrであるか空文字であるかを判定する
    • const CharT* sに対して、(!s) || (!*s)

starts_with()の例

// 安全な starts_with
template<SafeStringViewLike TString, UnsafeStringViewLike TPrefix> 
bool starts_with(TString&& s, TPrefix&& p) { 
  return string_view{forward<TString>(s)}.starts_with(forward<TPrefix>(p)); 
}

// 安全な starts_with
template<UnsafeStringViewLike TString, SafeStringViewLike TPrefix> 
bool starts_with(TString&& s, TPrefix&& p) { 
  return string_view{forward<TString>(s)}.starts_with(forward<TPrefix>(p)); 
}

// 安全ではない starts_with
template <UnsafeStringViewLike TString, UnsafeStringViewLike TPrefix> 
bool starts_with(carb::cpp::unsafe_length_t, TString&& s, TPrefix&& p) {
  return string_view{unsafe_length, forward<TString>(s)}.starts_with(unsafe_length, forward<TPrefix>(p)); 
} 

このような関数群はNVIDIA Omniverse Foundation Libraryというライブラリで使用され、コードベースでP3566で提案されている文字列実装に置き換えるために重要な役割を果たしてたとのことです。

P3712R0 2025-05 Library Evolution Polls

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

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

これはC++29導入を目指してLWGに転送するための投票です。

P3714R0 Virtual values have Virtual Value

P3565R0の提案についての否定的な意見書。

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

この提案では、P3565R0で提案されている新しい浮動小数点数モデルがコンパイラの最適化にとって障壁となることを短く説明しています。明確に述べてはいませんが、P3565に反対を示しているようです。

P3715R0 Tightening floating-point semantics for C++

C++における浮動小数点数セマンティクスの厳密な指定のために理解すべき現在の浮動小数点数演算周りの事についてまとめた文書。

P2746を皮切りに、C++における浮動小数点数の動作・セマンティクスを明確化しようとする動きがあり、これを受けて委員会としても浮動小数点数のセマンティクス明確化に本腰を上げ始めています。

しかし、現在提出されている提案は浮動小数点数の全体ではなく一部の側面についてのセマンティクス明確化を行おうとする提案になっています。浮動小数点数に関する問題のほとんどは互いに関連があり、それについて認識・理解しないまま進めてしまうとある問題を解決するために別の問題を深刻化してしまったり、あるいは別の問題の改善を困難にしてしまったりする恐れがあります。

この文書は、浮動小数点数に関して包括的な理解を促進するために、現在の状況の確認や将来の提案の評価のために必要な情報と浮動小数点数への理解を提供しようとするものです。

この文書の後半では、浮動小数点数のセマンティクスの問題についての解決策空間の探索と、最適な解決策と思われる案についての筆者の方の個人的な見解を提示しているものの、まだ何かを提案してはいません。

P3716R0 Subsetting

C++言語のサブセットを定義し、それを強制適用できるようにするための機能の提案。

この提案はプロファイル提案などと目的は同じであり、C++の言語機能や構成要素のうちで未定義動作に繋がりうる危険な操作や使用が推奨されないものなどの使用を制限することで、C++コードの複雑性を低下させたり、安全性を高めようとするものです。

言語のサブセット化は標準がそれを回避しようとしているだけで一般の開発者は既に行っていることです。例えば、組み込み環境で動的メモリ確保を無効化する、例外やRTTIを無効化する、コンパイラオプションで特定の警告をエラーとして扱う、特定の言語バージョンに制限する、などです。

この提案は、このような言語のサブセットを定義する標準的な方法を提供し、それを強制することのできる言語機能を提供しようとするものです。

提案によれば、サブセットは次のいずれか1つ以上の方法で定義されます

  • 言語キーワードを完全に禁止する
  • 特定の型の使用を禁止する
  • 特定の関数の使用を禁止する
  • 特定の言語機能の列挙された集合を禁止する
    • 配列のdecay、Cの可変長関数、ポインタ演算など

これに加えて、サブセットは次のような能力を持ちます

  • サブセットのサブセットを定義できるようにする
  • 各サブセットは禁止するルールが抑制可能かどうかを示す
  • サブセットの集合はオープンエンドである必要があり、他の組織(MISRA, AutoSAR, LLVM, Microsoftなど)が独自のサブセットを定義できるようにする

サブセット機能の設計原則として次のことが挙げられています

  • コードはサブセットがない場合と全く同じようにコンパイルされて動作するか、コンパイルされないかのどちらか
  • サブセットは常に直交的に結合する
    • サブセット間に相互作用はなく、動作も変化しない
  • コンパイラとリンカは、プログラムの一部にサブセットで制限されたコードがあることで特別な情報や権限を得ることがない
  • サブセットの定義は、必ずしもWG21だけが行うわけではない
  • 特定のサブセット化ルールの抑制は、任意の大きさの抑制リストを必要とせずに移植可能である必要がある

これはプロファイル提案とほとんど同じことを目指しているようにも思えますが、プロファイルは言語のサブセット化を目指すものではありません。プロファイルはあくまで、安全性を向上させるために一部の操作が禁止されたり静的解析が保証されたりする、いわばSafeブロックをC++に導入しようとするものです。一方で、ここでのサブセット化はSafeブロックを導入するようなものではなく、安全性向上だけではなくC++の複雑さの低減も目指しており、サブセット化によって言語の表現力が低下することも許容します。

P3717R0 Update Annex E onto Unicode 16

C++標準文書のAnnex E(UAX31への参照)をUnicode 16に更新する提案。

C++規格書のAnnex Eは、P1949でのUAX31対応の際にC++にどのようにそれを適用し、またどこに適用していないかなどの選択を記録し説明したものです。

P1949採択と並行してUAX31の更新が行われており、P1949のC++導入後にユニコード標準が更新されUAX31にも更新がありました。別の提案のP3658ではそれによって識別子として使用可能になる文字を拡張することが提案されています。また、空白文字についての整理も行われているため、C++の構文上の空白文字についての扱いを整理・改善する機会にもなりえます。

この提案ではそのような変更を一切提案していませんが、Annex Eの内容をユニコード16とAUX31の更新に追随させ、特にユニコード標準から削除された「UAX31-R1a Restricted Format Characters」への適合状況に関する言及を削除することを提案しています。

SG16での議論によると、ここでの主な提案事項である「UAX31-R1a Restricted Format Characters」の削除に関してはCWG Issue 2843による解決を受け入れることになったようです。ただし、こちらのイシューではユニコードへの参照の更新は行っておらず、それはこの提案を利用して引き続き行われるようです。

おわり

この記事のMarkdownソース




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

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