文書の一覧
全部で81本あります。
もくじ
- N5035 2026-03 WG21 admin telecon meeting (obsolete; replaced by N5037)
- N5036 ISO/IEC JTC1/SC22/WG21 White Paper, Extensions to C++ for Transactional Memory Version 2
- N5037 2026-03 WG21 admin telecon meeting
- P0876R22 fiber_context - fibers without scheduler
- P2000R5 Direction for ISO C++
- P2285R1 Are default function arguments in the immediate context?
- P2583R0 Symmetric Transfer and Sender Composition
- P2728R11 Unicode in the Library, Part 1: UTF Transcoding
- P2929R2 simd_invoke
- P2953R4 Forbid defaulting operator=(X&&) &&
- P2964R2 Allowing user-defined types in std::simd
- P3045R7 Quantities and units library
- P3181R1 Atomic stores and object lifetimes
- P3385R7 Attributes reflection
- P3411R5 any_view
- P3440R2 Add mask_from_count function to std::simd
- P3596R0 Undefined Behavior and IFNDR Annexes
- P3642R4 Carry-less product: std::clmul
- P3666R3 Bit-precise integers
- P3688R6 ASCII character utilities
- P3724R3 Integer division
- P3737R3 std::array is a wrapper for an array!
- P3816R2 Hashing meta::info
- P3822R1 Conditional noexcept specifiers in compound requirements
- P3844R3 Restore simd::vec broadcast from int
- P3844R4 Reword [simd.math] for consteval conversions
- P3856R4 New reflection metafunctions - is_structural_type (US NB comment 49)
- P3856R5 New reflection metafunction - is_structural_type (US NB comment 49)
- P3864R1 Correctly rounded floating-point maths functions
- P3874R1 Should C++ be a memory-safe language?
- P3876R1 Extending support to more character types
- P3899R1 Clarify the behavior of floating-point overflow
- P3904R1 When paths go WTF: making formatting lossless
- P3932R0 Resolve LWG4470: Fix integer-from in [simd]
- P3936R1 Safer atomic_ref::address (FR-030-310)
- P3938R1 Values of floating-point types
- P3941R2 Scheduler Affinity
- P3953R1 Rename std::runtime_format
- P3966R0 2026-01 Library Evolution Poll Outcomes
- P3969R0 Fixing std::bit_cast of types with padding bits
- P3970R0 Profiles and Safety: a call to action
- P3971R0 Generalised type rebinding for structures of uniform elements
- P3973R0 bit_cast_as: Element type reinterpretation for std::simd
- P3977R0 A New Taxonomy for Contracts
- P3978R0 constant_wrapper should unwrap on call and subscript
- P3978R1 constant_wrapper should unwrap on call and subscript
- P3978R2 constant_wrapper should unwrap on call and subscript
- P3980R0 Task's Allocator Use
- P3981R0 Better return types in std::inplace_vector and std::exception_ptr_cast
- P3981R1 Better return types in std::inplace_vector and std::exception_ptr_cast
- P3982R0 Fix the meaning of strided_slice::extent for C++26
- P3983R0 simd object representation
- P3984R0 A type-safety profile
- P3985R0 Concepts for std::simd
- P4003R0 Coroutines for I/O
- P4004R0 Reconsider CWG 1395 "Partial ordering of variadic templates reconsidered"
- P4005R0 A proposal for guaranteed-(quick-)enforced contracts
- P4006R0 Transparent Function Objects for Shift Operators
- P4007R0 Senders and Coroutines
- P4008R0 Clean Modular Mode: Legacy Opt-out for C++
- P4009R0 A proposal for solving all of the contracts concerns
- P4010R0 Add funnel shift operations to bit header
- P4011R0 Redefining narrow contract
- P4012R0 value-preserving consteval broadcast to simd::vec
- P4014R0 The Sender Sub-Language
- P4015R0 Enforcing Contract Conditions with Statements
- P4016R0 Canonical Parallel Reduction: A Fixed Expression Structure for Run-To-Run Consistency
- P4019R0 constant_assert
- P4020R0 Concerns about contract assertions
- P4021R0 compile_assert - an assert that evaluates at compile time
- P4022R0 Remove try_append_range from inplace_vector for now
- P4023R0 Strategic Direction for AI in C++: Governance, and Ecosystem
- P4024R0 Guidance on Building Consensus and Converging Proposals
- P4025R0 The SG19 Priority List for C++29/32
- P4026R0 Core Issue 3123 "Global lookup for begin and end for expansion statements"
- P4027R0 2026-02 Library Evolution Polls
- P4029R0 The SG14 Priority List for C++29/32
- P4030R0 Endian Views
- P4031R0 Rename system_context_replaceability namespace
- P4032R0 Strong ordering for meta::info
- P5000R0 Direction for ISO C++29
- おわり
N5035 2026-03 WG21 admin telecon meeting (obsolete; replaced by N5037)
3月の全体会議に先立って行われる、WG21管理者ミーティングの案内。
N5037がより新しいバージョンのようです。
N5036 ISO/IEC JTC1/SC22/WG21 White Paper, Extensions to C++ for Transactional Memory Version 2
P2066ベースの最小トランザクショナルメモリのWhite Paper。
これは以前のTransactional Memory TSv2(N4923)をTSではなくホワイトペーパーとして発行するためのものです。
N5037 2026-03 WG21 admin telecon meeting
3月の全体会議に先立って行われる、WG21管理者ミーティングの案内。
P0876R22 fiber_context - fibers without scheduler
スタックフルコルーチンのためのコンテキストスイッチを担うクラス、fiber_contextの提案。
以前の記事を参照
- P0876R11
fiber_context- fibers without scheduler - WG21月次提案文書を眺める(2022年10月) - P0876R12
fiber_context- fibers without scheduler - WG21月次提案文書を眺める(2023年02月) - P0876R13
fiber_context- fibers without scheduler - WG21月次提案文書を眺める(2023年04月) - P0876R14
fiber_context- fibers without scheduler - WG21月次提案文書を眺める(2023年10月) - P0876R15
fiber_context- fibers without scheduler - WG21月次提案文書を眺める(2024年02月) - P0876R16
fiber_context- fibers without scheduler - WG21月次提案文書を眺める(2024年04月) - P0876R17
fiber_context- fibers without scheduler - WG21月次提案文書を眺める(2024年07月) - P0876R18
fiber_context- fibers without scheduler - WG21月次提案文書を眺める(2024年10月) - P0876R19
fiber_context- fibers without scheduler - WG21月次提案文書を眺める(2025年01月) - P0876R20
fiber_context- fibers without scheduler - WG21月次提案文書を眺める(2025年03月) - P0876R21
fiber_context- fibers without scheduler - WG21月次提案文書を眺める(2025年07月)
このリビジョンでの変更は
- N5032にリベース
- ネットワーク関連の最新提案を参照に追加
などです。
P2000R5 Direction for ISO C++
C++26に向けて、C++の標準化の方向性を示す文書。
以前の記事も参照
このリビジョンでの変更は
- 短期、中期的な事項についてP5000に移動
- P3970R0/P4024R0/P4023R0へのリンクを追加
などです。
安全性・セキュリティやAI、合意形成についての言及がP3970R0/P4024R0/P4023R0へのリンクとともに追記されています。
P2285R1 Are default function arguments in the immediate context?
関数テンプレートのテンプレートパラメータに依存するデフォルト引数のインスタンス化の失敗をSFINAEできるようにする提案。
以前の記事を参照
このリビジョンでの変更は
- 提案のトーンを提案から分析へと落とした
- 実装上の課題に関する議論を追加
- 関数デフォルト引数のラムダ式についての分析を追加
- デフォルトメンバ初期化子がデフォルトコンストラクタの削除に影響を与える場合を検討範囲に追加
などです。
この提案はC++26に提出されたNBコメントに関連して更新されたようですが追求は停止されており、P4149が引き継いでいるようです。
P2583R0 Symmetric Transfer and Sender Composition
コルーチンとsenderの統合において、対称転送が不可能になるトレードオフが生じていることを解説する文書。
コルーチンの対称転送(Symmetric Transfer)は、コルーチン内で別のコルーチンを再開する際に追加のスタックフレームを消費せずに別のコルーチンへ制御を移す仕組みです。
C++コルーチンが対称転送をサポートする仕組みは、awaitableと呼ばれる型の.await_suspend()関数の戻り値としてコルーチンハンドルを返すようにすることです。
.await_suspend()はその戻り値によって3種類の動作を指定します
// 宣言の例、いずれか1つを選択する void await_suspend(coroutine_handle<>); // unconditional suspend bool await_suspend(coroutine_handle<>); // conditional suspend coroutine_handle<> await_suspend(coroutine_handle<>); // symmetric transfer
そして、対称転送はコルーチン内部でコルーチンを呼び出す際に起こりうるスタックオーバーフローを回避するためにも使用できます。これは典型的にはコルーチンtask型内で別のコルーチンをco_awaitするときに発生し、特にforループの中でそれが起こっていると問題になる可能性が高くなります。
task completes_synchronously() { co_return; } task loop_synchronously(int count) { for (int i = 0; i < count; ++i) { co_await completes_synchronously(); // countの値次第でスタックオーバーフローが発生する(対称転送をサポートしない場合) } }
これは、元のコルーチン(loop_synchronously()のコルーチン)と内部で呼ばれるコルーチン(completes_synchronously()のコルーチン)との間の中断-再開が相互的な再帰関数呼び出しになってしまうことで、関数呼び出しに伴うスタックフレームが積み重なり、いずれスタックを使い果たすことによっておこります。
対称転送はC++コルーチンシステムの制約によって現在のコルーチンの中断と別のコルーチンの再開(.resume()呼び出し)が末尾呼び出しになることを保証するものでもあり、これによって結果的にコンパイラの末尾呼び出し最適化によって.resume()呼び出し時に積まれるスタックフレームを削除することができるようになることでスタックオーバーフローが回避されます。
std::execution::taskはsender/receiverモデルをベースとして構築されたtask型であり、senderとコルーチンの相互運用メカニズムによってコルーチンtask型としても使用できるものです。std::execution::taskはかなり急いでC++26に導入されたものであるためその仕様には粗が目立ち、コルーチンとして使用された場合に対称転送をサポートしていません。そのため、C++26内で対称転送をサポートするための作業が進行しています(P3801R0やP3796R1など)。
したがって、execution::taskではコルーチン内コルーチン呼び出し(senderをコルーチンとして扱う場合でも)においてスタックオーバーフローのリスクがあります。
ex::task<void> f(int i); ex::task<void> g(int total) { for (auto i = 0; i < total; ++i) { co_await f(i); // totalの値によってスタックオーバーフローの可能性がある } }
execution::taskに関する議論では、execution::taskが対称転送を持たないことを単に機能の欠如であり追加すればいいと考えているようですが、この文書はその認識は間違いで、execution::taskが対称転送をサポートしていない(するのが難しい)のはsender/receiverモデルのトレードオフであると指摘しています。
std::executionではsenderをコルーチンとして扱う方法もコルーチンをsenderとして扱う方法も提供されていますが、いずれの場合でも対称転送は行われていません。
対称転送のサポートのためにはawaitableの.await_suspend()からコルーチンハンドルを直接返す必要があるのですが、senderチェーンおよびそれに接続されたreceiverはcoroutine_handle<>を持っていません。これは当然で、sender/receiverモデルにおいてはsenderもreceiverも単なる構造体でありその合成は構造体のネストで行われ、コルーチンフレームの様な追加の状態を必要としない(スタックで完結する)ため、coroutine_handle<>の様なハンドルを必要としないためです。
コルーチンをsenderとして扱う場合、そこで使用されるreceiver(awaitable-receiver)はコルーチンハンドルへアクセスすることができます。しかし、この場合でもsender/receiverプロトコルの制約(完了関数が戻り値を返さない)によってそのコルーチンハンドルを適切に返す方法が無いため、対称転送をサポートできません。
簡単にまとめると次のようになっています
senderアルゴリズムはコルーチンではなく構造体であるreceiverを作成し、これにはコルーチンハンドルがない- コルーチン上に構築される
receiverであってもset_value()がvoidを返すreceiverの持つコルーチンハンドルを適切に返す方法がない
.await_suspend()は合成レイヤーでもsender/receiverプロトコルでも提供されない値を返すことができない
いずれにしても対称転送に必要なcoroutine_handle<>がありません。
ただし、これはstd::executionモデルの欠陥を表すものではありません。std::executionはヘテロジニアスコンピューティング向け(典型的にはGPU)に設計されており、その領域では対称転送がメリットをもたらさないためそれが考慮されていません。これが問題となるのは、コルーチンと連携しようとした場合のみです。
これはstd::executionの設計の不備やexecution::taskの機能不足などではなく、sender/receiverモデルの設計選択から生じるアーキテクチャ上のトレードオフであるとこの文書は強く指摘しています。
文書では、この問題の解決のためには次の3つの方向性のいずれかを取ることができるとしています
- 既知のコルーチン型に対して
receiver抽象化をバイパスするドメイン固有のカスタマイズ方法を提供する - 末尾呼び出しを保証する言語機能を提供する
- コルーチン間の合成を直接
awaitableプロトコルで処理(senderとして扱わない)し、必要な境界でのみsenderモデルを使用するtask型を採用する
この文書はこれらの事を提案しているのではなく、このようなトレードオフがあることを議論の際に認識しておくことを促すものです。
P2728R11 Unicode in the Library, Part 1: UTF Transcoding
以前の記事を参照
- P2728R0 Unicode in the Library, Part 1: UTF Transcoding - WG21月次提案文書を眺める(2023年01月)
- P2728R3 Unicode in the Library, Part 1: UTF Transcoding - WG21月次提案文書を眺める(2023年05月)
- P2728R5 Unicode in the Library, Part 1: UTF Transcoding - WG21月次提案文書を眺める(2023年07月)
- P2728R6 Unicode in the Library, Part 1: UTF Transcoding - WG21月次提案文書を眺める(2023年08月)
- P2728R7 Unicode in the Library, Part 1: UTF Transcoding - WG21月次提案文書を眺める(2024年10月)
- P2728R8 Unicode in the Library, Part 1: UTF Transcoding - WG21月次提案文書を眺める(2025年10月)
- P2728R10 Unicode in the Library, Part 1: UTF Transcoding - WG21月次提案文書を眺める(2025年12月)
このリビジョンでの変更は
- コード単位アダプタにおける配列の拒否に関する記述を修正
- バイトオフセットとエンディアンの処理例を追加
などです。
P2929R2 simd_invoke
std::simdで組み込み関数の使用を簡易にする呼び出しラッパ関数の提案。
以前の記事を参照
R1での変更は
- 最新のWDに合わせて更新
- Callableの機能を調べるように変更し、
invoke_indexedを削除
このリビジョンでの変更は
- 最新のWDに合わせて更新
std::invokeとの混同を避けるために名前を変更- Callableの順序をインデックスの順序と指定
- 多くの細かな説明と表現の修正
- ConstraintsをMandatesに変更し、ハードエラーとして扱うようにした
- prototype-based chunkingがサポートされていない理由の説明を追加
などです。
P2953R4 Forbid defaulting operator=(X&&) &&
右辺値修飾されたdefault代入演算子を禁止する提案。
以前の記事を参照
- P2953R0 Forbid defaulting
operator=(X&&) &&- WG21月次提案文書を眺める(2023年12月) - P2953R1 Forbid defaulting
operator=(X&&) &&- WG21月次提案文書を眺める(2025年01月) - P2953R2 Forbid defaulting
operator=(X&&) &&- WG21月次提案文書を眺める(2025年09月) - P2953R3 Forbid defaulting
operator=(X&&) &&- WG21月次提案文書を眺める(2026年01月)
このリビジョンでの変更は
- ambitious approach の実装と展開に関する検討事項を追加
などです。
P2964R2 Allowing user-defined types in std::simd
std::simdのデータ型としてユーザー定義型を使用できるようにする提案。
以前の記事を参照
- P2964R0 Allowing user-defined types in
std::simd- WG21月次提案文書を眺める(2024年02月) - P2964R1 Allowing user-defined types in
std::simd- WG21月次提案文書を眺める(2024年05月)
このリビジョンでの変更は
- カスタマイズ重視のアプローチから特性ベースのアプローチへ変更
- カスタマイズに関する項目をdesign alternativeセクションへ移動
- 実装例を追加
などです。
このリビジョンでの特性ベースアプローチでは、次の制約を満たす型をstd::simdにおいて要素型として使用できるようになります
- トリビアルコピー可能
- サイズが 1, 2, 4, 8, 16 バイトのいずれか
alignof(T) <= sizeof(T)のアライメント制約
ただし、これだと該当する型が多数あるため、オプトアウトするためのメカニズムとしてdisable_vectorization変数テンプレート(trueになるように特殊化)を用意しています。
ポインタ型、共用体、CV修飾された型、空の型はデフォルトでオプトアウトするようにしています。
std::simdの演算で使用するためには、対応する演算子がユーザー定義型でも有効である必要があります。例えば次のような制約によってチェックされます
template<typename T, typename BinaryOp> concept supported-binary-op = /* exposition only */ (is_arithmetic_v<T> || (is_enum_v<T> && !is_scoped_enum_v<T>)) ? requires(T a, T b) { T(BinaryOp{}(a, b)); } : // 組み込み型 requires(T a, T b) { { BinaryOp{}(a, b) } -> same_as<T>; }; // ユーザー定義型、戻り値型チェックが厳格
struct Meters { float value; Meters operator+(Meters rhs) const { return Meters{value + rhs.value}; } bool operator<(Meters rhs) const { return value < rhs.value; } }; simd::vec<Meters> a, b; auto sum = a + b; // ✅ OK: operator+ returns Meters auto mask = a < b; // ✅ OK: operator< returns bool struct NoAdd { float value; }; simd::vec<NoAdd> x, y; auto result = x + y; // ❌ Error: operator+ not defined struct DifferentReturn { int16_t value; int32_t operator+(DifferentReturn) const; // Change return type }; simd::vec<DifferentReturn> v, w; auto bad = v + w; // ❌ Error: int32_t is not DifferentReturn
数学関数に関してはユーザー定義型に対して特殊化してあるものが無いと使用できません。これは効率的なデフォルトを提供することが難しいためです。
vec<MyFloat> v; auto result = sin(v); // ❌ Compile error: constrained to arithmetic types
template<typename Abi> basic_vec<MyFloat, Abi> sin(const basic_vec<MyFloat, Abi>& v) { // User-provided vectorized implementation }
この提案はintelの社内向けのstd::simd実装で実装されてテストされているようで、報告によればユーザー定義型を使用した場合でも要素ごと演算をベクトル化して手書きintrinsicと同等のコードを生成できたとしています。ただし、reduce(リダクション操作)のような複雑な演算ではうまくいかない場合もあったとのことです。
P3045R7 Quantities and units library
物理量と単位を扱うライブラリ機能の提案。
以前の記事を参照
- P3045R0 Quantities and units library - WG21月次提案文書を眺める(2024年02月)
- P3045R1 Quantities and units library - WG21月次提案文書を眺める(2024年05月)
- P3045R3 Quantities and units library - WG21月次提案文書を眺める(2024年10月)
- P3045R4 Quantities and units library - WG21月次提案文書を眺める(2024年12月)
- P3045R5 Quantities and units library - WG21月次提案文書を眺める(2025年01月)
- P3045R6 Quantities and units library - WG21月次提案文書を眺める(2025年07月)
このリビジョンでの変更は
- 対象読者の更新
- いくつかの章を大幅に簡略化し、コンテンツの重複を削除
- コンパイラエクスプローラーの例をmp-unisの最新版に合わせて更新
- Creating distinct quantity kinds with
is_kindセクションを追加 - Safety セクションを再構成し、6つの異なる安全性レベルを中心に構成
AssociatedUnitコンセプトを削除PrefixableUnitコンセプトを追加- non-ideal alternativesの議論を0リテラルとの直接比較に置き換え
named_constantのサポートを追加pi mag_constantをpi_cにリネームし、πをnamed_constant名として使用できるようにしたquantity_values型特性をrepresentation_valuesに変更quantity::one()静的メンバ関数を削除- SG20向けに、Teachability セクションを大幅に拡充
などです。
P3181R1 Atomic stores and object lifetimes
オブジェクトへの最後の書き込みがその破棄の前に行われなければならない、というルールを緩和する提案。
以前の記事を参照
このリビジョンでの変更は
- typoの修正
- “Towards a solution”という表現をより具体的な提案文へ置換
- [basic.life]全体を含むように文言を拡張
などです。
P3385R7 Attributes reflection
リフレクションにおいて、エンティティに指定されている属性の情報を取得・付加できるようにする提案。
以前の記事を参照
- P3385R0 Attributes reflection - WG21月次提案文書を眺める(2024年09月)
- P3385R1 Attributes reflection - WG21月次提案文書を眺める(2024年10月)
- P3385R2 Attributes reflection - WG21月次提案文書を眺める(2024年12月)
- P3385R3 Attributes reflection - WG21月次提案文書を眺める(2025年01月)
- P3385R4 Attributes reflection - WG21月次提案文書を眺める(2025年03月)
- P3385R5 Attributes reflection - WG21月次提案文書を眺める(2025年05月)
- P3385R6 Attributes reflection - WG21月次提案文書を眺める(2025年10月)
このリビジョンでの変更は
[[assume]]サポートを削除
などです。
P3411R5 any_view
viewを型消去するためのview、any_viewの提案。
以前の記事を参照
- P3411R0
any_view- WG21月次提案文書を眺める(2024年10月) - P3411R1
any_view- WG21月次提案文書を眺める(2025年01月) - P3411R2
any_view- WG21月次提案文書を眺める(2025年05月) - P3411R3
any_view- WG21月次提案文書を眺める(2025年07月) - P3411R4
any_view- WG21月次提案文書を眺める(2025年10月)
このリビジョンでの変更は
approximately_sizedをサポートview_interfaceから派生するようにする
などです。
P3440R2 Add mask_from_count function to std::simd
先頭N個のビットが立ったsimd_maskを作成するためのファクトリ関数を追加する提案。
以前の記事を参照
- P3440R0 Add
n_elementsnamed constructor tostd::simd- WG21月次提案文書を眺める(2024年10月) - P3440R1 Add
n_elementsnamed constructor tostd::simd- WG21月次提案文書を眺める(2025年07月)
このリビジョンでの変更は
- 関数名を
n_elements()からmask_from_count()に変更 - 事前条件の動作を精査し、明文化
- 汎用コードでの自然な使用のために、simd-genericフリー関数に変更
- マスク型以外のベクトル型/スカラ型を受け入れる
- パフォーマンスに関する考察を追加
- 手動マスク生成アプローチに対するより強い反論を盛り込み、モチベーションセクションを強化
- 範囲ベースバージョンの設計案を追加
などです。
提案文書より、サンプルコード
for (int i = 0; i < count; i += simd::vec<float>::size()) { auto block = partial_load<simd::vec<float>>(data.subspan(i)); auto mask = mask_from_count<simd::vec<float>>(count - i); process(block, mask); // Same code path for all iterations }
P3596R0 Undefined Behavior and IFNDR Annexes
UB/IFNDRが発生する箇所のリストを規格に追加する提案。
UB(未定義動作)とIFNDR(ill-formedであるものの診断不要)と指定されているコア言語の要素・操作について、これをリスト化することは以前から必要な作業として認識されていました(P1705R1やP2234R0、P3075R0など)。
この提案はそれを行って標準文書にAnnexとして付加しておこうとするものです。
Contractsを使用したコア言語UBに暗黙の契約を付加する提案(P3100)では既にこのリストが添付されており、プロファイル機能やコア言語UBホワイトペーパーの取り組みにおいてこのようなリストが必要とされたことから、今回具体的に作業が行われて提案されていると思われます。
この成果は規格文書リポジトリのub-ifndrブランチで既に共有されており、CWGのレビュー準備ができているとのことです。
P3642R4 Carry-less product: std::clmul
整数のキャリーレス乗算を行う関数の提案。
以前の記事を参照
- P3642R0 Carry-less product:
std::clmul- WG21月次提案文書を眺める(2025年05月) - P3642R2 Carry-less product:
std::clmul- WG21月次提案文書を眺める(2025年07月) - P3642R3 Carry-less product:
std::clmul- WG21月次提案文書を眺める(2025年12月)
このリビジョンでの変更は
- §4. Possible implementation に高速な実装例を追加
- コンパイラ実装が純粋なライブラリ実装よりもすぐれている理由について追記
@llvm.clmulがLLVMに追加されたことを言及wide_resultのoperator<=>をhidden friendに戻した
などです。
P3666R3 Bit-precise integers
C23の_BitIntをC++に導入する提案。
以前の記事を参照
- P3666R0 Bit-precise integers - WG21月次提案文書を眺める(2025年09月)
- P3666R1 Bit-precise integers - WG21月次提案文書を眺める(2025年10月)
- P3666R2 Bit-precise integers - WG21月次提案文書を眺める(2025年12月)
このリビジョンでの変更は
- N3747がWG14でC2y向けに承認されたことを言及
std::simdのサポートを削除- §5.10. Lack of simd support for bit-precise integers セクションを参照
std::atomicのサポートを削除- §5.9. Lack of atomic support for bit-precise integers セクションを参照
to_string()がconstexprになったためP3438R0への言及を削除- [utility.intcmp]への変更が不要になったため削除
などです。
P3688R6 ASCII character utilities
ASCIIに関連する文字の判定・処理関数群を提供する提案。
以前の記事を参照
- P3688R0 ASCII character utilities - WG21月次提案文書を眺める(2025年05月)
- P3688R1 ASCII character utilities - WG21月次提案文書を眺める(2025年07月)
- P3688R2 ASCII character utilities - WG21月次提案文書を眺める(2025年08月)
- P3688R3 ASCII character utilities - WG21月次提案文書を眺める(2025年09月)
- P3688R4 ASCII character utilities - WG21月次提案文書を眺める(2025年10月)
- P3688R5 ASCII character utilities - WG21月次提案文書を眺める(2025年12月)
このリビジョンでの変更は
- §3.5. Case-insensitive comparison functions における設計議論を拡充
is_asciiの使用をascii_isに置換ascii_is_punctuationの説明からu32string_viewへの言及を削除- ASCII-compatibleの定義を改訂
などです。
P3724R3 Integer division
商と剰余を様々な丸めモードで計算するライブラリ関数の提案。
以前の記事を参照
- P3724R0 Integer division - WG21月次提案文書を眺める(2025年07月)
- P3724R1 Integer division - WG21月次提案文書を眺める(2025年10月)
- P3724R2 Integer division - WG21月次提案文書を眺める(2025年12月)
このリビジョンでの変更は
- §2.1.1. Language support for integer division にJuliaのサポートを追記
- §4.4.3. To-nearest-rounding division and tie breaking に各タイブレーク丸めモードの説明と理由を追加
- §4.4.4. ISO/IEC 60559 rounding modes に
roundTiesTowardZeroを追加
などです。
P3737R3 std::array is a wrapper for an array!
std::arrayの実装自由度を制限する提案。
以前の記事を参照
- P3737R0
std::arrayis a wrapper for an array! - WG21月次提案文書を眺める(2025年09月) - P3737R1
std::arrayis a wrapper for an array! - WG21月次提案文書を眺める(2025年10月) - P3737R2
std::arrayis a wrapper for an array! - WG21月次提案文書を眺める(2025年12月)
このリビジョンでの変更は
- §2.1. What the standard says で
data()について言及 - §3. Motivation with implicit object creation and providing storage を拡張
data()の仕様を簡素化
などです。
P3816R2 Hashing meta::info
meta::infoのハッシュサポートを追加する提案。
以前の記事を参照
- P3816R0 Hashing
meta::info- WG21月次提案文書を眺める(2025年07月) - P3816R1 Hashing
meta::info- WG21月次提案文書を眺める(2025年12月)
このリビジョンでの変更は
- “Ordered maps and sets”セクションを“Motivation”に統合
- 注釈の例を追加
などです。
P3822R1 Conditional noexcept specifiers in compound requirements
複合要件において条件付きnoexcept指定をサポートする提案。
以前の記事を参照
このリビジョンでの変更は
- 実装とhistoryに関するセクションを追加
- サンプルコードを追加
if/while/switchの条件との曖昧さを避けるためにconditionをconstant-expressionに置き換え
などです。
P3844R3 Restore simd::vec broadcast from int
↓
P3844R4 Reword [simd.math] for consteval conversions
simd::vecに対する数学関数の呼び出しにおいて、引数のsimd::vecへの変換を呼び出し側で行うようにする提案。
以前の記事を参照
- P3844R0 Restore
simd::vecbroadcast fromint- WG21月次提案文書を眺める(2025年10月) - P3844R2 Restore
simd::vecbroadcast fromint- WG21月次提案文書を眺める(2025年12月)
R3での変更は
- 誤って削除されていた
math-floating-pointに関する文言を復帰 hypotの例の誤った制約を修正- 提案する文言を、
constevalコンストラクタと[simd.math]の部分に分割
このリビジョンでの変更は
constevalコンストラクタに関する提案をP4012R0に分離- [simd.math]の文言において同じ名前の関数をすべてまとめる
isgreater, islessequal, islessgreater, isunorderedの追加オーバーロードの戻り値型を修正- プレースホルダ関数をprvalueへの
static_castに簡略化basic_vec型はトリビアルコピー可能であるため、実装ではas-ifでコピーを省略できる
- 数学関数の引数を参照する場合は“arguments” ではなく“parameters”を使用する
などです。
この提案は当初目指していた定数整数値のブロードキャストコンストラクタの復活が別の提案に分離され、それが行われた場合に数学関数呼び出しで不可解なエラーが発生する問題の解決のみが残されています。
問題とは、simd::vecにconsteval変換/コンストラクタによって変換可能である場合に、数学関数内部で変換を行うと非constexpr/consteval関数内でconsteval関数を呼び出そうとして引数が定数式ではないことによってエラーを発生させるというものです。
例えばstd::pow(x, 3)はxの型によらずx³を計算しますが、xがsimd::vecになるとこの呼び出し形式はエラーになります。
simd::vec向けのpow()は次のように宣言されています
template<class V0, class V1> constexpr math-common-simd-t<V0, V1> pow(const V0& x, const V1& y);
これは関数引数では元の型のまま受け取って関数内部で両引数をsimd::vecへ変換し、SIMD実装によるpowを実行しようとするものです。pow(x, 3)の様な呼び出しとこの提案のR3までにあった整数定数ブロードキャストコンストラクタ(constevalコンストラクタ)によって、2つ目の引数は関数内部でsimd::vecに変換されますがそのコンストラクタがconstevalコンストラクタであることによってその引数が定数式であることが要求されます。
この変換は即時エスカレーションによってpow()の呼び出しが定数式として処理されることでエラーを回避するのですが、それによってpow(x, 3)の様な呼び出しをしているところではその外側の関数(あるいはxの経路)に対しても定数式を要求してしまい、これが意図しないエラーを引き起こします。
constexpr void f1(simd::vec<float> vec) { pow(vec, 3); // ok、即時エスカレーションによりf1()が定数式で実行される } void f2(simd::vec<float> vec) { pow(vec, 3); // ng、vecは定数式ではない }
このf1()も定数式として実行可能なように呼び出されている必要があります。
P4012R0が採択されるまではこのような変換はユーザー定義型におけるものだけですが、P4012R0のような機能が導入されればどのみち問題になります。そのため、この提案ではこの問題を数学関数の呼び出し時に呼び出し側で(引数で)変換するようにすることで回避しようとしています。
pow()の場合は、オーバーロードを3つに分けて、simd::vec以外の型が渡された場合に引数で変換を行うようにしています。
template<math-floating-point V> constexpr deduced-vec-t<V> pow(const V& x, const V& y); // 両方ともsimd::vecの場合 template<math-floating-point V> constexpr deduced-vec-t<V> pow(const deduced-vec-t<V>& x, const V& y); // xがsimd::vecではない場合 template<math-floating-point V> constexpr deduced-vec-t<V> pow(const V& x, const deduced-vec-t<V>& y); // yがsimd::vecではない場合
deduced-vec-t<V>はその引数の型が推論されるのを防止し、なおかつVの推論にも参加させず、型をconst Vに指定するものです。
これは2引数以上の関数において問題になり、3引数関数では7つのオーバーロードに分割する必要があります。
この提案は2026年3月の全体会議で承認され、C++26に適用されています。
P3856R4 New reflection metafunctions - is_structural_type (US NB comment 49)
↓
P3856R5 New reflection metafunction - is_structural_type (US NB comment 49)
ある型の値がNTTP(定数テンプレートパラメータ)として使用可能であるかを調べる関数の提案。
以前の記事を参照
- P3856R0 New Reflection metafunction -
is_destructurable_type- WG21月次提案文書を眺める(2025年07月) - P3856R3 New reflection metafunctions - is_structural_type (US NB comment 49) and
is_destructurable_type- WG21月次提案文書を眺める(2025年12月)
R3での変更は
is_destructurable_typeとその実装例を削除is_structural_typeのシグネチャからアクセスコンテキスト引数を削除is_structural_typeの型特性を追加
このリビジョンでの変更は
- 文言の改善
などです。
R3では元々提案のメインであったis_destructurable_typeが削除されました。
P3864R1 Correctly rounded floating-point maths functions
正しい丸め(correct rounding)を行う浮動小数点数演算関数の提案。
以前の記事を参照
このリビジョンでの変更は
- 著者の追加
- 浮動小数点数型名のプレースホルダが
floating-point-typeからiec-559-typeへ変更 round-nearest-to-evenをroundTiesToEvenへ変更- Error handling セクションを追加
- 提案する関数のエラー処理方法は既存の数学関数と同じ方法で扱う
- 機能テストマクロの追加
- Design considerations セクションを追加
などです。
P3874R1 Should C++ be a memory-safe language?
C++の安全性についての戦略として、構成による安全性(Safety by Construction)を採用すべきとする提案。
以前の記事を参照
このリビジョンでは、R0の公開後の議論においてWG21内でメモリ安全な言語の定義が確立されていない事が判明したことから、その定義を提供するとともにC++がメモリ安全な言語を目指すべきかどうかという問いに答えを出し、明確な目標を提案することを目指すものに方向性が変化しています。それに伴ってタイトルの変更や著者の追加が行われ、内容も多くの部分が再構成されています。
この提案ではメモリ安全な言語を
- 未定義動作がないこと
- 抽象マシンのセマンティクスに違反するあらゆるメモリ操作に対して、自動的に強制される保証が存在すること
と定義しています。
この提案ではC++がこのメモリ安全な言語となることを達成するためのアプローチとして、subset-of-superset(スーパーセットのサブセット)戦略を提案しています。この戦略は次の要件を満たすものです
- サブセットは構文的に明示的かつ明確に定義されており、この範囲では未定義動作が発生することは無く、アクセス可能なコードもこのサブセット内に限定される
- サブセット外のコードは未定義動作を示す可能性があるが、明確に定義されたサブセット内のコードに直接アクセスすることは可能
この取り組みの参考になる比較対象としてはRustとCircleが挙げられています。
この提案の方向性は、現在のプロファイルや暗黙の契約アサーションなどの方向性とは異なるものを示しており、それらの取り組みを補完するものとしつつも、有用かつ体系的に未定義動作の無いサブセットを提供するスーパーセット機能と組み合わせなければC++をメモリ安全な言語にすることはできないとしています。すなわち、C++29に向けた現在の主要な取り組みとは異なる道筋を示すものです。
P3876R1 Extending support to more character types
std::from_chars/std::to_charsでchar以外の文字型による文字列をサポートするようにする提案。
以前の記事を参照
このリビジョンでの変更は
- abstractを拡張
- libc++がASCIIのみをサポートするという誤った記述を削除
- LWG4522を提出し、[diff.cpp26.format]への変更を削除
- § [charconv.from.chars] で"code unit"の使用が正しい理由を説明
などです。
P3899R1 Clarify the behavior of floating-point overflow
浮動小数点数演算におけるオーバーフロー時の未定義動作を修正する提案。
以前の記事を参照
このリビジョンでの変更は
- §2.1.3. Undefined behavior on yielding infinity の言葉遣いを修正
- §2.3. Implementation divergence で浮動小数点数アンダーフローについて調査
- 定数式におけるアンダーフローを許容する
- §3.3. Floating-point underflow を参照
- 提案する文言において"mathematically defined in the domain of real number arithmetic"という表現を使用
などです。
P3904R1 When paths go WTF: making formatting lossless
std::filesystem::pathのフォーマットにおいて情報の損失が発生しないようにする提案。
以前の記事を参照
このリビジョンでの変更は
- "Implementation experience"セクションを追加
などです。
P3932R0 Resolve LWG4470: Fix integer-from in [simd]
std::simdのinteger-fromの動作を修正する提案。
この提案は、integer-from<Bytes>がstd::complex<double>の様な型に対して正しく動作しないことを指摘するイシューに端を発する3つの関連する問題の解決を図るものです。
integer-fromは次のような説明専用のエイリアステンプレートで、指定されたBytesに等しいサイズの符号付整数型を返すものです。
template<size_t Bytes> using integer-from = see below;
詳細は不明ですがこれはBytes == 16の時に正しく動作しないようで(16バイトの整数型の提供が必須ではないため?)、std::complex<double>の様な16バイトサイズになりそうな型について仕様箇所で問題が起こりうる様です。
その後、この変更によってstd::simd::catで意図しないABI変更が生じることが発見され、それがLWG Issue 4518として報告されました。
simd::catは複数のsimd::vecを連結して一つのsimd::vecにする関数です。
template<class T, class... Abis> constexpr vec<T, (basic_vec<T, Abis>::size() + ...)> cat(const basic_vec<T, Abis>&... xs) noexcept; template<size_t Bytes, class... Abis> constexpr basic_mask<Bytes, deduce-abi-t<integer-from<Bytes>, (basic_mask<Bytes, Abis>::size() + ...)>> cat(const basic_mask<Bytes, Abis>&... xs) noexcept;
2つのオーバーロードの違いはsimd::vecに対するものかsimdマスク型に対するものかの違いですが、マスク型に対するオーバーロードの方がinteger-fromを使用していることによって戻り値型の変更が起こりえます。
simd::basic_vec<T, Abi> x = ...; auto [...vs] = simd::chunk<2>(x); // 2要素ごとのvecに分割 auto y = simd::cat(vs...); // 再び結合 static_assert(is_same_v<decltype(x), decltype(y)>); // 型が合わない可能性がある
これは、simd::catが複数のsimd::vecを取る際にABIタグ型の一致を要求していないためで、混在した入力が与えられた際に戻り値のABIタグを何にすべきかの正解が無いことに起因しています。
この提案ではこれらの問題を解決するために、まずinteger-fromの代わりにマスクの要素数を取得するエイリアステンプレートを追加し、integer-fromの使用箇所のほとんど(結果的にほぼresize_t周りのみの変更)をこれとすでにあるsimd::vecの要素数を取得するもの(simd-size-v)とマスクの要素サイズを取得するもの(mask-element-size)で置き換えます。これにより、要素型のサイズと一致する整数型を介してABIタグ型を導出する代わりに、入力のsimd::vec、マスク型要素数に応じたサイズによってABIタグ型を導出するようにします。
次に、simd::catの戻り値型ではresize_tを使用したうえで引数の先頭のもののABIタグ型を使用するようにしています。
template<class T, class... Abis> constexpr resize_t<(basic_vec<T, Abis>::size() + ...), basic_vec<T, Abis...[0]>> cat(const basic_vec<T, Abis>&... xs) noexcept; template<size_t Bytes, class... Abis> constexpr resize_t<(basic_mask<Bytes, Abis>::size() + ...), basic_mask<Bytes, Abis...[0]>> cat(const basic_mask<Bytes, Abis>&... xs) noexcept;
resize_t<N, V>はサイズNのVに基づくsimd::vec/マスク型を返すもので、その際にNとVに基づくABIタグ型の導出・変換を行うものです。Abis...[0]はパラメータパックに対するインデックス指定であり、resize_t<N, V>のVに対して引数の最初のABIタグ型を使用するようにしています。
LWG Issue 4414は4470が変更する箇所と同じ個所を変更しているため(かつ報告者が同じなため)、この提案に組み込まれています。
LWG Issue 4414では、simd::resize(resize_t)やsimd::bindで使用されていたdeduce-abi-tの動作がサポート外の入力に対して規定されていない事でsimd::resizeやsimd::bindの動作も不定になっていたことや、simd::resize/simd::bindにおけるdeduce-abi-tの使用方法がその定義と一貫していなかった事が問題として報告されています。
この提案では、deduce-abi-tの規定を明文化してサポート外の入力に対しては未規定の型を示すとしたうえで、その使用箇所における使用方法を変更後の定義と整合させています。
まとめると、この提案では
- LWG4470:
integer-fromの定義の不備 - LWG4518:
simd::catの戻り値型のABIタグの意図しない変更が発生しうる - LWG4414:
deduce-abi-tの定義の不備と使用方法の不一致
の3つの問題を協調的に修正しています。
この提案は2026年03月の全体会議で承認され、C++26に採択されています。
- LWG Issue 4414. §[simd.expos.abi] deduce-abi-t is underspecified and incorrectly referenced from rebind and resize
- LWG Issue 4470. The use of
integer-from<Bytes>all over [simd] is incorrect forBytes=sizeof(complex<double>) - LWG Issue 4518. simd::cat return type requires inefficient ABI tag change/conversion
- P3932 進行状況
P3936R1 Safer atomic_ref::address (FR-030-310)
std::atomic_ref::address()の戻り値型をvoid*にする提案。
以前の記事を参照
このリビジョンでの変更は
- 文言の修正
address_return_tを説明専用に変更
などです。
この提案は2026年3月の全体会議で承認され、C++26に適用されています。
P3938R1 Values of floating-point types
浮動小数点数型の表現可能な値のモデルについて、もう少し明確にする提案。
以前の記事を参照
このリビジョンでの変更は
- §3.5. Is negative zero negative? What about infinity and NaN? の現状を合理化
- 文言の修正
- 負のゼロの浮動小数点数リテラルの不適切な扱いを修正
- CWG3129の参照を追加
- ISO/IEC 60559 に準拠する定義を緩和してsignaling NaNの処理を改善
などです。
P3941R2 Scheduler Affinity
execution::affine_onアルゴリズムの改善提案。
以前の記事を参照
- P3941R0 Scheduler Affinity - WG21月次提案文書を眺める(2025年12月)
- P3941R1 Scheduler Affinity - WG21月次提案文書を眺める(2026年01月)
このリビジョンでの変更は
get_scheduler/get_start_schedulerに要件を追加affine_on::transform_senderの表記の誤字修正
などです。
P3953R1 Rename std::runtime_format
std::runtime_format()をstd::dynamic_format()にリネームする提案。
以前の記事を参照
このリビジョンでの変更は
- サンプルコードの修正
などです。
P3966R0 2026-01 Library Evolution Poll Outcomes
2026年1月に行われたLEWGにおける投票の結果。
次の提案が投票にかけられLWGに転送されています
- P3505R2 Fix the default floating-point representation in
std::format - P3826R3 Fix or Remove Sender Algorithm Customization
- P3450R0 Extend
std::is_within_lifetime
投票の際に寄せられたコメントが記載されています。
P3739R2も予定されていたはずで、特に言及がありませんが、キャンセルされたようです。
P3969R0 Fixing std::bit_cast of types with padding bits
パディングビットを含む型からのstd::bit_cast時の動作を修正する提案。
std::bit_cast<To>(from)において、変換元fromの型にパディングビット(あるいはその値表現に寄与しないビット)がある場合、この変換は未定義動作となります。
例えばx86環境でlong doubleを128ビット整数型にキャストする次のようなコードはコンパイラによって動作が異なります
constexpr auto x = std::bit_cast<__int128>(0.0L); // GCC accepts (x = 0), Clang rejects
80ビットのx87long double型には48ビット分のパディングビットがあるため、このキャストは未定義動作です。
std::bit_cast自体は実行時にキャストを行うものでもあるものの、このことは本来コンパイル時にその型(ToとFrom)から静的に検査できるものです。現在のところこれは要求されておらず仕様の空白(未定義)となっているため、コンパイラは必ずしもこれを警告しません。
また、このことは_BitInt型(P3666で提案中)のキャストにおいて頻発することが予想されます。
この提案はこの問題を解決するために、2つのアプローチを提示しています。
std::bit_castのこのような使用(変換元がパディングビットを含み、そのビットが宛先で値表現に含まれるような変換)をill-formedとする- パディングビットを不定値ではなく0として扱ってキャストする
std::bit_cast_zero_paddingを追加する- これはパディングビットの扱い以外は
std::bit_castと同じ動作をする
- これはパディングビットの扱い以外は
- パディングビットを不定値ではなく0として扱ってキャストする
std::bit_castをパディングビットに対しても動作するように修正するstd::bit_cast_zero_paddingと同じ動作になるようにする- C++20へのDRとする
2のアプローチは既存のコードがそのまま正常化されUBが無くなる点が利点です。しかし、逆に既存のコードにおいてstd::bit_castに動作が変わってしまいます。
パディングビットを検出してそれを0クリアするという処理には単なるビットコピーよりもコストがかかります。さらに、この動作がコンパイラバージョンによって異なることは、あるバージョンのコンパイラのもとで記述されたコードがより古いバージョンのコンパイラだと動作が異なり、未定義動作となってしまうことにもなります。
そして、std::bit_castがパディングビットをクリアするという動作はその名前からは予測できないものであり、ユーザーに驚きをもたらす可能性があります。おそらくstd::bit_castの動作イメージはビットを保ったままそれを解釈する型だけを変える、というものになると思いますが、パディングビットのクリアはこのようなイメージからは外れるものです。これはまた、ユーザーが誤った仮定によってパディングビットのキャストを意図せず行っている場合のミスを隠蔽する結果にも繋がります。
この提案はまだどちらのアプローチを選択するか決定してはいませんが、EWGのレビューでは1のアプローチは好まれなかったようです。
P3970R0 Profiles and Safety: a call to action
C++安全性の向上のためのプロファイルフレームワークのクロスプラットフォームな導入に向けての行動喚起提案。
これは、C++29以降の導入を目指してホワイトペーパーにおいて作業されているプロファイル機能について、特に実装者に向けた具体的な行動喚起を行う提案文書です。
背景には安全性向上を謳う提案が増加しているもののプロファイル機能程洗練されておらず、にもかかわらずプロファイル機能に対する言及等がないものが増加しており、プロファイル機能に対する認知度が低下しているように見えることがあるようです。
そのため、プロファイル機能の普及のためにすべての主要な実装でポータブルに利用できることが重要であるとして、主に実装者に向けて連携して作業するように促しています。
また肝心のプロファイルそのものについての初期セットを提案しています。
- Initialization
- すべてのオブジェクトが初期化前に使用されることを禁止する
- Ranges
- 配列やコンテナ等範囲の危険な使用をコンパイル時に禁止し、実行時に検出する
- Resources
- すべてのリソースはRAIIによって管理されることを強制する
- Education
- 初心者が危険なコードを書かないようにより制限されたバージョンのC++を適用する
- Invalidation
- ダングリングポインタによるアクセスを禁止する
- Arithmetic
- 暗黙的な縮小変換を禁止
- オーバーフローやアンダーフローも禁止
これらのプロファイル初期セットはまだリストアップされたのみで具体的な仕様を伴っているわけではありませんが、より高度な静的解析を必要とせずに比較的簡単に実装可能なものと目されています。
P3971R0 Generalised type rebinding for structures of uniform elements
コンテナ型等において、要素型の変換のみを行うユーティリティの提案。
コンテナ型やそれと似た構造をもつ型(要素型Tに対してC<T>の様に使用する型C)において、その要素型だけを交換した新しい型を取得したい場合、あるいはその新しい型に元の型を変換したい場合というのがたまにあります。この時、コンテナごとにその最適な方法が異なります。
例えばstd::vector<float>をstd::vector<double>に変換したい場合は次のように書くことができる一方で
std::vector<double> widen(const std::vector<float>& data) { return std::vector<double>(data.begin(), data.end()); }
std::arrayだと少し面倒になります
template<std::size_t N> std::array<double, N> widen(const std::array<float, N>& data) { std::array<double, N> result; std::copy(data.begin(), data.end(), result.begin()); return result; }
コンテナ型だけではなく、std::complexのような型でも同様の変換が欲しい場合があります
std::complex<double> widen(const std::complex<float>& data) { return std::complex<double>(data); }
このような変換には変換前後の型の知識が必要になるため、単一の汎用的な関数を用いて記述することができません。
template<typename Container> auto widen_to_double(const Container& data) { using T = typename Container::value_type; // コンテナ型ではこのメンバが存在しない場合がある // Container<double> から Container<float> を作成するにはどうすればいいか? // - vector: range constructor // - array: manual copy with known size // - complex: direct construction // - user types: ??? // 統一的な解決策が存在しない }
C++26のstd::simd::vec型においてもこのことが問題となり、より頻繁に使用されることが予想されていたため、このような変換のためにstd::simd::rebind_tが用意されています。これは、std::simd::vec<T, ABI>をstd::simd::vec<U, ABI>に変換するためのもので、今のところstd::simdのもの(vecとマスク)でしか使用できません。
この提案はsimd::rebind_tをベースにした汎用的な再バインド用ユーティリティを提案するものです。
この提案の核となるのは次の2つの機能です
rebind_t<U, T>objの型の要素型を型Tから型Uに変換した型を求める
std::rebind<U>(obj): 型ではなく実際の変換を行うカスタマイゼーションポイントオブジェクト
rebind_tの宣言例
namespace std { template<typename U, typename T> using rebind_t = decltype(rebind<U>(std::declval<T>())); }
std::rebindはCPOであり、引数型に対してADLによってrebind()を探索して呼び出します。std::rebindに対して呼び出し可能なように定義しておけばユーザー定義型でも両方が使用可能になります。
また、標準ライブラリの一部の型についてデフォルトでstd::rebindを使用可能にしておくことも提案しています。サポートされるのはシーケンスコンテナ型とstd::complexです。
| ライブラリ型 | ::value_typeを持つか |
rebind_t<U, C> |
|---|---|---|
std::array<T, N> |
Yes | std::array<U, N> |
std::vector<T, A> |
Yes | std::vector<U, rebind_alloc_t<A, U>> |
std::deque<T, A> |
Yes | std::deque<U, rebind_alloc_t<A, U>> |
std::list<T, A> |
Yes | std::list<U, rebind_alloc_t<A, U>> |
std::forward_list<T, A> |
Yes | std::forward_list<U, rebind_alloc_t<A, U>> |
std::complex<T> |
No (special case) | std::complex<U> |
アロケータ対応コンテナ型の場合、そのアロケータ型もrebind_allocを用いて再バインドされます。
これを用いると、先ほどの要素型をdoubleに変換するwiden_to_double()は次のように書くことができます
template<typename Container> auto widen_to_double(const Container& data) { return std::rebind<double>(data); } // Works uniformly for all supported types: std::vector<float> v = {1.0f, 2.0f, 3.0f}; auto vd = widen_to_double(v); // vector<double> std::array<float, 3> a = {1.0f, 2.0f, 3.0f}; auto ad = widen_to_double(a); // array<double, 3>
なお、次のライブラリ型は意図的に除外されています
- 連想コンテナ
- 比較型も再バインドが必要になるものの、一般的な解決策が無いため
pair/tuple- 同じ要素型がある場合にどの要素を再バインドすればいいか不明なため
- コンテナアダプタ
- 内部コンテナも再バインドしなければならないが、実装上の課題がある
- 内部コンテナを指定するテンプレートパラメータも再バインド対象
priority_queueには連想コンテナと同じ問題がある
- 内部コンテナも再バインドしなければならないが、実装上の課題がある
chrono::durationや単位に基づく型- 表現型と単位のどちらを再バインドすべきかあいまいなため
これらのライブラリ型でも、実装経験や明確な根拠に基づいて将来的にサポートすることは可能です。
この提案の機能はどうやら、以前にP3445R0で提案されていたsimd_castをより汎用化したものです。
P3973R0 bit_cast_as: Element type reinterpretation for std::simd
ベクトルデータの一定粒度での再解釈キャストを行うbit_cast_asの提案。
SIMDプログラミングでは、SIMDレジスタに格納されたベクトルデータを格納時点とは異なる要素単位で再解釈する必要が生じることが良くあります。例えば、バイト配列をshort型ベクトルとして再解釈したり、浮動小数点数ベクトルをバイト配列として再解釈してバイト表現にアクセスするなどです。
SIMD intrinsicではこのような操作は自然にサポートされており、std::simdでも一応サポートされています。ただし、そのためにはstd::bit_castを使用する必要があり、キャスト先の型(特に要素数)は手動で求める必要があります。
この提案は、このような再解釈キャストにおいてキャスト先の型を自動で求めることのできるユーティリティとしてbit_cast_as()を提案するものです。
このbit_cast_as()は以前にP3445R0で提案されていたsimd_bit_castをより汎用化したものでもあります。以前の記事も参照してください。
この提案のbit_cast_as()は、現在の次のような再解釈キャストコードを
// Verbose: must specify element count and worry about ABI selection auto shorts = std::bit_cast<vec<uint16_t, 8>>(bytes);
次のように書くことができるようにするものです
// Clear: element count inferred automatically auto shorts = std::bit_cast_as<uint16_t>(bytes);
これはどちらも同じキャストを行っており、std::simd::vecオブジェクト(おそらく1バイト単位の16要素)bytesを再解釈してstd::uint16_t要素ベクトルとして読み出すものです。
std::bit_castによるコードは宛先の型を求めて記述する必要があり、特に要素数を手動で計算する必要があります。それに対してbit_cast_as<T>()は、要素の変換先の型だけを指定して、その型と入力の型から変換先の型と要素数を自動計算するものです。
bit_cast_as<T>()によるキャストでは、全体の長さが変換前後で変化しないことが要求されます。これが満たされない場合、bit_cast_as<T>()はコンパイルエラーとしてそれを報告します。
vec<uint8_t, 15> vec; auto bad = bit_cast_as<uint32_t>(vec); // Error: 15 bytes != N * 4 bytes
bit_cast_as<T>()はTとして単純な数値型だけではなく、条件を満たしたクラス型もサポートすることを提案しています。条件とは、array-likeレイアウトと呼ばれるレイアウトであることで、これは配列のように要素が連続していてパディングが無いことを求めています。これに従わない場合は未定義動作(か実装定義動作)になり、ここの部分についてはP3983R0(下の方)で作業されているようです。
宣言と実装の例
namespace std::simd { template<typename T, typename U, typename Abi> auto bit_cast_as(const basic_vec<U, Abi>& v) noexcept { constexpr size_t old_count = simd<U, Abi>::size(); constexpr size_t new_count = old_count * sizeof(U) / sizeof(T); // Step 1: Rebind to new element type using new_type = rebind_t<T, basic_vec<U, Abi>>; // Step 2: Resize to new element count using new_vec = resize_t<new_count, new_type>; return new_vec{/* bit reinterpretation */}; } }
std::simd::rebind_tとstd::simd::resize_tはstd::simdのためのリバインド(要素型の変更)とリサイズ(要素数の変更)を行った型を求めるためのユーティリティです。
この提案ではさらに、ベクトル型をsimd::vecに限定せず、std::arrayやstd::span等のメモリ連続性のある配列にまで広げようとしています。
std::array<uint8_t, 16> bytes = /*...*/; auto shorts = std::bit_cast_as<uint16_t>(bytes); // Returns array<uint16_t, 8> std::span<int, 8> ints = /*...*/; auto bytes = std::bit_cast_as<std::byte>(ints); // Returns span<byte, 32> (view)
P3977R0 A New Taxonomy for Contracts
C++における関数契約の分類を拡張する提案。
これはContractsに関連した話ではあるもののContracts機能に関する提案ではありません。
現在のC++には2つの関数契約の分類があります
- 広い契約(Wide contracts)
- 事前条件を持たない
- 未定義動作を指定しない
- 少なくとも呼び出し元の責任で未定義動作が発生することは無い
- 狭い契約(Narrow contracts)
- 広い契約ではない契約。事前条件を持つ
- 事前条件に違反すると未定義動作となる
この分類は長らく(少なくともC++11から現在まで)非常に有用なものとして扱われてきており、標準ライブラリにおけるnoexcept指定の有無もこれに倣った基準で行われています。
しかし、この分類が直観に反した結果になる場合や適用できない場合があります。
例えば、負/NaNの入力が与えられた場合にプログラムを終了するfloat sqrt(float)関数があるとします。この実装ではこの関数の保証として、負/NaNの入力に対してプログラムを終了することが保証されており、未定義動作は指定されていません。そのため、この関数には事前条件がありません。したがって、この関数は広い契約を持ちます。
しかし、このことはおそらく人によっては意外な結果であり、広い契約ではないと考えるでしょう。このように、特殊な状況での終了を規定する広い契約と符号なし整数の加算のような予期しない事態の発生しない広い契約の間には質的な違いがあります。
このことは、C++26 Contractsに関連して提案されている無視できない契約アサーションのセマンティクスによっても問題となります。負の入力が与えられた場合にプログラムを終了するsqrt関数があるとき、次の関数は広い契約を持っているといえるでしょうか?
float sqrt(float x) pre <quick_enforce> (x >= 0);
(構文はP3400のラベル機能のもの)
必ずチェックされ違反していたら終了する事前条件があるため、この関数には未定義動作がありません。そのため、その観点からは広い契約を持っていると言えます。しかし一方で、この関数は事前条件を持っており、その観点からは狭い契約を持っています(が、事前条件違反は未定義動作を意味していません)。
また、次のような関数は広い契約と狭い契約のどちらを持つでしょうか?
void abort() pre <quick_enforce> (false);
この関数は常に満たされないという意味で最も狭い契約を持っているはずです。しかし同時に、事前条件が確実に満たされることでプログラムを終了するという(関数名から読み取れる)望ましい動作が未定義動作を起こさずに達成されることになります。ではこの関数は広い契約を持つのでしょうか?
先に示した2つの分類ではこれらのような場合に当てはまる適切な表現が無いため、現在の分類は不完全であると言えます。
この提案は現在の2分類をさらに細分化する形で関数契約の分類を定義しなおそうとするものです。
この提案ではまず、関数の動作を主要な動作(Primary Behaviour)と二次的動作(Secondary Behaviour)に分けて考えます。主要な動作が関数の本来行うべき動作であるとすると、二次的動作は主要な動作を引き起こさない方法で呼ばれた場合の動作です。これは例えば、float sqrt(float)に対して負の入力を与えた場合の動作の事です。
関数の事前条件がすべて満たされていることは、その関数の主要な動作を得るために必要であっても十分ではありません。同様に、少なくとも一つの事前条件が満たされないことは関数の二次的動作を得るために十分であっても必要ではありません。負の入力に対してNaNを返す保証のあるsqrt()関数においては、負の入力を与えても事前条件違反にはならないものの、その動作は二次的動作です。
この二次的動作が次の事を保証する契約のことを誠実(faithful)な契約とこの提案では定義します
- 呼び出し元に制御を戻す
- 主要な動作とは異なる動作をする
誠実な契約の定義は二次的動作によってのみ定義され、主要な動作は無関係です(例えば主要な動作が呼び出し元に制御を返さなくても構わない)。
誠実性の判定例
- 負の入力に対してプログラムを終了する
float sqrt(float)は呼び出し元に制御を戻さない二次的動作を持つため、誠実ではない - 事前条件付きの
float sqrt(float)は呼び出し元に制御を戻さない二次的動作を持つため、誠実ではない void abort()の通常のもの(名前の通りの動作をするもの)は、二次的動作を持たないため、誠実な契約を持つpre <quick_enforce> (false)を持つvoid abort()は、主要な動作が存在せず二次的動作が呼び出し元に制御を戻さないため、誠実ではない
この誠実性はこの提案の最終的なカテゴリではなく、そのうちの2つをまとめるためのカテゴリになります。
この提案においては、広い契約は4つのサブカテゴリに分類され、広い契約とは別に2つの契約分類は追加されます。
- 広い契約
- 誠実な契約
- 自由な(Free)契約
- 明確な(disambiguating)契約
- 損失のある(Lossy)契約
- 切断されている(Disconnecting)契約
- 誠実な契約
- 狭い契約
- 病的な(pathological)契約
- 無条件に堅牢化された(Unconditionally hardened)契約
追加されるのは次の6種類です
- 広い契約
- 自由な(Free)契約
- 二次的動作がない
- 明確な(disambiguating)契約
- 誠実な契約かつ、二次的動作が主要な動作と重ならない
- 呼び出し元は呼び出し結果からその動作が主要な動作か二次的動作かを区別できる
- 損失のある(Lossy)契約
- 誠実な契約かつ、二次的動作が主要な動作と重なる
- 呼び出し元は呼び出し結果からその動作が主要な動作か二次的動作かを区別できない
- 切断されている(Disconnecting)契約
- 二次的動作が呼び出し元に制御を返さない
- プログラム終了や無限ループなど
- 二次的動作が呼び出し元に制御を返さない
- 自由な(Free)契約
- 病的な(pathological)契約
- 無条件に堅牢化された事前条件の違反によって、主要な動作(の一部)が得られる
- 無条件に堅牢化された(Unconditionally hardened)契約
- 病的な契約ではない
- 事前条件を持ち、全ての事前条件が無条件に堅牢化された事前条件になっている
無条件に堅牢化された事前条件とは、無視できない事前条件の事です。この事前条件は必ず評価され、破られるとプログラムを終了させることが保証されています。広い契約に属さない新しい2つの契約はこれによって(狭い契約とは別に)定義されます。
標準のテキスト等によって指定された平易な言語による契約がどのカテゴリに分類されるかはそれが提供する保証によって決定されます。厳密に保証されていないことはより広いカテゴリへ分類するために使用できません。例えば、vector::operator[]の場合、特定の実装が境界チェックを行うとしても狭い契約のままであり広い契約を持つことはありません(仕様がそれを関数の主要な動作に含めておらず、事前条件違反が起きた場合の動作を規定しないため)。
提案ではこれらのカテゴリや用語などについてもう少し厳密な定義を行ったうえでそれらを導いています。
また、この提案はContracts関連の議論における言葉の定義を提供しようとするもので、標準規格にこれらの定義を導入しようとするものではありません(なので正確には提案ではない)。
この文書はおおむねP2900のContractsに擁護的な立場を取っています。そのうえで、文書では無視できない契約アサーション(無条件に堅牢化された事前条件)がABIの一部となり、その変更がABI破損に繋がることを指摘しています。
例えば、はじめライブラリヘッダにおいて次のように宣言された関数があるとします
// Precondition: x >= 0 void foo(int x);
この定義はソースファイルで分離されています。このコメントが仕様であるとして、この関数は狭い契約を持ちます。
狭い契約においては実装者は違反時の動作を自由に選択できるため、次のように書いても良いかもしれません
void foo(int x) pre <quick_enforce> (x >= 0);
しかし、これは無視できないアサーションであるため、事前条件違反時の動作がABIと一体化しています。これを変更することは(利用者にとって)ABI違反となります。
この事前条件が無視できないことはコンパイラが分かっているとすると、例えば次のような呼び出し側でのチェックは冗長であることが分かるため、最適化によって削除される可能性があります
foo(y); if(y >= 0) { ... }
この時にfooの事前条件が削除され、ライブラリだけが差し替えられたとするとfoo()の呼び出し後に未定義動作に陥る可能性があります。このことは、事前条件違反時の実装の自由度とは根本的に異なる問題です。この場合実装上の決定が呼び出し側に見えており、ソース内で閉じていないためです。
一方、P2900の契約アサーションはこの問題を考慮して設計されており、実装者がABIの破損を恐れることなく導入することができます。
void foo(int x) pre (x >= 0);
このアサーションはignoreセマンティクスを持つ可能性があるため、コンパイラも利用者もこの条件を仮定できないため、ABIの一部になりません。
このことから、契約違反時の動作の実装の自由には重要な制約があることが分かります。すなわち、狭い契約を持つ関数では、実装者は契約に反する動作の扱いを無条件に堅牢化された事前条件に昇格することはできません。言い換えると、無条件に堅牢化された事前条件のみを持つ関数は狭い契約を持たず、事前条件を持つため広い契約も持ちません。
このことは、より包括的な分類体系の必要性を示唆するものであり、狭い契約と無条件に堅牢化された契約が分かれている事の理由でもあります。
P3978R0 constant_wrapper should unwrap on call and subscript
↓
P3978R1 constant_wrapper should unwrap on call and subscript
↓
P3978R2 constant_wrapper should unwrap on call and subscript
std::constant_wrapperの関数呼び出し演算子と添字演算子に対する振る舞いを修正する提案。
std::constant_wrapper<V>はNTTP値に対してほぼすべての演算子オーバーロードを提供することでNTTP値をオブジェクトを介して透過的に扱うことができます。std::constant_wrapperで定義されている演算子オーバーロードはすべて、引数としてstd::constant_wrapper自体を受け取り、それらの中身を取り出して対応する演算子を呼び出し、その結果を再びstd::constant_wrapperに包んで返す、という動作をします。
すなわち、std::constant_wrapper<V>の演算子オーバーロードはstd::constant_wrapper同士の間で作用するものです。しかし、実際には演算子引数としてstd::constant_wrapperにラップしていない型の値を渡しても動作します。これは、std::constant_wrapperの変換演算子とADLによってラップ元の型について演算子定義が発見されるためです。std::constant_wrapper<V>は実際にはstd::constant_wrapper<V, decltype(V)>のようにテンプレートパラメータを保持しており、2番目のテンプレートパラメータ経由でラップ元の型についての関連名前空間(ADL探索対象)がstd::constant_wrapper<V>の関連名前空間に追加されるため、このような動作が実現されています。
これにより、std::constant_wrapper<V>はその演算について可能な限りstd::constant_wrapperの型空間内に留まり続け(ラップし続け)ますが、それが叶わない場合でもラップしている型空間内で演算を行えるようになっています(この場合結果はアンラップされる)。前者の演算は定数式が必須である一方、後者の演算は実行時に行うこともできます。
using std::cw; constexpr int iota[4] = {0, 1, 2, 3}; constexpr int fun(int x, int y) { return x + y; } constexpr int unary() { return 1; } cw<1> + 1; // → 2 : the thing it holds cw<1> + cw<1>; // → cw<2> : can stay wrapped cw<iota>[1]; // → 1 : the thing it holds cw<iota>[cw<1>]; // → cw<1> : can stay wrapped cw<fun>(1, 2); // → fun(1, 2) : the thing it holds cw<fun>(cw<1>, cw<2>); // → cw<fun(1, 2)> : can stay wrapped∗ cw<fun>(cw<1>, 2); // → fun(cw<1>, 2) : the thing it holds cw<unary>(); // → cw<unary()> : can stay wrapped∗
*で示されているところはその呼び出しが定数式でなければアンラップされます。
型Tの値Vに対するstd::constant_wrapper<V>オブジェクトとTの値の間の演算はすべてADL経由で演算子がルックアップされるのですが、コア言語の規定において[]と()だけ扱いが異なっていることによって、ADL経由でのルックアップができません。
inline constexpr int iota[4] = {0, 1, 2, 3}; auto test1() { auto x = std::cw<iota>; return x[1]; // #1 OK } auto test2() { auto x = std::cw<std::array<int, 4> {0, 1, 2, 3}>; return x[1]; // #2 ill-formed }
int fun(int x) { return x + 1; } auto test1() { auto x = std::cw<fun>; return x(1); // #4 OK } auto test2() { auto x = std::cw<[](int x) { return x + 1; }>; return x(1); // #5 ill-formed }
ADL経由でのルックアップ時に、クラス型でオーバーロードされた演算子は基本的にhidden friendなものかクラス外で定義されているものしか考慮されません。そのため、単にメンバ関数としてオーバーロードされている演算子はその引数がすべてstd::constant_wrapperで包まれていないと(そのうえで定数式で呼び出し可能でないと)呼び出し不可能になります。
struct X1 { friend int operator+(X1, int a) { return a + 1; } }; struct X2 { int operator+(int a) { return a + 1; } }; struct X3 { constexpr int operator+(int a) const & { return a + 1; } }; auto test3() { { auto x = std::cw<X1{}>; int r1 = x + 1; // OK // r1: int(2) } { auto x = std::cw<X2{}>; int r2 = x + 1; // ill-formed } { auto x = std::cw<X3{}>; int r3 = x + std::cw<1>; // OK // r3: std::constant_wrapper<2, int> } }
r2のようにADL経由でしか演算子オーバーロードがルックアップされない問題は[]や()だけでなく他の二項演算子でも同様ではあるのですが、それらの違いとして[]や()は非メンバとして定義できない点があります。そのため、通常通りに使用しているとこの問題を回避できません。
この提案は[]と()に対してのみこの問題を解決しようとするものです。
全ての演算子に対して問題を解決しないのはこの問題がコア言語の非一貫性から来ているためで、[]と()は非メンバとして定義できないことでその非一貫性の影響をもろに受けてしまうことにあります。理想的にはすべての演算子の動作を一貫させるように修正することが望ましいですが、それを行うとコア言語もしくはライブラリに対する多大な(かつ非互換を伴いうる)作業が必要になり、std::constant_wrapperのこの問題の修正としてC++26に間に合いません。そのため、この提案では[]と()に対してのみこの問題を回避できるように修正しています。
この提案では次の4点を提案しています
std::constant_wrapperの[]と()では、ラップ対象の型の演算子を直接呼び出すようにするstd::constant_wrapperの[]と()をstaticメンバとして定義するstd::constant_wrapperの[]と()においてINVOKEを一貫して使用するようにするstd::constant_wrapperを<utility>に移動する
2の変更は1によってstd::constant_wrapperの[]と()が非std::constant_wrapper引数について実行時に呼び出される可能性が出てくるため、3の変更はstd::reference_wrapperの動作と一貫させるため、4の変更はstd::constant_wrapperが型特性ではないことなどからふさわしくないためです。
1の変更については、まず既存の宣言が削除されてより汎用的なシグネチャのものに置き換えられます
namespace std { template<auto X, class adl-type> struct constant_wrapper { ... // 元の定義(この提案で削除 template<constexpr-param T, constexpr-param... Args> constexpr auto operator()(this T, Args...) noexcept requires requires { constant_wrapper<T::value(Args::value...)>(); } { return constant_wrapper<T::value(Args::value...)>{}; } template<constexpr-param T, constexpr-param... Args> constexpr auto operator[](this T, Args...) noexcept-> constant_wrapper<(T::value[Args::value...])> { return {}; } // 新規宣言 template<class... Args> static constexpr decltype(auto) operator()(Args&&... args) noexcept(see below); template<class... Args> static constexpr decltype(auto) operator[](Args&&... args) noexcept(see below); }; }
そのうえで、呼び出し方法は次のように決定されます
- すべての
std::remove_reference_t<Args>...がconstexpr-param(定数式で利用可能)でありかつstd::constant_wrapper<INVOKE(value, std::remove_reference_t<Args>::value...)>が有効である場合constant_wrapper<INVOKE(value, remove_reference_t<Args>::value...)>{}を返し
- それ以外の場合
INVOKE(value, std::forward<Args>(args)...)
これは()についてですが、[]に置き換えても同様です。
1の呼び出しは必ず定数式で行われ、結果は再びstd::constant_wrapperでラップされます。この時、Argsはすべてstd::constant_wrapperである必要があります。
2の呼び出しは実行時に行われる可能性があり、結果はXの型で定義されている演算子が返す型になります。この時、Argsで定義されているstd::constant_wrapperはそのまま渡され、std::constant_wrapperを受け取れない場合は暗黙変換演算子によって変換されることになります。
この定義では、std::constant_wrapperのもつ可能な限りstd::constant_wrapperの型空間でラップしたまま扱うという性質を維持しつつ、それができない場合でもラップ対象の型の演算子を直接呼び出すという動作を取ることができます。このことは、この提案以前のstd::constant_wrapperにおいて他の二項演算子の動作と一貫します(前述のように、演算子がhidden friendではないメンバ関数として定義されている場合でも呼び出し可能になる点を除きます)。
P3980R0 Task's Allocator Use
execution::taskのアロケータ取り扱い方法を改善する提案。
execution::taskにおけるアロケータには、コルーチンにおける利用とsender/receiverのチェーンにおける利用の2つの側面があります。前者はコルーチンフレームのメモリ割り当てを制御するためのものであり、後者はsenderチェーンの環境を通じて子senderにおけるメモリ割り当て戦略を制御するためのものです。
この提案は、execution::taskのアロケータ関連のNBコメントを解決するための修正を提案するものです。
この提案では大きく次の2点の変更を提案しています
- コルーチンフレーム確保用と、子
senderに提供される環境用のアロケータを分離するtaskの環境から子senderに転送されるアロケータは、taskに接続されたreceiverの環境から取得したものであるべきtaskのコンストラクタで受け取ったアロケータはコルーチンフレームの確保に使用する- どちらのアロケータもプロミス型に保存する必要はないため、保存しないようにする
- アロケータ引数の位置の変更
std::allocator_argに続いてアロケータを指定することでアロケータを受け取るインターフェースは、関数の最初で受け取るか任意の位置で受け取れるようにするかの自由度があるtaskのアロケータ(コルーチンフレームの確保用アロケータ)は、taskコルーチン(のランプ関数)の最初の引数で受け渡すようにする
この提案により、execution::taskコルーチンではアロケータのカスタマイズ有無について次のように引数宣言を行う必要があります
// アロケータカスタイマイズをサポートしない task<> none(int x) { ... } // アロケータカスタイマイズをサポートする task<> comes_first(allocator_arg_t, auto a, int x) { ... } // アロケータのカスタマイズはオプション task<> optional(allocator_arg_t, auto, int x) { ... } task<> optional(int x) { return optional(allocator_arg, allocator<char>(), x); }
P3981R0 Better return types in std::inplace_vector and std::exception_ptr_cast
↓
P3981R1 Better return types in std::inplace_vector and std::exception_ptr_cast
C++26で追加されたポインタを返す関数などについて、より良い代替としてstd::optional<T&>を追加する提案。
std::inplace_vectorはキャパシティが静的に指定されているためそのキャパシティ内で挿入を行うことが重要であり、これを簡易化するインターフェースとしてtry_~というインターフェースが用意されています。これらは、要素を後端に追加しようとするものの、キャパシティに余裕が無く追加できない場合にnullptrを返す(成功したら追加した要素へのポインタを返す)関数です。
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);
このような戻り値のために使用できる型としてはC++26でstd::optional<T&>が追加されています。std::inplace_vectorはstd::optional<T&>とほぼ同時期に並行して作業されていたため、その採用はほぼ検討されていませんでした。そのため、NBコメントでこれらの関数の戻り値型をstd::optional<T&>にすることが指摘されています。
それらのNBコメントも受けて、この提案ではこれらの関数の戻り値型をstd::optional<T&>に変更することを提案しています。
利点として次の事を挙げています
.push_back()はT&を返すため、.try_push_back()がstd::optional<T&>を返すことは本質的に正しい選択- これは失敗を許容する関数の一般的な構造
T*には多くのセマンティクスがあるが、ここで必要なセマンティクス(単一オブジェクトへの参照か、何も返さない)はstd::optional<T&>がもつ単一のセマンティクスそのものT*で利用可能なAPIはほとんどこのケースで適切ではない一方で、std::optional<T&>のAPI(モナディック関数など)は有用性が高い- パターンマッチング機能は検討中であるものの、そこでは
std::optionalとポインタ型とでマッチング方法が異なる
- パターンマッチング機能は検討中であるものの、そこでは
- 標準ライブラリの堅牢化モードにより、
*と->はアクセスチェックが行われる- ポインタにはそのサポートはない
また、同じくC++26で導入されたstd::exception_ptr_cast<E>にも同様の事が言えるため、こちらの戻り値型もstd::optional<const E&>に変更することを提案しています。
変更後の宣言例
namespace std { template<class T, size_t N> class inplace_vector { ... template<class... Args> constexpr optional<reference> try_emplace_back(Args&&... args); constexpr optional<reference> try_push_back(const T& x); constexpr optional<reference> try_push_back(T&& x); ... }; }
namespace std { template<class E> constexpr optional<const E&> exception_ptr_cast(const exception_ptr& p) noexcept; }
- P3830R0 NB-Commenting is Not a Vehicle for Redesigning
inplace_vector- WG21月次提案文書を眺める(2025年09月) - P3981 進行状況
P3982R0 Fix the meaning of strided_slice::extent for C++26
strided_slice::extentの意味を変更する提案。
std::strided_sliceはC++26で導入されたstd::submdspanにおいてスライスの取り方を指定するもので、入力のmdspan内での先頭からのオフセット、そこから参照する要素数(エクステント)、その要素数の中でのステップ幅(ストライド)を指定するものです。
std::mdspan md = ...; auto smd = std::submdspan(md, std::strided_slice{offset, extent, stride});
strided_sliceに指定する値はすべて入力mdspan(上記だとmd)に対してのものですが、あまり知識のない状態で見るとこれには2つの解釈があります。
- 入力の
mdspanに対する指定- 上記の通り
- この場合、出力
mdspanの要素数は1 + (extent - 1) / stride
- 出力の
mdspanに対する指定- つまり、
offsetは無視して、extentとstrideは出力のmdspanのものとして指定しているように見える - この場合、入力
mdspanにはextent * strideの要素数が少なくとも要求される
- つまり、
現在の振る舞いは1ですが、ほとんどの場合にこの2つの解釈は一致した結果となります。しかし、これによって制限されていることがあります
strideの値が0の場合、一意ではないレイアウトが生成される可能性がある- 入力
mdspanでの除算の使用による
- 入力
- 出力範囲の値を静的に指定しつつ、ストライドは動的に維持する方法がない
この提案では、strided_sliceの現在の振る舞いを修正して、入力ではなく出力のmdspanに対しての指定をするように修正するものです。
また、submdspanと同等のスライス取得機能を持つ既存の他言語ではstrided_sliceとは異なり、その指定はfirst, last, strideで指定することが一般的です。{2, 5, 1}という指定はstrided_sliceでは入力範囲の[2, 7)を参照しますが、他言語の場合は[2, 5)を参照します。
この提案では、これについてrange_sliceという新しいスライス指定型を追加することを提案しています。range_sliceはfirst, last, strideというメンバを持つ集成体型です。
template<typename FirstType, typename LastType, typename StrideType = constant_wrapper<1zu>> struct range_slice { [[no_unique_address]] FirstType first{}; [[no_unique_address]] LastType last{}; [[no_unique_address]] StrideType stride{}; };
そして、submdspanにおいてrange_sliceのように3つの値に分解できる型(3要素タプルなど)をrange_sliceと同様に扱ってfirst, last, strideの指定としてみなすようにすることも提案しています。
さらに、range_sliceを追加したとするとstrided_sliceという名前は適切ではなくなります。なぜなら、どちらもストライドの指定は行っているためです。そこで、strided_sliceをextent_sliceにリネームすることを提案しています。
結局この提案では
strided_sliceをextent_sliceにリネームし、extent, strideを出力について指定するように意味を変更するrange_sliceの新規追加- 3つの値に分解できる型を
range_sliceとして扱うようにする
の3つを提案しています。そして、1については破壊的変更となるためC++26をターゲットにすることを提案しています。
変更前後の比較
// とりあえず一次元配列だとする std::mdspan md = ...; // before { auto smd = std::submdspan(md, std::strided_slice{offset, extent, stride}); assert(smd.extent(0) == smd.size()); assert(smd.mapping().stride(0) == stride); assert(smd.size() == (1 + (extent - 1) / stride)); } // after { auto smd = std::submdspan(md, std::extent_slice{offset, extent, stride}); assert(smd.extent(0) == extent); assert(smd.mapping().stride(0) == stride); assert(smd.size() == extent); }
int arr[12] = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11 }; std::mdspan md{arr}; auto smd1 = std::submdspan(md, std::extent_slice{.offset=1, .extent=3, .stride=3}); // [1, 4, 7] auto smd2 = std::submdspan(md, std::range_slice{.first=1, .last=9, .stride=3}); // [1, 4, 7] auto smd3 = std::submdspan(md, std::tuple{1, 9, 3}); // [1, 4, 7]
1の提案については、出力mdspanのレイアウト指定の各値が既知である場合、その値の計算が不要になることからパフォーマンス上のメリットがあることをベンチマーク結果とともに指摘しています。
P3983R0 simd object representation
std::simd::vecのstd::bit_cast後のオブジェクト表現を規定する提案。
上の方のP3973R0とモチベーションは共通するのでそちらも参照してください。
simd::vecはトリビアルコピー可能であると規定されておりstd::bit_castが可能ですが、ビットキャスト後のオブジェクト表現については未規定となっているため、ビットキャストを使用するstd::simdコードには移植性がありません。
vec<float> v = {...}; // C++26で合法だが、結果の要素型がint32として有効かは実装定義 auto as_int = std::bit_cast<vec<int32_t>>(v);
一方で既存のSIMD Intrinsicではこのような再解釈キャストを明示的かつ移植性がある形でサポートしているため、Intrinsicのユーザーがsimd::vecに移行する時にこのビットキャストの移植性が失われます。しかも、これは特に診断されないため、このことを認識していなければ対策を取ることもできません。
SIMD演算コードにおいてはSIMDレジスタ全体の再解釈キャストは良く使用されており、SIMDベクトル型は通常の配列の様なレイアウトであることを仮定したキャストが一般的に行われています。これはSIMDレジスタのハードウェア設計を反映しており、プラットフォームに関わらず十分に根拠のある仮定です。
このような問題を解決するために、この提案はsimd::vecの再解釈キャストを移植性のあるものにしようとするものです。
ここでは2つの変更を提案しています
std::simd::vec<T, native-abi<T>>が配列と同様のレイアウトを取ることを規定する- レイアウトの保証を確認する型特性クエリを追加する
is_simd_array_like_v<T, ABI>のような型特性によって、simd::vec<T, ABI>の特殊化のレイアウトが配列と同様かをチェックする
native-abi<T>は要素型Tについて現在のプラットフォームターゲットにおいて最も効率的なデータ並列実行を可能にする実装を選択するためのABIタグ型を表す型エイリアスです。このABIタグ型に基づいてsimd::basic_vecの特殊化あるいは内部構造が選択されます。
native-abi<T>はsimd::basic_vec<T>のデフォルトABIタグ型であり、これが使用される場合は1つのsimd::vecが1本のSIMDレジスタに正確にマッピングされ、そのレジスタ上で要素間と末尾のパディングがない配列と同様のレイアウトになることを意図しています。
したがって、native-abi<T>が使用されているsimd::vecは確実に配列と同じレイアウトになることが保証されます。また逆に、これを保証することはnative-abi<T>使用時のsimd::vecのレイアウトに一定の義務付けを行うことにもなります。
単にsimd::vec<T>とした場合はdeduce-abi-tを使用してABIタグ型が選択されますが、おそらくこれは要素数を指定しなければnative-abi<T>と一致するはずです。
一方で、basic_vec<uint32_t, fixed_size<22>の様な特定の固定要素数が指定されている場合のレイアウトは実装の選択に強く依存するため、対象外となっています。
2の型特性クエリの判定は1とは異なり、必ずしもnative-abi<T>が使用されている場合に限定されません。その範囲については実装定義とされているため移植性を担保するものではありませんが、実装の自由度を最大化しつつより広いsimd::vec特殊化についてレイアウト保証を利用できるようになる可能性があります。
この提案ではこの2つのアプローチを相補的なものとして、両方を提案しています。
また、マスク型simd::basic_mask<N, ABI>にも同様の問題があるものの、マスクの表現方法がハードウェアによって異なるため(std::array<bool, N>のような表現を採用する場合もあれば、std::vector<bool>のような表現を採用する場合もある)、特定のABIタグについてsimd::vecのような保証を提供するのは困難だとしています。そのため、マスクに関しては2の方法(型特性クエリの提供)のみを提案しています。
namespace std::simd { template<typename T, typename Abi> inline constexpr bool is_simd_array_like_v = /* see below */; template<size_t Bytes, typename Abi> inline constexpr bool is_mask_array_like_v = /* see below */; }
この提案はC++29をターゲットにしているようです。
P3984R0 A type-safety profile
プロファイル機能について説明した文書。
この文書はC++29以降に導入すべくホワイトペーパーとして検討が進められているプロファイル機能について解説したものです。文書では、プロファイル機能の概略や概念、具体的なプロファイルを用いたプロファイルそのものの構成などについて、規格の専門家ではない人に向けた解説が行われています。
解説文書であって提案ではないのですが、少し手を加えれば標準テキストに翻訳可能に記述されていることと、改めてまとめられた文書であることから、半分提案のように扱われているようです。
少し上にあるP3970R0により詳しくありますが、ここでもプロファイル機能を早期に確立するために委員会と実装者の協調的な行動を促しています。
P3985R0 Concepts for std::simd
std::simd関連のコンセプトの提案。
std::simdのベクトル型であるsimd::vecは2つのテンプレートパラメータを取る型です。SIMD演算を自動化するという目的上、simd::vecを用いた計算処理は要素型をテンプレート化して定義しておくことが一般的になると思われます。その際、simd::vecを受け取る関数テンプレートの記述は少し煩雑になります
template<typename T, typename ABI> requires std::integral<T> constexpr std::simd::basic_vec<T, ABI> add_sat(const std::simd::basic_vec<T, ABI>& lhs, const std::simd::basic_vec<T, ABI>& rhs) noexcept;
要素型とは別にABIタグ型(プラットフォーム固有の要素数指定など)があることで、テンプレートパラメータが2つ必要になります。また、要素型の制約も必要になります。この煩雑性はstd::simd自体でもよく見られます。
この提案は、主にユーザーが使用するためのstd::simd関連型の判定を行うコンセプトを提案するものです。
提案されているのは次の3カテゴリ10個のコンセプトです。
- 型の検出
vec_type<V>: 任意のstd::simd::basic_vec<T, Abi>を判定するmask_type<V>: 任意のstd::simd::basic_mask<Bytes, Abi>を判定するvec_or_mask_type<V>: 上記のorコンセプト、vec/maskを判定する
- 要素型についての特化
vec_integral<V>: 整数要素型のsimd::vecを判定するvec_signed_integral<V>: 符号付整数要素型のsimd::vecを判定するvec_unsigned_integral<V>: 符号なし整数要素型のsimd::vecを判定するvec_floating_point<V>: 浮動小数点数要素型のsimd::vecを判定するvec_complex<V>: 複素数要素型のsimd::vecを判定するvec_arithmetic<V>: 整数型か浮動小数点数型を要素型とするsimd::vecを判定する
- 要素型のマッチング
vec_of<V, T>: 要素型Tのベクトル型を判定する
これらのコンセプトは主にベクトル型simd::vecの要素型に焦点を当てています。これは、SIMDアルゴリズムは要素型に依存することが多く、プラットフォームによって異なるサイズ(ABIタグ型)に依存することはほとんどないためです。
提案されるコンセプトの宣言と定義の例
namespace std::simd { template<class V> concept vec_type = /* see below */; template<class M> concept mask_type = /* see below */; template<class V> concept vec_or_mask_type = vec_type<V> || mask_type<V>; template<class V> concept vec_integral = vec_type<V> && integral<typename V::value_type>; template<class V> concept vec_signed_integral = vec_type<V> && signed_integral<typename V::value_type>; template<class V> concept vec_unsigned_integral = vec_type<V> && unsigned_integral<typename V::value_type>; template<class V> concept vec_floating_point = vec_type<V> && floating_point<typename V::value_type>; template<class V> concept vec_complex = vec_type<V> && /* V::value_type is complex<T> */; template<class V> concept vec_arithmetic = vec_type<V> && (integral<typename V::value_type> || floating_point<typename V::value_type>); template<class V, class T> concept vec_of = vec_type<V> && same_as<typename V::value_type, T>; }
既存のstd::simd定義においては説明専用のコンセプトがいくつか定義されており、中にはここで提案されているものとよく似ているものもあります。それらをここで提案しているコンセプトで置き換えるかはLWGに委ねるとしています。それを行うか行わないかで2つのアプローチを提示しています。
P4003R0 Coroutines for I/O
コルーチンベースI/Oライブラリの設計について解説した文書。
P4007R0やP4014R0ではstd::execution(sender/receiver)のモデルがネットワークライブラリ(I/O)には向いていないのではないか、という主張がなされていますが、この文書はそこから参照されているコルーチンベースのI/Oライブラリの設計について解説しています。
それらの文書も含めてこの文書の出発点は、「C++20コルーチンを直接I/O処理に使用して、言語が提供する機能を観察する」という指針の下でユースケースを重視した開発から生まれたライブラリ実装の経験を元にしています。したがって、この文書や後続の文書で主張されていることは理論上だけのものではなく、実践を通して実証されたパターンがベースになっています。
この文書で解説されているライブラリはcppallianceのcapyという参照実装で既にある程度実装されており、これを使用する派生ライブラリも実装されています(ただし、実用状況は不明です)。cppalliance/capyはC++の標準ネットワークライブラリのための基盤機能となる事を目標にしており、この文書はそのための最初の一歩でもあります。
解説しているライブラリの標準化のための文言も含まれています。
- cppalliance/capy: Provides facilities for creating and accessing optional services during runtime.
- P4003 進行状況
P4004R0 Reconsider CWG 1395 "Partial ordering of variadic templates reconsidered"
CWG Issue 1395の変更を元に戻す提案。
CWG Issue 1395はC++11時代に提出されてC++17時点で解決されたイシューで、可変長テンプレートと非可変長テンプレートの関数のオーバーロード時の順序決定に関するものです。
次のようなコードにおいてオーバーロード解決の結果が曖昧になる問題を修正しようとしたものでした
template<class T> void print(ostream &os, const T &t) { os << t; } template <class T, class... Args> void print(ostream &os, const T &t, const Args&... rest) { os << t << ", "; print(os, rest...); } int main() { // イシュー解決前は曖昧になる print(cout, 42); print(cout, 42, 1.23); }
しかしこの解決は結果的に可変長テンプレートのオーバーロード解決時の順序付けに大きな変更を入れることになり、解決から10年経った現在でもEDGでしか実装されていないようで、そのEDGでもこれによる挙動がバグとして報告されているようです。
問題は、可変長テンプレートと非可変長テンプレートの関数テンプレートがあって、呼び出しのテンプレートパラメータの数がどちらにもマッチしうる場合に、可変長テンプレートの方が優先されてしまう場合があることです。
template<typename ... T> char *f(T &...); // #1 template<typename T> int *f(T &&); // #2 int i; auto *p = f(&i);
この例では、CWG 1395解決前(Clang/GCC/MSVC)は#2を選択しますが、解決後(EDG)は#1を選択します。
関数テンプレートのオーバーロード解決時の順序付けにおいて、参照除去などいくつか変換された後で2つの関数テンプレートの相互の型マッチングが行われます(片方のテンプレートパラメータをもう片方の実引数型から推論して成功するかどうかを見る)。CWG 1395解決前は、#1と#2の型マッチングにおいて、パラメータパックから非パック引数を推論できないため失敗し、#1は#2より特殊化されずに最終的に#2が選択されていました。
CWG 1395解決後はパラメータパックからの推論が早期失敗しなくなり、パック内の型を直接推論するようになったことによってその過程を通過し(逆方向も同じく成功し)、後の方で参照修飾の順序付け(&&よりも&の方が順位が高くなる)で#1がより特殊化されていると判定されます。
CWG 1395で言及されている別の例(CWG 1825)での動作を見てみると
template <class ...T> int f(T*...) { return 1; } // #1 template <class T> int f(const T&) { return 2; } // #2 void g() { f((int*)0); }
CV参照修飾は取り除かれた型でマッチングされ、CWG 1395以前はパックと非パックの推論ができないことでマッチングが早期に失敗していましたが、CWG 1395後はそこはパスするようになり、#2 -> #1の推論は成功するものの逆は失敗することで#1の方がより特殊化されていると判定されます。これについては、現在GCC/MSVCは#2を選択しClangは曖昧としてエラーになります。
このケースは前のものとは異なりテンプレートパラメータの修飾が異なります。
また、CWG1395の解決後にその箇所に関連するサンプルコードの根拠が無くなっているという問題があります。次のサンプルは規格書([temp.deduct.partial]/8)にあるものです
template<class... Args> void f(Args... args); // #1 template<class T1, class... Args> void f(T1 a1, Args... args); // #2 template<class T1, class T2> void f(T1 a1, T2 a2); // #3 f(); // calls #1 f(1, 2, 3); // calls #2 f(1, 2); // calls #3; non-variadic template #3 is more specialized // than the variadic templates #1 and #2
現在の規格にはf(1, 2, 3)が#2を、f(1, 2)が#3を呼び出す根拠が無くなっています(CWG 1395による変更後は、この2つのケースは曖昧になる)。
さらに、CWG 3154で指摘されている問題にも関連しています
template<typename ...T> void f(T*...); // #1 template<typename U> void f(U, U); // #2
現在の規格では#1が選択されますがCWG 1395以前は#2が選択されます。
結局、これらの問題に共通するのは可変長テンプレートとそうでないものの2つのテンプレートがある時に、非可変長よりも可変長のオーバーロードの方が優先されるようになっており、それがユーザーの期待に反しているということです。さらに、このことはほとんど実装されていません。この提案では、現在の実装の慣行に従う形でこれを解決しようとするものです。
提案では2つのオプションを提示しています
- CWG 1395の変更をrevertする
- [temp.deduct.partial]/11 にある、順序付けが同等である場合にパックの無い方を優先する規定は残す
- CWG 1825の例が曖昧ではなく#2を選択するように調整する
提案ではオプション1を推しているようで、文言はそちらのみが提供されています。
P4005R0 A proposal for guaranteed-(quick-)enforced contracts
終了セマンティクスが保証された契約アサーションの提案。
この提案はP3911で提案されているような無視できず失敗時に終了することが保証されているアサーションを、P3911とは異なり全く別の構文によって追加しようとするものです。
この提案の内容は次の点です
- 関数宣言に関数本体の実行前に必ず
trueに評価される必要のあるエントリ条件を指定するentry_condを追加する - 関数宣言に関数本体のリターン時に必ず
trueに評価される必要のあるリターン条件を指定するreturn_condを追加する mandatory_assert文を追加する
これらのいずれのアサーションにおいても、条件式がtrueに評価されない場合に違反ハンドラ経由(enforce)か直接(quick enforce)プログラムが終了されます。
この提案のアサーションは、例外の変換やconst化などの変換を全く行いません。また、P3910R0で提案されているようなセマンティクスとマングル名を対応させる方法を採用することも推奨されています。
P4006R0 Transparent Function Objects for Shift Operators
シフト演算子に対応する関数オブジェクトを追加する提案。
<functional>には加算等の算術演算や比較、論理演算に対応する関数オブジェクトがあります(例えばstd::plusやstd::lessなど)。これらの関数オブジェクトはそれぞれが演算子による(二項)演算の一つに対応しており、実装は単に引数を対応する演算子による式の呼び出しに転送するだけのものです。
このような関数オブジェクトの利点の一つは、アルゴリズムを始めとする呼び出し可能オブジェクトを渡して動作をカスタマイズできる場所において簡潔な記述を行えることです。
auto rng = std::views::iota(0) | std::views::take(5); // ラムダ式で指定する auto acc1 = std::ranges::fold_left(rng, 0, [](auto lhs, auto rhs) { return lhs + rhs; } ); // 関数オブジェクトを使用 auto acc2 = std::ranges::fold_left(rng, 0, std::plus<>{});
通常の演算子は式の形で記述することしかできずそのままだと関数引数等に渡せませんが、このような関数オブジェクト型があれば引数に渡したり型として使用したりすることができます。
<functional>にはC++で使用可能な演算子に対応してこのような関数オブジェクトが提供されていますが、すべての演算子に対応したものがあるわけではありません。代入系操作やインクリメント、アドレス取得、そしてシフト演算に対応したものは定義されていません。
この提案はそのうちシフト演算に対応する関数オブジェクトを追加しようとするものです。
提案しているのは左シフト<<に対応するstd::bit_lshiftと、右シフト>>に対応するstd::bit_rshiftの2つです。
宣言と定義例
namespace std { template<class T = void> struct bit_lshift { constexpr T operator()(const T& lhs, const T& rhs) const { return lhs << rhs; } }; template<class T = void> struct bit_rshift { constexpr T operator()(const T& lhs, const T& rhs) const { return lhs >> rhs; } }; // Transparent specializations template<> struct bit_lshift<void> { template<class T, class U> constexpr auto operator()(T&& lhs, U&& rhs) const noexcept(noexcept(std::forward<T>(lhs) << std::forward<U>(rhs))) -> decltype(std::forward<T>(lhs) << std::forward<U>(rhs)) { return std::forward<T>(lhs) << std::forward<U>(rhs); } using is_transparent = void; }; template<> struct bit_rshift<void> { template<class T, class U> constexpr auto operator()(T&& lhs, U&& rhs) const noexcept(noexcept(std::forward<T>(lhs) >> std::forward<U>(rhs))) -> decltype(std::forward<T>(lhs) >> std::forward<U>(rhs)) { return std::forward<T>(lhs) >> std::forward<U>(rhs); } using is_transparent = void; }; }
実装は他の関数オブジェクトと同様に単純に対応するシフト演算子呼び出しに転送するのみです。追加の事は一切しません。
これにより、関数オブジェクトとしてシフト演算を単純に指定できるようになります。
// Without P4006 - verbose lambda std::transform(values.begin(), values.end(), shifts.begin(), results.begin(), [](auto v, auto s) { return v << s; }); // With P4006 - concise and self-documenting std::transform(values.begin(), values.end(), shifts.begin(), results.begin(), std::bit_lshift<>{});
P4007R0 Senders and Coroutines
std::executionとコルーチンの非同期モデル間のギャップを指摘する文書。
この文書は、非同期I/O(特にネットワークI/O)をstd::executionで扱うときに、それをコルーチンとして扱う際(主にco_awaitの利用による構造的な並列性の活用)に生じるドメインギャップについて説明しています。
この文書の指摘するドメインギャップはstd::executionの設計の欠陥ではなく、std::executionとコルーチンを組み合わせた時に(そしてI/Oという領域において)発生するもので、std::executionとコルーチンのモデル間の相違が浮き彫りになったものです。
文書では次の4つのギャップを報告しています
- エラー報告
- I/Oに特有の、部分的な成功を
sender/receiverの3つのチャネルにうまくマップできない - 部分的な成功: I/Oがキャンセルされたが、データは(途中まで)受信している
- Asioでは
void(error_code, size_t)としてモデル化されており、多くのI/Oライブラリが同様に扱っている
- Asioでは
sender/receiverのset_stoppedチャネルは引数を取らないため、この場合に部分的に受信したデータが失われる- 回避しようとするとすべてを
set_valueチャネルで扱うことになり、sender/receiverの通常のエラー処理モデルが利用できなくなる
- I/Oに特有の、部分的な成功を
- コルーチンから
senderへのエラー転送- 例えば
execution::taskによるコルーチン内で、失敗でそのtaskを終了させようとする場合にco_return errが使用できない- コルーチンの
co_returnはsender(task)のset_valueチャネル(成功)にマップされる sender(task)のset_errorチャネルにシグナルする方法が無い
- コルーチンの
- 検討されている
co_yield with_error(...)のような方法は、既存のco_yieldの使用慣習(値の生成と中断、終了を意味しない)に反する - コルーチンのプロミス型において
return_voidとreturn_valueがどちらかしか定義できないことが原因ではあるものの、これは他のコルーチンライブラリでは問題になっていなかった
- 例えば
- フレームアロケータの伝播
- コルーチンではコルーチンフレームのアロケーションが必要となり、それをカスタムするためのアロケータの伝播が
sender/receiverモデルの方法(環境による伝播)とマッチしない- 例えば
execution::taskコルーチンを起動しようとするときには、既にコルーチンフレームは確保済み- アロケータの伝播タイミングが遅い
- コルーチンにアロケータを与えるには引数で指定する必要があるが、
sender/receiverモデルの方法と整合しない
- 例えば
- コルーチンではコルーチンフレームのアロケーションが必要となり、それをカスタムするためのアロケータの伝播が
- 対称転送
senderでは対称転送をサポートできない- P2583R0(少し上)でより詳しく解説されている
これらのギャップは、std::executionの設計選択(がコルーチンの設計選択と異なることによる)のコストを示しており、これらのギャップはトレードオフです。
- エラー報告のギャップ: 結果報告チャネルのトレードオフ
std::execution:let_value, upon_error, upon_stoppedという3つのアルゴリズムは型レベルで分離されており、コンパイル時に3つの結果報告チャネルを分岐できる- コルーチン:
(error_code, size_t)はまとめて返され、エラーとキャンセルにおけるバイト数は意味を持ち分離できない
- エラー転送のギャップ:
co_yieldのトレードオフstd::execution: コルーチンからsenderへの独立したエラーチャネルco_yield with_error(ec);
- コルーチン: 確立された
co_returnのセマンティクスco_return std::unexpected(ec);
- フレームアロケータ伝播のギャップ: フレームアロケータのトレードオフ
std::execution:connect / startによる遅延実行senderパイプラインはゼロアロケーション
- コルーチン:
promise_type::operator newは呼び出し元で適切なフレームアロケータとともに実行される
- 対称転送のギャップ: 対称転送のトレードオフ
std::execution:senderアルゴリズムは戻り値の無い完了関数(set_value())を持つ構造体senderパイプラインはゼロアロケーション
- コルーチン:
await_suspend()がcoroutine_handle<>を返す- O(1)スタックのコルーチンチェーン
これらのギャップを解消するためにはstd::executionとコルーチンのいずれかもしくは両方の設計の変更が必要となります。現状この影響を受けるのはstd::executionとコルーチンの間に立つものであり、ユーザーが利用可能なのはexecution::taskのみです。
execution::taskにはこのギャップにより生じる問題がすでに認識されており、この文書ではそれが構造的なもので軽微な修正では済まないと指摘しています。C++26にこのままexecution::taskを含めると、この問題が未解決のままABIに固定され、そのコストは利用者が負担することになります。C++26からexecution::taskを削除しても、コストは誰にも発生しません。
最終的に、この文書では次の事を推奨しています
std::executionをC++26に入れるexecution::taskはC++29に延期する- コルーチンネイティブのI/O設計を
std::executionベースのものと並行して検討する
推奨事項の一つであるコルーチンのためのI/Oライブラリの設計はP4003R0(少し上)で行われています。
P4008R0 Clean Modular Mode: Legacy Opt-out for C++
Cに由来する安全ではない記法をオプトアウトするための機能の提案。
この提案のモチベーションはプロファイル機能と同じく、既存のC++コードに対する後方互換性を維持しつつもCから引き継いでいる安全ではない構成(縮小変換やCキャスト、Cの標準ライブラリ機能など)を明示的に禁止することで、新しく記述されるコードにおいての安全性を高めようとするものです。
この提案ではそのためにモジュールを使用します。まずstdモジュールを3つの部分に分けます
std.core: 除外不可能な低レベルプリミティブ- ポインタ、型レイアウト、アトミック操作、ビット演算
std.modern: 安全な最新のライブラリコンポーネント- コンテナ、スマートポインタ、
<ranges>
- コンテナ、スマートポインタ、
std.legacy: C互換およびレガシー機能- 配列のdecay、Cキャスト、Cの標準ライブラリ
その上で、exclude std.legacy;構文によってstd.legacyに該当する機能をそのモジュール内でオプトアウトするようにします。
// Default behavior (100% backward-compatible): import std.core; import std.modern; import std.legacy; // Clean Mode (user opt-in) exclude std.legacy; // Removes only legacy pitfalls, retains full low-level power
この提案ではこれをクリーンモードと呼んでいます。
上記のモジュールはライブラリ機能だけではなく言語機能も含んでおり、exclude std.legacyによってstd.legacyに割り当てられている言語機能(C由来の安全ではない構成)が禁止されコンパイルエラーになるようになります。
exclude std.legacy; // クリーンモードを有効化 void clean_code() { int arr[10]; int* p = arr; // ERROR:配列のdecayは禁止 double d = 3.14; int i = (int)d; // ERROR:Cキャストは禁止 }
このように、クリーンモードは明示的なオプトアウトによって安全ではない構成を禁止するため、デフォルトは現在のC++のままです。したがって既存のコードはクリーンモードが利用可能な場合でも引き続き有効なコードです。主に新しく書かれるコードをクリーンモードで記述する事でクリーンモードへの移行を段階的に少しずつ進めることができます。
この提案はモチベーションもアプローチもプロファイル機能と近しいものがありますが、プロファイル機能については特に言及されていません。プロファイル機能に比べると具体性や柔軟性が低く、適用範囲が狭いものになってはいます。
P4009R0 A proposal for solving all of the contracts concerns
P2900のContracts機能における懸念点をすべて解消するライブラリ中心のContracts機能の提案。
P2900のContracts機能に対してはいくつかの懸念点が提起されており、無視できない契約アサーションをC++26内で追加しようとする動きがあります。しかしそのような設計の提案には問題点がいくつか指摘されていたようです。
この提案ではそれらを解消したうえで、P2900のContracts機能に対して提起されている懸念点をすべて解消することのできる、ライブラリ中心のContracts機能の設計を提案するものです。
P2900の記述に対して
void f(int x) pre(x >= 0); int f() post(r: r >= 0); contract_assert(x);
この提案の記述は次のようになります
void f(int x) pre(std::pre(x >= 0)); int f() post(r: std::post(r >= 0)); contract_assert(std::cassert(x));
現在のアサーション構文について、デフォルトではP2900と同じ動作をするものの、特定の型が渡された場合にその型に応じてセマンティクスを選択できるようにしています。なおかつ、この契約アサーション自体の評価セマンティクスは
- enforce
- ignore
の2つに絞ることを提案しています。
例えば、pre(expr)においてexprの評価結果がstd::ignoreの型である場合、このアサーションはignoreセマンティクスで評価されます。exprの結果がbool値でありfalseである場合はP2900と同じ動作をします。
std::pre(), std::post(), std::cassert()などの関数は現在の評価セマンティクスの構成(実装定義の方法で決定される)に合わせて戻り値を調整することでアサーションの評価セマンティクスを決定するものです。例えば現在の構成がignoreセマンティクスの場合はstd::ignoreを返し、observeの場合(追加するとしたら)は違反時に違反ハンドラを呼び出し関数自体はtrueを返します。
std::pre()などの関数は一見冗長ですが、このような関数はユーザー定義のカスタム関数を使用することもできます。これによって評価セマンティクスのほとんどの部分をライブラリで定義することができるようになり、言語仕様に依存しなくなります。将来的に新しいセマンティクスを追加する場合でもライブラリの更新のみで行えます。
また、const化についてはC++29以降でconstify(expression)などの様に記述するオプトインなものとして検討することを提案しています。
さらに、Contracts機能自体をホワイトペーパーに移して(C++26から削除して)この設計を検討していくことを推奨しています。
この提案の方向性はEWGの議論においてリジェクトされています。
P4010R0 Add funnel shift operations to bit header
ファンネルシフトを行う関数の提案。
ファンネルシフトとは、2つの整数値を連結した中間値上でシフト演算を行い、そこから元のビット幅で結果を取り出すシフト演算です。
提案文書より、16bit整数値に対する右ファンネルシフトの動作説明
Funnel Shift Right Example: funnel_shift_right(0xABCD, 0x1234, 8)
入力値 (どちらも16-bit):
high = 0xABCD = 1010101111001101
low = 0x1234 = 0001001000110100
Step 1: 結合する (high in upper, or left-most, bits)
0xABCD 0x1234
┌───────────────┬──────────────┐
10101011110011010001001000110100
Step 2: 8ビット右シフト
0xAB 0xCD12
┌───────────────┬──────────────┐
........101010111100110100010010
Step 3: 下位16bitを取り出す
0xCD12
┌──────────────┐
1100110100010010
Result: 0xCD12
入力から出力の流れが漏斗(funnel)の形状でイメージできることからこのような名前が付いているようです。
ファンネルシフトは、ハッシュ関数や暗号、圧縮など様々な分野でよく使用されるビット操作で、x86/64とARM共に専用の命令を備えているほか、CUDAやLLVMなどにおいてはソフトウェアサポートも提供されています。
ファンネルシフトは通常のC++コードとして記述して実行することもできるのですが、次のような問題があります
- 可読性: ファンネルシフトという意図が複雑なコードによって不明瞭になる
- ヒューマンエラー: 複雑なビット操作の連続になり、実装ミスが発生しやすい
- エッジケースのケア: シフト量の値によるエッジケースで何が起こるかを決定して、一貫して実装する必要がある
- 最適化: ユーザーの実装はHWの命令にマッピングされづらい
- SIMDへの移植性: SIMDコードで利用する場合はプラットフォーム固有のintrinsicに依存するため、移植性が低くなる
この提案は、ファンネルシフトを行う標準ライブラリ関数を提供することで、ファンネルシフト操作を利用する際のこれらの問題を解消することを目指すものです。
提案では、<bit>ヘッダにスカラ整数値用の関数を追加する事を提案しており
namespace std { template<class T, class S> constexpr T funnel_shift_left(T high, T low, S s) noexcept; template<class T, class S> constexpr T funnel_shift_right(T high, T low, S s) noexcept; }
同時に<simd>ヘッダにもstd::simd::vec用の関数を追加することを提案しています。
namespace std::simd { template<class V0, class V1> constexpr V0 funnel_shift_left(const V0& high, const V0& low, const V1& s) noexcept; template<class V0, class V1> constexpr V0 funnel_shift_right(const V0& high, const V0& low, const V1& s) noexcept; template<class V, class S> constexpr V funnel_shift_left(const V& high, const V& low, S s) noexcept; template<class V, class S> constexpr V funnel_shift_right(const V& high, const V& low, S s) noexcept; }
提案文書より、サンプルコード
// ハッシュを混ぜる例 uint32_t mix(uint32_t x, uint32_t y) { return std::funnel_shift_right(x, y, 17); }
エッジケースへの対応は、HW命令へのマッピングを重視して次のように決定されています
- シフト量の範囲
- ビット数
Nに対して0 <= s < N(事前条件) - 暗黙の剰余演算(ユーザーの指定
sに対してNを法とする剰余を取った値をシフト量とすること)は行わないrotl/rotrでは行われている
- ビット数
- シフト量がゼロの場合
funnel_shift_right(high, low, 0):lowを返すfunnel_shift_left(high, low, 0):highを返す
- 入力の整数型
- 符号なし整数型限定
std::simdのオーバーロードは、要素ごとにこのスカラ版ファンネルシフトを行う形になります。どちらも既存のHW命令にマップすることを意識して設計されているため、その動作に大きな違いはありません。
P4011R0 Redefining narrow contract
狭い契約と広い契約の定義を変更する提案。
少し上のP3977R0が関連しているためそちらも参照してください。
次のような関数があるとき
void f(int x) pre(x >= 0) { if (x < 0) std::unreachable(); }
この関数は事前条件を持つため、(その関数定義によらずに)狭い契約を持ちます。狭い契約の定義によれば、この関数の呼び出し時の事前条件違反は未定義動作であり、定義は実際にそうなっています。
しかし、P3911で提案されているような無視できない契約アサーションによって未定義動作を排除する機能が使用されると
void f(int x) pre!(x >= 0) { if (x < 0) std::unreachable(); }
この関数は未定義動作に陥る事が無くなり広い契約を持つようになりますが、依然として事前条件を持っているため狭い契約を持ってもいます。
このような矛盾のような状態に陥るのは現在の狭い契約と広い契約の定義が契約アサーションとその多様なセマンティクスを考慮できていないためであり、この提案ではその定義を修正することで対応しようとしています。
この提案による狭い契約と広い契約の定義は次のようになります
関数が狭い契約を持つのは、少なくとも一つの入力(関数引数および関数の動作に影響を与えるグローバル状態など)に対してその呼び出しが誤りである場合である。
それ以外の場合、関数は広い契約を持つ。
この定義による帰結として、人間あるいはツールはある特定の関数呼び出しがバグであるかを判断できます。
ここでの誤り(erroneous)とはerroneous behaviorと同じ言葉を使用しているものの、必ずしもerroneous behaviorを意味するわけではありません(EBも含んではいます)。
この定義の目的は、関数の動作に関する議論を分離し、議論する人々が共通の言葉で議論できるようにすることにあります。
P4012R0 value-preserving consteval broadcast to simd::vec
simd::vecにconstevalブロードキャストコンストラクタを追加する提案。
この提案はP3844R2から分離されたものです。P3844はこの提案の内容と数学関数オーバーロードに対する変更の2つを含んでいましたが後者だけがC++26に採択済みです。この提案の内容はLEWGのレビューで強い懸念(このような動作をおこなう型の前例がない)が示されていたため分離されました。
この提案の変更は
- [simd.math]に関連する議論と提案を削除
- LWGで提起された懸念事項に関する議論を追加
- 設計空間に関する議論をまとめて、LEWGで承認されたものについてのみにする
- 配置の変更
などです。
この提案はsimd::vec<float> x;に対してx + 1のような記述をできるようにするものです。このようなコードでは右辺オペランド(1)がsimd::vec<float>に暗黙変換されることで動作(この時1がsimd::vecの全ての要素に展開されるので、ブロードキャストコンストラクタと呼ばれる)しており、その際にint -> floatの縮小変換が発生するために現在は一律に禁止されています(縮小変換が発生しなければ使用可能)。
この提案では、縮小変換が起こる場合のコンストラクタをconstevalにして縮小変換によって情報の欠落が発生しない事をコンパイル時にチェックすることで定数に対してのみx + 1のような記述を許可するものです。
これには次のような懸念がLWGのレビューにおいて表明されていました
- 新規性、標準ライブラリ中にはこのような動作をする型が無い
- この提案による制約付きのコンストラクタはコンセプト/型特性で正しく検出できない
- Immediate-escalation によりユーザーが意図したものよりも多くのコードがコンパイル時実行になりうる
- Immediate-escalation 後のエラーは理解しづらくなる可能性がある
このような懸念に対してLEWGのレビュー中に十分な時間を取って対処されていなかったため、全体投票で議論を呼ぶような採決を強行することを回避するためにP3844から分離されました。
P3844R0~R2においては、constevalコンストラクタにおいて変換可能(convertible_to<value_type>)であり値の損失が発生しない(value-preserving-convertible-to<value_type>)ことで制約していましたが、これだとvec<float>() + 1.5がvec<float>になってしまうという問題がありました(vec<double>になるか禁止したい)。それに対しては算術型のTに対してはcommon_type_t<T, value_type>がvalue_typeであることを要求することで対処しましたが、これにもshort等のリテラルが存在しない型についてvec<short>() + 1が許可されないという問題があります。
それらについて対処した結果、制約は次のようになっていました。
template <typename From, typename To> concept potentially-convertible-to = is_arithmetic_v<From> && convertible_to<From, To> && !value-preserving-convertible-to<From, To> && (is_same_v<common_type_t<From, To>, To> || (is_same_v<From, int> && is_integral_v<To>) || (is_same_v<From, unsigned> && unsigned_integral<To>));
これを用いてsimd::vecのブロードキャストコンストラクタを制約します
template <class From, class To> concept simd-consteval-broadcast-arg = explicitly-convertible-to<From, To> && potentially-convertible-to<remove_cvref_t<From>, To>; template <class T> class basic_vec { public: template <explicitly-convertible-to<T> U> constexpr explicit(see below) basic_vec(U&&); // #1 template <simd-consteval-broadcast-arg<T> U> consteval basic_vec(U&&); // #2 };
constevalブロードキャストコンストラクタ(#2)は対応する通常のコンストラクタの制約(explicitly-convertible-to)を包摂していることによって、算術型UについてTへの変換が可能(potentially-convertible-to)な場合は常にconstevalブロードキャストコンストラクタが選択されます。これによって、算術型からsimd::vec(basic_vec)への明示的変換の一部はできなくなります。
値を保持する型変換を持つすべての型はpotentially-convertible-toで弾かれることにより#1を選択し、非算術型(算術型へのユーザー定義変換を持つ型)は#2が有効ではないため#1を選択します。
constevalコンストラクタでは縮小変換が実際にロスを生じないことをチェックする必要があり、その際のエラー発生方法としては例外送出が提案されていました。この提案では、具体的な例外型を導入しないようにすることを提案しています。
using V = simd::vec<float>; template <typename T> struct X { explicit operator T() const; }; template <typename... Ts> concept has_common_type = requires { typename std::common_type_t<Ts...>; }; static_assert(not std::convertible_to<X<float>, V>); // この提案以前の動作と同じ / Parallelism TSと異なる static_assert( std::convertible_to<float, V>); static_assert( std::convertible_to<short, V>); static_assert( really_convertible_to<short, V>); static_assert( std::convertible_to<int, V>); // この提案により動作が変わる / Parallelism TSと同じ static_assert(not really_convertible_to<int, V>); static_assert( std::constructible_from<V, X<float>>); static_assert(not std::constructible_from<V, X<short>>); static_assert( std::constructible_from<V, double>); static_assert( std::constructible_from<V, float>); static_assert( std::constructible_from<V, short>); static_assert( std::constructible_from<V, int>); static_assert(not has_common_type<V, double>); static_assert( has_common_type<V, int>); // it's vec<float> V f(int n, short m, std::reference_wrapper<int> l, std::reference_wrapper<float> f) { V x = '\1'; // OK x = 1; // OK (この提案により動作が変わる) x = 0x5EAF00D; // ill-formed x = V(n); // ill-formed (この提案により動作が変わる / Parallelism TSと異なる) x = m; // OK x = V(l); // OK x = f; // OK x = 1.1; // ill-formed x = V(1.1); // OK x = X<float>(); // ill-formed: no match for operator= (no known conversion …[]) x = float(X<float>()); // OK (obvious) x = V(X<float>()); // OK }
really_convertible_toコンセプトは、この提案の型変換を考慮したうえで変換可能性を調べるコンセプトです。
現状(この提案以前のC++26)と比較すると、重要な相違点は次の4点です
| 式 | 現状 | この提案 |
|---|---|---|
convertible_to<int, V> |
false |
true |
common_type_t<V, int> |
❌ | V |
x = 1; |
❌ | ✅ |
x = V(n); |
✅ | ❌ |
おそらく、convertible_to<int, V>がtrueを示すにもかかわらずx = V(n)の様な明示的変換ができない(コンパイル時の一部の定数に限定される)ことが最も懸念された点です。
提案文書より、動作の違いのサンプル
int n = 1; /// この提案で変更されない動作 simd::vec<float> x = 1.f; x = 0x5EAF00D; // ill-formed x = n; // ill-formed x = static_cast<float>(n); // OK static_assert(constructible_from<V, int>); // ✅ /// この提案で変更される動作 x = 1; // OK x = static_cast<simd::vec<float>>(n); // ill-formed pow(x, 3); // OK static_assert(convertible_to<int, V>); // ❌ std::common_type_t<V, int> y = x; // V
この提案はC++26にこのconstevalブロードキャストコンストラクタを追加することを推奨していますが、懸念の全てに応えられていない事を認めており、代替案として現在のブロードキャストコンストラクタのexplicit指定を削除することでC++29に向けての設計余地を残す案も提示しています。
P4014R0 The Sender Sub-Language
C++の新しいサブ言語としてのsenderとそのトレードオフを説明する文書。
この文書では、sender/receiverモデルの成り立ちとその実装であるstd::executionの簡単な解説を行い、このモデルは特定のドメインの非同期処理に特化したものでありそのためのトレードオフがあること、そのトレードオフによりすべての非同期処理に対して最適とは言えないことを解説しています。
sender/receiverモデルの確立している特性にはそれぞれオーバーヘッドが伴っています
| 特性 | メリット | コスト |
|---|---|---|
| 型の可視性 | コンパイラはワークグラフを型として認識できる | ヘッダオンリー実装(コンパイル時間増大) |
| 定常状態でのゼロアロケーション | ワークグラフ全体はスタック上に存在する | コンパイル時間増大 |
| コンパイル時のワークグラフ構築 | connectによって単一の型へ集約される |
複雑なプログラミングモデル |
| ナノ秒レベルの決定論的実行 | CUDA実装と同等のパフォーマンスを発揮する |
まとめると
- プログラミングモデルの複雑さ、理解の困難さ
- コンパイル時間
の2つのコストがsender/receiverモデルにはかかっています。このコストと得られるメリットのトレードオフは次の領域では正当化されます
- 高頻度取引(HFT)や金融計算
- ナノ秒レベルの決定性が重要
- GPU等のアクセラレータを用いたヘテロジニアスコンピューティング
- ベンダー固有の拡張が必須となる領域では、共通的な基盤モデルとしての性質も重要になる
- 組込みシステム
- ヒープが利用できないためゼロアロケーションの性質が重要
- 科学技術計算
- 手書きCUDAと同等のパフォーマンスが重要
しかし、より日常的なファイルやネットワークのI/Oにおいては必ずしもこれらのトレードオフが正当化されない可能性があります。特に、直接的なコルーチンの使用によるスタイルと比べたsender/receiverモデルの複雑さが問題となります。
そのため、sender/receiverモデルがC++におけるすべての非同期処理を扱う必要がなく、sender/receiverモデルとコルーチンそれぞれがそれを必要とする領域で適切に使用できること(非同期処理モデルを押し付けない事)が望ましいとしています。
この文書は提案ではありませんが、次の投票を行うことを促しています
std::executionはヘテロジニアスコンピューティングと比べて、コルーチンによる非同期I/Oに最適とは言えない- コルーチン駆動の非同期I/Oも、ヘテロジニアスコンピューティングと同様にその処理領域に特化した最適化が自在に行えるべき
今月公開の同じ著者の提案文書(P2583R0、P4003R0、P4007R0など)と同様に、sender/receiverによる非同期処理モデルは特定の領域に特化したもので、すべての非同期処理にとって最適ではないということを主張しています。
- HPCwire - Since 1987 – Covering the Fastest Computers in the World and the People Who Run Them
- P4014 進行状況
P4015R0 Enforcing Contract Conditions with Statements
無視できない契約アサーションを宣言ではなく文で記述するようにする文書。
C++26 Contracts機能に対して、契約条件を必ず評価しfalseの場合にプログラムを終了するという動作を確実に履行するセマンティクスを持つアサーション(無視できない契約アサーション)の必要性が強く主張され、C++26の土壇場で導入しようとする試みがいくつもあります。最終的にはいずれもその動作を規定しようとする段階で行き詰まっているようです。
P2900の範囲の契約アサーションはそのセマンティクスについて曖昧性を持っていることで、関数宣言に記述されていても関数の呼び出し境界での合意事項を示すのみで特定の動作を指定しません。これは意図的な設計であり、関数宣言で契約を表明する際に重要な性質でもあります。
一方で、無視できない契約アサーションは関数宣言に記述しようとすると単に合意事項の表明に留まらずにその関数の動作の一部を指定してしまいます。これはプログラマよりもむしろコンパイラに対してそのような動作の保証を与えてしまうことによって、合意事項の変更が容易に後方互換性を破壊することになります。
この文書では、ルール(順守すべき制約)とオファー(ルールの適用、強制)という言葉によってそれらを区別し、ルールとオファーをそれぞれC++における宣言(declaration)と文(statements)に対応させて分離する設計方針を提示しています。
- 宣言は、コンポーネント間の合意事項(ルール)を表明するにとどめる
- 文は、関数本体内に隠蔽された形で合意事項の強制(オファー)という振る舞いを記述する
これに基づいて、無視できない契約アサーションは関数宣言において既存のアサーションを拡張する形で導入するのではなく、関数本体内(あるいは呼び出し側)において特定の文によって記述するようにします。
int f(int n) { // 無視できない事前条件 enforce_condition(0 <= n); // 無視できない事後条件、関数の最後に実行される enforce_condition_on_return(r: -100 < r && r < 100); ... return n; }
int f(int n) pre(0 <= n) post(r: -100 < r && r < 100) { // preとpostと同じ条件を強制する enforce_preconditions; enforce_postconditions_on_return; return n; }
enforce_conditionやenforce_condition_on_returnはこの文書による無視できない契約アサーションです。制御が到達すると必ず評価され、falseになるとプログラムを終了させます。
これらのアサーションが宣言に現れていないことによって、呼び出し側から見た時にその動作は関数の一部にならず、呼び出し側のコンパイラも動作について特別な仮定をおくことができなくなります(つまり通常の関数と同等)。
式を指定しない形式(2つ目のサンプルコード)によって、関数宣言で契約を記述しつつ同じものを強制するように記述することもできます。
また、enforce_preconditions_on_call/enforce_postconditions_on_callの様な文によって、ある範囲の関数呼び出しにおいてその事前条件と事後条件の適用を強制する(すなわち無視できないアサーションにする)方向性についても提示しています。
この文書ではこのような設計の方向性を示しているものの、それをC++26や29に向けて提案するものではありません。
P4016R0 Canonical Parallel Reduction: A Fixed Expression Structure for Run-To-Run Consistency
範囲の並列集計を行う機能について、実行時の再現性を確保するようにする提案。
C++には範囲の集計を行う方法として大きく次の2つのものがあります
std::accumulate- 左畳み込みの式構造が規定されているため処理は決定的であるものの、逐次的な処理であり並列化は許可されていない
ranges::fold_leftなども同様
std::reduce- 並列化可能でスケーラブルだが、結合順序やグルーピング方法などが未規定であり、結合法則が成り立たない演算(浮動小数点数など)においてスレッド数やスケジューリングなどの違いによって、同じ環境でも実行ごとに結果が変化する可能性がある
すなわち、標準ライブラリには並列化可能かつ処理される式の構造が決定的であるような範囲の集計機能が欠けています。この提案は、そのギャップを埋めるためのものです。
この提案では、std::accumulateがやっているような抽象的な式構造を規定することを並行集計機能に対しても行うことで、並行評価を許容しながらも実装側が準拠すべき(変更してはならない)式の大枠の構造を指定し、それによって並行集計処理の演算のグループ化と実行順序を決定的にしようとしています。
より一般的には、並行集計処理は次の3つの要素によってその振る舞いが決定されます
- 式の構造
- 実行される抽象的な計算内容
- グループ化、かっこ付け、オペランドの順序
- アルゴリズム
- 式のどの側面が明示的に定義されているか、あるいは意図的に未規定のままであるかを決定する意味論的な契約
- 実行方法
- 式の評価をどのようにスケジュールするか、逐次・並列・ベクトル化など
C++の標準ライブラリのアルゴリズムおよび集計機能に関してはこの分離に基づいて設計されています。1の式構造及び2のアルゴリズムはアルゴリズム関数そのものによって指定され、3の実行方法は実行ポリシーによって指定されます。
実行ポリシーは実行の側面を制御するもので、スケジューリングや並行性を制約する一方で式構造やアルゴリズムの意味論には全く影響を与えません。execution::seqが指定されていたとしても式構造を指定するわけではないため、reduce(execution::seq, ...)はstd::accumulateと同じにはならず、非結合的な演算においては実行ごとに異なる結果を返しえます。
提案では標準として式構造を固定していることを式の所有権を持つと表現しており、std::accumulateとstd::reduceの違いは式の所有権を持っているかどうかにあるとしています(std::accumulateは式を所有している)。
したがって、この提案が導入しようとしているのは、式の所有権を持ちながらも並列化が可能な範囲集計処理です。
式の所有権を持たないstd::reduceは式構造を全く指定していませんが、それを完全に指定しているstd::accumulateと部分的に指定しているstd::inclusive_scan/std::exclusive_scanの規定を見てみると、集計アルゴリズムの式構造には次の2つの要素があります
- オペランドの評価順序
- 入力範囲の要素がどの順番で消費されるか
- オペランドのグルーピングの方法
- それぞれの要素の結合順序
- かっこの付け方、あるいは畳み込みの方向
- それぞれの要素の結合順序
std::accumulateはこの2つを完全に指定している(左から右への消費、左畳み込み)ことによって式構造が固定されており、scan系アルゴリズムはオペランドの評価順序を規定している一方でグルーピングの順序を指定していません。そして、reduceはすべてを指定しないことによってハードウェアの並列性を最大化しようとしています。
すなわち、集計アルゴリズムの式構造としてはこの2つの事を指定することで再現性のある集計処理を実現することができます。
また、集計処理においては入力要素列を何かしらの木構造にマッピングする必要があり、この木の構築方法にも選択肢があります。この木の種類によっても最終的な集計の順序が変化するため、これもまた重要な選択となります。
この提案では並列化可能な集計処理のための規範的な木構造として、Iterative Pairwiseを採用することを提案しています。これは、範囲の先頭要素から順番にグルーピングすることで木構造を構成し、その木の葉から集計していくものです。
もう1つの候補としてrecursive bisection(2分木による分割統治)も挙げられており、この2つの方法は各レーンの要素数が2の累乗個の場合に同じ結果を生成しますが、異なる場合は余った要素の処理方法に差が出ます。提案では、次のような理由からIterative Pairwiseを選択しています
- オンラインアルゴリズムであること
- 要素数
Nが処理の初めに必要ではない size()を使用できないforward_rangeや、非同期的に到着するストリーミングデータに対してデータの全体がそろわなくても処理を開始できる
- 要素数
- P2300
sender/receiverモデルとの相性の良さ- 前述のストリーミング特性がP2300モデルにおける、実行場所・方法と実行対象の分離と相性がいい
senderパイプライン自体が遅延評価であるため、senderパイプラインの構築と評価の前にデータを実体化させることなく、パイプラインを流れる要素を消費するsenderとして自然に表現できる
- 既存実装の慣行と整合する
- GPUスレッド(ワープ)やSIMDレーンへのマッピングが容易であり、GPUやSIMDプログラミングにおいても非常に一般的なメンタルモデルに合致する
- HWがネイティブサポートしていることでオーバーヘッドが最小になる
- NVIDIA CUBなどの既存ライブラリでも採用されている
- GPUスレッド(ワープ)やSIMDレーンへのマッピングが容易であり、GPUやSIMDプログラミングにおいても非常に一般的なメンタルモデルに合致する
この提案による並列集計機能が所有すべき式の構造は、ユーザーが選択したトポロジ座標によってパラメータ化される規範的な式(canonical expressions)として定義されます。規格はこの規範的な式を1つ定義し、その戻り値は(実行戦略によらず)その式を評価した結果と同等のものになる必要があります。
この規範的な式の唯一のパラメータがレーンパラメータLであり、Lは直感的にはSIMDのレーンやGPUのスレッドに対応しています。L = 1の場合は入力範囲を分割しないで構築する単一の木構造になり、Lを増やすと入力範囲をL個に分割して、それぞれのレーン毎に並列化して集計する実装戦略が可能になります。
レーンパラメータLと入力要素数Nに対して、入力範囲はceil(N / L)個の要素毎にL個のレーンに分割され、各レーンにおいてIterative Pairwiseに従った集計処理が行われ、最後にレーン間でIterative Pairwiseに従った集計を行い結果とするのが、この提案の規範的な式になります。
規範的な式においてはオペランドの評価順序と結合順序が固定されているため、レーンパラメータLを決めると実行すべき式構造が実行環境や戦略とは無関係に固定されます。入力順序(範囲の順序)と二項演算、およびレーンパラメータが固定されると仕様に準拠する実装間で同一の式構造が特定されます。そのうえで浮動小数点数の評価モデルが固定されれば並列集計機能の結果の再現性が保証され、結果のビット単位の同一性保証が得られます。
規範的な式の詳細な定義は次のようになります
入力範囲X[0..N)、二項演算op、レーン数L(1 <= L)として
- 分割
XをL個のインターリーブされたレーンに分割する- 要素
X[i]はi mod Lで指定されるレーンに割り当てられ、各レーン内では入力順序が維持される
- Stage1: レーン毎の集計
[0, L)の各レーンjについて、CANONICAL_TREE_EVAL(op, Yj)を評価するYjはK = ceil(N / L)個の要素を持つNがLの倍数ではない場合、要素が足りないレーンにはダミー要素を追加するN < Lの場合、L - N個のレーンにはダミー要素を追加する
CANONICAL_TREE_EVAL(op, Yj)の評価中にダミー要素が存在する場合(NがLの倍数ではない場合)、そのダミー要素に対応する要素はopを呼び出すことなく木の評価の次のステップへ伝播される(ダミー要素は伝播しない)- グループにダミー要素しかない場合は、そのグループの結果をダミー要素とする(
opは呼び出されない)
- グループにダミー要素しかない場合は、そのグループの結果をダミー要素とする(
- Stage2: レーン間の集計
L個のレーンの結果R[0..L)を、CANONICAL_TREE_EVAL(op, R[0..L))によって集計する- ダミー要素は2のステップと同様に
opの評価がスキップされる
- ダミー要素は2のステップと同様に
- 初期値の統合
- 初期値(
init)が提供されN == 0の場合、A{init}を返すAはinitの型Tに対してremove_cvref_t<T>、初期値が無い場合は入力要素型Vに対してremove_cvref_t<V>
- 初期値(
init)が提供され0 < Nの場合、I = A{init}、R_allを3のステップ(Stage2)の結果として、op(I, R_all)を返す - それ以外の場合(初期値無しかつ
0 < N)、R_allを返す
- 初期値(
CANONICAL_TREE_EVAL(op, rng)は、Iterative Pairwiseによる木構造とそれに従った集計評価を表します。すなわち、範囲rngの先頭から隣接する要素同士を左から右へグルーピングしていき、それらのグループ間で同様に再帰的にグルーピングすることで木構造を定義するものです。この時、各段階で要素数が奇数となる場合(グルーピング後に余りの要素が出る場合)、それを単一のグループとして次の再帰的グルーピングステップにそのまま伝播させます。木の評価においては、グルーピングされた2つの要素を用いてopを評価します。
前述のように、木の形状はレーン毎の要素数Kおよびレーン数L(すなわちNとL)によって完全に特定され、この構造によって指定される集計処理は抽象的な式構造のみとなります。この規範的な式と同じ結果が得られるのであれば、独立した部分式を並行化して評価することが許可されます。
2段階集計とCANONICAL_TREE_EVALによる規範的な式の構成例
Example: N = 12, L = 4 (3 elements per lane)
Input: [e₀ e₁ e₂ e₃ e₄ e₅ e₆ e₇ e₈ e₉ e₁₀ e₁₁]
Lane: 0 1 2 3 0 1 2 3 0 1 2 3
┌─── Stage 1: レーン毎の規範的な木構造 ───┐
Lane 0 Lane 1 Lane 2 Lane 3
op op op op
/ \ / \ / \ / \
op e₈ op e₉ op e₁₀ op e₁₁
/ \ / \ / \ / \
e₀ e₄ e₁ e₅ e₂ e₆ e₃ e₇
↓ R₀ ↓ R₁ ↓ R₂ ↓ R₃
┌─── Stage 2: レーン間の規範的な木構造 ──┐
op
/ \
op op
/ \ / \
R₀ R₁ R₂ R₃
↓
result
二項演算opの評価において、上記木構造が指定するようにオペランドの順序は固定されます。initが提供される場合はinitは左オペランド(op(init, R_all))です。一方、opの相対的な評価順序は規定されません。上記規範的な式の評価結果と一致する限りにおいて、実装は部分式(各レーン内およびレーン間でのop評価)を任意の順序で評価することができ、それによってopに副作用がある場合でもその相対順序は未規定となります。
この規範的な式構造におけるもう一つの特徴は、入力範囲の要素を各レーンに分配する際の分配方法としてインターリーブ方式を採用していることです。これは、範囲の先頭からN/L個をレーン0、次のN/L個をレーン1のようにブロック分割するのではなく、範囲の先頭要素から順番にそれぞれのレーンに割り振っていく形になり、連続する要素はそれぞれ異なるレーンに割り振られることになります。
このインターリーブ方式が選択されたのはパフォーマンス上の利点のためです
- SIMDレジスタへのデータのロードと直接的に対応する
L個の連続した要素を単一のベクトルロード命令で読み込むことで、各要素がL個のレーンに分配される- ブロック分割ではレジスタを埋めるためにメモリ上の離れた
L箇所からギャザーロードを行う必要がある
- 要素数
NがLの倍数でなくても、すべてのレーンの要素数がほぼ均一になる(ダミー要素をカウントすると完全に均一になる)- 最大でも1要素の差の範囲内ですべてのレーンの個数が均等化され、レーン間で偏りが生じない
- これにより各レーン間でIterative Pairwise木の形状が完全に一致し、レーン内でのIterative Pairwise集計処理においてSIMDのロックステップ実行を極めて効率的に処理できる
- Stage1ではシャッフルやギャザー操作が不要
- レーン内での集計時はレーン間を跨ぐようなデータ交換が不要
- Stage1ではメモリからロードしたベクトルを垂直加算することで各レーンの中間結果
R[0..L)を構築できる - Stage2の水平加算はこのループ終了後に一度だけ実行すればよい
L = 4の場合のStage1垂直加算のイメージ
Memory: E[0] E[1] E[2] E[3] | E[4] E[5] E[6] E[7] | E[8] ...
└─── Vector 0 ────┘ └─── Vector 1 ────┘
Iteration 1: Load V0 = {E[0], E[1], E[2], E[3]} → Acc = V0
Iteration 2: Load V1 = {E[4], E[5], E[6], E[7]} → Acc = Acc + V1
...
Final: Acc = {R_0, R_1, R_2, R_3}
すなわち、インターリーブ方式はSIMDの特性に強く合致しており、規範的な式によって決定性を保証しながらHWを最大限活用した高速化も目論むものです。
この提案は並行化可能な規範的な式構造を定義することによって実行戦略によらない範囲集計処理の決定性を保証しようとするものです。その際に、二項演算opに対して代数的な要件(結合法則/交換法則の成立、単位元の存在など)は課されません。opに求められるのは、型Aの2つの引数で呼び出し可能であることとその結果がAに変換可能であることです。opの評価回数は0 < Nの場合に正確にN - 1回、初期値がある場合はO(N)回になります。
また同様に、算術演算(特に浮動小数点演算)の決定性に関して何かしらの機能追加や変更を行おうとするものでもありません。
また、この提案自体はこのような設計の方向性が受け入れられるか、このことを問題として改善していくつもりがあるかなどを委員会に問うためのものであり、継続するとしても抽象的な式構造についての規範を定義することに注力する予定で、この変更のためのAPIの修正などは別の提案で行っていく予定です。そのため、レーンパラメータLをどのようにアルゴリズムに指定するのか、アルゴリズムは既存のものに対してどのように追加・拡張されるのかなどはまだここでは検討されていません。
この提案は非常に長いですが、提案のほとんどの部分は関連するインフォメーションや実装者/数値計算専門家向けのappendixが占めています。ここで説明していることはおおむね1~5章で書かれていることです。興味があれば他の部分を見てみると面白いかもしれません。
P4019R0 constant_assert
コンパイル時にtrueに評価される必要があるアサートの提案。
コンパイラのオプティマイザは最適化に利用するためにプログラムコードに対して様々な事を証明する能力があります。それはある種の静的解析ツールの持つ能力に近いものであり、この能力を活用するとコンパイル時により多くの事を検証することができる可能性があります。
この提案では、コンパイラのオプティマイザの持つその能力を利用するためのアサート、constant_assert()を提案しています。
constant_assert(expr)は、exprがデッドコードではなく、定数畳み込みされておらず、かつコード生成段階でtrueと評価されない場合にill-formedとなります。
用途としてはオプティマイザの検証(コード上でオプティマイザの動作をチェックを行う)や、検証済みの[[assume(expr)]](assume(expr)の代わりに使用することで、未定義動作等のリスクなく同じ効果が得られる)等が挙げられています。
constant_assertは、static_assertよりもより多くの事をコンパイル時にチェックでき、assertのように実行時のオーバーヘッドや終了リスクが無く、安全に(仮定が誤っている場合の未定義動作なく)assumeと同じ効果を得ることのできる、ニッチではあるものの既存のアサーションとは異なる領域をカバーするアサーションです。
double f(double d) { static_assert(d != 0.0); // dは定数式で使用できない assert(d != 0.0); // チェックが実行時に行われ、`false`ならプログラムが終了される [[assume(d != 0.0)]]; // dが0だと未定義動作 constant_assert(d != 0.0); // dの値がコンパイル時(コード生成時)に0に評価されない限りコンパイルエラー ... }
constant_assertの条件式は通常の定数式として評価可能である必要はなく、オプティマイザが最適化の結果としてコンパイル時に(定数畳み込みなどによって)評価できればエラーにはなりません。上記の例では、constant_assert()がエラーにならない場合d != 0.0が満たされることが静的に証明されていることになります。
constant_assert(expr)はGCCの提供する__builtin_constant_p(expr)を使用することでユーザーコードで実装可能なようです。
[[gnu::always_inline]] constexpr void constant_assert(bool cond) { if (not __builtin_constant_p(cond)) { [] [[gnu::error("constant assert- not constant")]] () { }(); } if (not cond) { [] [[gnu::noinline, gnu::error("constant assert- false")]] () { }(); } }
__builtin_constant_p(expr)は最適化レベルによってその動作が変化します。また、constant_assert(expr)が標準化された場合はexprをコンパイル時に解決できるかどうかはコンパイラとそのオプティマイザ、および最適化レベルによって変化します。この提案ではそれを許容し、コンパイラ間の差異が可視化されることで競争が促される可能性があるとしています。
P4020R0 Concerns about contract assertions
P2900のContracts機能に関する賛否についての標準化の観点からの中立的な意見書。
この文書では、関数に対する契約やContracts機能の必要性・目的についての簡単な説明を行い、P2900のContracts機能の主張とそれへの反対意見について、標準化という観点からなるべく中立的に筆者の方の意見としてまとめられています。
最終的にこの文書では、無視できない契約アサーションに関して次の事を推奨しています
- 何もしない(P2900に機能追加しない)
- Contracts機能をC++26から削除し、ホワイトペーパー等別の出荷経路を検討する
2026年3月に行われた全体会議において、C++26にP2900のContracts機能が維持されることとそれに対して機能追加を(C++26フェーズでは)行わないことが決定されています。
P4021R0 compile_assert - an assert that evaluates at compile time
コンパイル時に真偽が判定される必要のあるアサーションの提案。
この提案は、P4019R0 constant_assertとかなり近いものを提案しています。この提案のcompile_assertもコンパイラの最適化器の能力まで活用して指定された条件式がfalseにならないことをコンパイル時に検査するものです。static_assertとの違いは式が定数式である必要がない点です。
この提案のcompile_assertは次のように定義されています
コンパイラが失敗経路(式が
falseに評価される経路)が到達可能であると判断する場合、プログラムはill-formed
コンパイラが到達可能なすべての経路がこの条件を満たすことを証明できる場合、そのプログラムはwell-formed
提案されている構文はメッセージ指定の有無による2種類です
compile_assert(expr);compile_assert(expr, message);
提案文書より、サンプルコード
static void log_message(const char * p) { compile_assert(p != nullptr, "check not null"); printf("%s\n", p); } void output_string(const char * ptr) { // NB. The following lines are needed //if(nullptr != ptr) { log_message(ptr); } }
int main() { const int buf_size = 4; char buf[buf_size]; for (int i = 0; i != 5; ++i) { // will fire, as out of bounds compile_assert(i < buf_size, "check buf index"); buf[i] = 3; } }
提案文書より、ライブラリレベル実装例
#if defined(__OPTIMIZE__) && defined(__ENABLE_COMPILE_ASSERT__) #define COMPILE_ASSERT_ACTIVE 1 void _stop_compile() __attribute__ ((error("'compile_assert error detected'"))); /** * @def compile_assert * @brief Macro for compile-time assertion in optimized builds. * @param expression The compile-time condition to be checked. * @param message A description of the assertion (unused). */ #define compile_assert(expression, message) \ do { \ if (!(expression)) { \ _stop_compile(); \ } \ } while (0) #else #define compile_assert(condition, description) #define COMPILE_ASSERT_ACTIVE 0 #endif
この提案はP4019R0ともどもEWGIにてリジェクトされています(理由は不明です)。
P4022R0 Remove try_append_range from inplace_vector for now
std::inplace_vectorの.try_append_range()を削除する提案。
std::inplace_vectorの.try_append_range()は他のコンテナ型の.append_range()に対応するもので、引数の範囲を現在のコンテナに追加しようとするものです。std::inplace_vectorのキャパシティが限られていることによって、その操作は失敗する可能性があるため.try_append_range()という名前になっています。
namespace std { template<class T, size_t N> class inplace_vector { public: // ... template<container-compatible-range<T> R> constexpr ranges::borrowed_iterator_t<R> try_append_range(R&& rg); // ... }; }
しかし、.try_append_range()にはいくつか問題点が報告されています。
1つは、その操作においての失敗が自明ではない事です。
1つの要素を挿入しようとする場合は結果は挿入できるかできないかの2パターンですが、複数の要素を挿入しようとする場合はすべて挿入できたか、すべてできなかったか、一部だけができたか、のざっと分けても3パターンあります。
この動作の選択は設計選択ですが.try_append_range()という名前からではその動作が明確ではありません。現在の規定は部分挿入を許容しますが、それは失敗と言えないかもしれません。部分挿入を許容する場合、別の名前を選択したほうが良い可能性があります。
もう1つは、戻り値型についてです。
.try_append_range()は挿入できなかった最初の要素へのイテレータを返します。しかしこれは扱いづらいことが指摘されており、代わりに未挿入要素のsubrangeを返すことが提案されています(P3981R0)。しかしこれにも問題があり、subrangeが空の場合にtrueを返すoperator boolを備えていることで、他のtry系の関数と動作が変わってしまいます。
if (v.try_push_back(elem)) { // 挿入に成功した } if (not v.try_append_range(rg)) { // 挿入に成功した } if (v.try_append_range(rg)) { // rgの要素のうち少なくとも一つは挿入に失敗した }
戻り値型を変えるとしても、1つ目の問題のように名前から動作が明確ではない状態でbool値あるいはそれに変換可能な値を返すと誤解を招く可能性があります。そのため、誤解の余地のないすべての情報を含むような戻り値を返すことが望ましいです。
template <class R> struct append_some_return { // subrange into the elements inserted into the inplace_vector subrange<iterator> inserted; // subrange into the remaining elements that could not be inserted borrowed_subrange_t<R> remaining; }; template <container-compatible-range<T> R> constexpr append_some_return<R> append_some(R&& rg);
しかし、これは大きな設計変更となり、C++26の土壇場で変更すべきではありません。
この提案は、これらの問題の解決とその議論の時間を得るために、C++26ではstd::inplace_vectorの.try_append_range()を削除することを提案しています。
急いで解決しようとせず、C++29のサイクル内で設計検討を行うことでより良い解決策を目指す方針です。
この提案は2026年3月の全体会議でC++26に向けて採択されました。
P4023R0 Strategic Direction for AI in C++: Governance, and Ecosystem
C++標準化においての生成AIへの対応方針の提言文書。
主に
- 標準化プロセスにおける生成AIの利用についてのガイドライン
- 生成AIのためのC++データセット/ツール・ガイドラインについてのコミュニティへの行動喚起
の2本の柱について提言しています。
これはC++ Direction Paper(P2000/P5000)に対する提案です。
P4024R0 Guidance on Building Consensus and Converging Proposals
提案文書を書く際のガイダンスの提言文書。
これはWG21のメンバに向けたもので、提案文書を書く際に合意形成を促進し、より早い段階でより広い分野の関係者からのフィードバックを得られるようにするためのガイダンスについて提案されています。
この目的は、提案がある特定のドメインや組織に固有なものになったり、レビュープロセスの遅い段階で設計に対する異論や議論が巻き起こることなどを防止し、合意形成とレビュープロセスの促進のためです。
これはC++ Direction Paper(P2000/P5000)に対する提案です。
P4025R0 The SG19 Priority List for C++29/32
SG19(機械学習Study Group)のC++29/32に向けた優先作業リスト。
機械学習関連コードの実装のためにC++は良く使用されているものの、標準ライブラリの機能ではそのほとんどの部分をカバーできていません。それによるエコシステムの分断を防止するために、基本的な機能について標準ライブラリで提供しておくことを目指してC++29以降で作業を進めていく必要があります。
この文書は、それにあたっての優先項目のリストです。
- C++29の最優先事項
- データサイエンス基盤(
std::data_frame)およびCSV読み込み機能- 提案や作業はなし
- コア数学機能(
std::linalg/mdspan)、mdarraystd::linalg/mdspanはC++26までに導入済み、mdarrayは作業中
- グラフデータ構造
- 開発中(SG19)
- 統計計算
- 開発中(SG19)
- データサイエンス基盤(
- C++29以降の戦略的方向性
- 自動微分とJIT
- リフレクションも活用して、微分をコンパイル時にもサポートする
- JAXと同等の機能(コンパイル時の数学的最適化)を手に入れる
- 低精度演算
std::float8_t(FP8)とstd::int4_t、重み圧縮のためのnibble型のサポートが必要
- スマートテンソル
- メタデータ認識型
mdspan(名前付きテンソル) - コンパイル時に文字列または型によってテンソルの次元に名前を付けて扱うことで、テンソルの次元の取り違えを防止する
- メタデータ認識型
- 自動微分とJIT
特に、C++がPythonの単なるバックエンドに留まらないようにすることを意識しているようです。
P4026R0 Core Issue 3123 "Global lookup for begin and end for expansion statements"
template forがrangeを判定する条件についてのイシューの解説スライド。
template for(展開ステートメント: expansion statements)はコンパイル時にタプルやrangeに基づくイテレーションによって文を生成することのできるfor文で、主にリフレクションの文脈で使用されます。
template forは範囲forによく似ていますがそれとは異なり、タプルや構造化束縛可能なものもイテレーションすることができ、規格では3種類の対象が指定されています。その中にはrangeも含まれており、expansion-iterableな式(expansion-iterable expression)として指定されています。
式Eがexpansion-iterableな式であるとは、次の事を満たす場合です
- 配列型ではなく
- 範囲
forの場合と同じようにbegin-exprとend-exprを決定したうえでbegin-exprとend-exprがそれぞれE.begin()とE.end()の形式である、またはbegin(E)とend(E)のADLがそれぞれ少なくとも一つの関数または関数テンプレートを見つける
template for(auto v : E) { ... }のように記述されているときに、Eがexpansion-iterableな式であればそれをrangeとしてイテレータによってイテレーションしようとします。
ここで問題があるのは、expansion-iterableな式を判定する際に式EについてADLを用いてbegin/endを探しに行く際にその有効性まで見ていない点です。すなわち次の一文です
begin(E)とend(E)のADLがそれぞれ少なくとも一つの関数または関数テンプレートを見つける
「少なくとも一つの関数または関数テンプレートを見つける」とはADLで候補が何か見つかれば良く、それが呼び出し可能であることをチェックしていません。これによって、std::tupleを指定するtemplate forはそれをexpansion-iterableな式として判定してしまいます(std名前空間にはフリー関数のbegin/endがあるため)。
std::tupleは別のルール(構造化束縛可能な型)で処理されるべきであり、これは明らかに間違っています。
Core Issue 3123 はこれの報告と解決を行うとしているもので、そこでは先ほどのADLの場合の判定でその呼び出しの有効性までチェックするようにしています。
しかし、それを行うとテンプレートのインスタンス化が引き起こされます。テンプレートのインスタンス化はimmediate context(SFINAE可能なコンテキスト)外でエラーとなる可能性がある他、テンプレートのインスタンス化が起こることが非局所的な影響を及ぼす(リフレクションによって観測可能になる)可能性があるとして懸念が示されているようです。
このスライドはこの懸念に対応するもので、リフレクションによるインスタンス化が可視ではないことやエラーが起こる場合はtemplate forと関係ないこと、また範囲forやtemplate forの非rangeケースでもインスタンス化エラーによるコンパイル失敗は起こりうることなどを示しています。
結局このスライドでは、Core Issue 3123で提示されている解決策をそのまま受け入れることが最善である(インスタンス化は発生するがそれほど問題にはならない)としています。
このスライドの主張およびCore Issue 3123の解決策は受け入れられたようで、2026年3月の会議でC++26に採択されています。
P4027R0 2026-02 Library Evolution Polls
2026年2月に行われるLEWGにおける投票の予定。
今月の投票はNBコメントの解決に関連したもののみです
- US 90-197
- P3739R4の一部
- US 67-118, PL-012, GB 03-119, DE-120, and FI-121
- P3842R0: A conservative fix for constexpr uncaught_exceptions() and current_exception()
- US 135-216 and US 136-217
- FR-030-310
- P3936R0
- US 254-385
- P3980R0 section 3.1 (wording change A)
- US 253-386, US 255-384 and US 261-391
- P3980R0の一部
- US 68-122, US 150-228, GB 08-225, PL-006, and US 149
- P3981R0
P4029R0 The SG14 Priority List for C++29/32
SG14(低遅延・ゲーム・金融・組み込み Study Group)のC++29/32に向けた作業リスト。
C++が安全性の向上に力を入れていることは理解し支持しつつも、SG14の担当領域においてはゼロコスト抽象化・予測可能な遅延・決定論的な実行などの重大な制約があり、C++の今後の機能でも引き続きこれらの点は重要視されています。
それを踏まえて、この文書はC++29以降にそれらの制約の下で標準機能の設計にどう関与していくかを説明しています。
- 安全性
- Safe C++ / Profilesの採用を支持する
- 安全性が最重要視されていることは認めつつも、安全のための機能が実行時オーバーヘッドやレイテンシの増大を引き起こさない事を保証する必要がある
- SG14では、決定論的例外処理を安全性分野への具体的な貢献として位置付けている
- CPUバウンドI/Oおよびネットワーク処理
- ネットワーク処理をP2300(
std::execution)から分離する- SG14はネットワークライブラリをP2300上に構築すべきではないと提言している
- P2300で要求されるアロケーションパターンは低遅延ネットワークと互換性がない
- ダイレクトスタイルI/Oを標準化する
- C++29ネットワークモデルとしてP4003(もしくは類似のダイレクトスタイルモデル)を優先する
- これはASIO/Beastのパフォーマンスとコルーチンの使いやすさを両立させ、ゼロオーバーヘッド原則を維持する
- ネットワーク処理をP2300(
- C++をゲーム開発者により使いやすくするための優先課題
- P3442R1(
[[invalidate_dereferencing]]属性)- この属性はカスタムアロケータと手動メモリ管理における静的安全性を高める
- この属性によりuse after freeバグがコンパイル時に発見しやすくなり、実行時オーバーヘッドが無い
- P3707R0 (
std::is_always_exhaustive特性)- ゲームロジックは基本的に状態駆動型であり、コンパイル時にある種の網羅性を強制することのできる機能により、処理されない状態などのロジックバグを実行時オーバーヘッドなく削減できる
- P3442R1(
- 超低遅延と高頻度取引
- ロックフリー並行処理とメッセージング
- 低遅延金融分野では、異なるスレッドがOSレベルのロックやブロック無しで通信する必要がある
- リングバッファ(P0059)
- 並行キュー(P0260)
- ハザードポインタとRCU
- C++26で導入、C++29で拡張予定
- キャッシュ局所性とメモリレイアウト
- 低遅延においては、アルゴリズムの計算量よりも予測可能なメモリアクセスの方が重要な場合が多い
- メンバレイアウト制御(P1112 / P1847)
- リロケーション(P1144 / P2786)
- ロックフリー並行処理とメッセージング
少し上のSG19のものと異なり、ここでの順序付けは優先順位というわけではないようです。
P4030R0 Endian Views
範囲の要素のエンディアン変換を行うRangeアダプタの提案。
この提案はユニコード文字列(範囲)の変換を行うユーティリティを提案しているP2728R7を補足するものです。P2728で提案されているのはユニコード文字列範囲の相互変換を行うRangeアダプタです。
P2728R7より、サンプルコード
std::u32string hello_world = u8"こんにちは世界" | std::uc::to_utf32 | std::ranges::to<std::u32string>();
to_utfNという名前でユニコード文字型毎に変換を行うRangeアダプタが提案されています。しかし、これらの変換におけるエンディアンはシステムネイティブのものに限定されています。例えば、リトルエンディアン環境でto_utf16によってUTF-16BEを生成することができません。
この提案はその需要に対応するためのもので、エンディアン変換だけを行うRangeアダプタを提案しています。この提案のアダプタとP2728のアダプタを組合わせることでエンディアンの異なるユニコード文字範囲を扱うことができます。
constexpr vector<uint32_t> utf16be_to_utf32be(const vector<uint16_t>& utf16be_data) { return utf16be_data | views::from_big_endian // ビッグエンディアンで読み出し | views::as_char16_t // char16_tに読み替え(UTF-16BEとして読む | views::to_utf32 // UTF-32(char32_t)に変換 | views::transform( // 整数に変換 [](const char32_t c) { return static_cast<uint32_t>(c); }) | views::to_big_endian // ビッグエンディアンに変換 | ranges::to<vector>(); // std::vector<uint32_t>に詰め替え }
提案しているのは次の4つです
views::from_little_endian: 要素ごとにリトルエンディアンに変換する(デフォルトエンディアンがビッグエンディアンの場合)views::from_big_endian: 要素ごとにビッグエンディアンに変換する(デフォルトエンディアンがリトルエンディアンの場合)views::to_little_endian: 要素ごとにリトルエンディアンに変換する(デフォルトエンディアンがビッグエンディアンの場合)views::to_big_endian: 要素ごとにビッグエンディアンに変換する(デフォルトエンディアンがリトルエンディアンの場合)
from_xxxとto_xxxの組は同じことをします。名前を変えて提供しているのは、上記例のようにパイプラインにおいて可読性を高めるためです。
これらのRangeアダプタは入力範囲の要素型が整数型(std::integral)である必要があり、エンディアン変換は整数値をstd::byteswapすることで行われます。
そのため、ユニコード文字専用の機能というわけではなく、ネットワークバイトオーダーの変換や一部のファイルフォーマットを扱う際など、エンディアン変換が必要となる他の場所でも使用することができます。
P4031R0 Rename system_context_replaceability namespace
system_context_replaceability名前空間の名前をリネームする提案。
std::execution::system_context_replaceabilityはparallel_schedulerに関連する名前空間で、システムが提供するparallel_schedulerをユーザーが置き換えるためのものが配置されています。
namespace std::execution { class parallel_scheduler { unspecified }; parallel_scheduler get_parallel_scheduler(); } namespace std::execution::system_context_replaceability { struct receiver_proxy; struct bulk_item_receiver_proxy; struct parallel_scheduler_backend; shared_ptr<parallel_scheduler_backend> query_parallel_scheduler_backend(); }
parallel_scheduler_backendはインターフェースであり、これを継承したクラスでシステムスケジューラを実装して、query_parallel_scheduler_backend()関数を上書き定義してそれを返すようにすることで、execution::get_parallel_scheduler()が返すparallel_scheduler実装を置換することができます。
system_context_replaceabilityという名前空間名にはsystem_contextという言葉が含まれているものの、最終的な仕様の中にsystem_contextというものは一切存在しません。これは、提案の初期の段階ではparallel_schedulerがsystem_contextという名前だったことの名残です。
しかしparallel_schedulerに変更された後でもなぜかこの名前空間名にだけsystem_contextが残り続けており、変更しようとした提案(P3804R1)もありましたがリジェクトされています。
system_contextという対象が不明なことによって、system_context_replaceability名前空間にあるものが何を置き換えているのか(置き換えようとしているのか)が分からなくなっています。
この命名は一貫性が無く、C++26をこのままにすると大惨事を招くとして、この提案では改めてsystem_context_replaceabilityの名前の変更を提案しています。
提案では次の2つの候補を提示しています
std::execution::parallel_scheduler_replaceabilitystd::execution::parallel_scheduler_replacement
筆者の方は2番目の方が若干好ましいとしていますが、どちらを主としているわけではありません。
P3804R1が他の変更も含んでいたのに対して、この提案はこのリネームだけを目指すものです。
- P3804R0 Iterating on
parallel_scheduler- WG21月次提案文書を眺める(2025年10月) - P2079R10 Parallel Scheduler - WG21月次提案文書を眺める(2025年07月)
- P4031 進行状況
P4032R0 Strong ordering for meta::info
meta::infoの値を順序付け比較可能にする提案。
^^によって得られるmeta::infoの値(リフレクション値)は==による同値比較が可能で、おおむね両辺が同じもののリフレクションになっている場合にtrueとなります。一方で、順序付け比較(<など)は提供されません。
C++26ではstd::type_orderという型の比較を行うものが導入されており、実装定義ではあるものの任意の型同士を全順序の上で比較して順序付けすることができます。また、meta::info型は構造的な型であることが規定されているため、NTTP(定数テンプレートパラメータ)に取ることができます。
すると、クラステンプレートを通してmeta::info型の間接的な順序付け比較が可能になります。
template <std::meta::info> struct S {}; // intとグローバル名前空間のどちらが先に順序付けられる? constexpr bool b = std::type_order<S<^^int>, S<^^::>>::value;
これは現在のところ未規定であり、何かしらの順序の定義が必要になる可能性があります。
std::type_orderの動機付けの一つであるコンパイル時の型のsetの様なものをmeta::infoで行えると便利である可能性があり、このようなハックを通してサポートするのではなく直接的にサポートするとより便利です。
template <typename ...> struct type_set_impl; // std::setの比較には<比較が必要 template <typename ...Ts> using type_set = [:substitute(^^type_set_impl, std::set{^^Ts...}):];
この提案は、meta::info同士の比較を行う三方比較演算子(<=>)を追加し、実装定義かつstd::type_orderと一貫した順序かつ全順序(std::strong_ordering)の上での比較を行えるようにしようとするものです。
P5000R0 Direction for ISO C++29
C++29の標準化作業に当たっての方向性を示す文書。
これはDirections Groupによって書かれたもので、C++29のサイクルにおける目標と優先事項を示すものです。
- Tier0: 安全性
- 言語とライブラリの双方で、未定義動作を解消しC++の安全性を高める作業を最優先で進める
- プロファイル
- ライブラリのセキュリティ強化
- 言語とライブラリの双方で、未定義動作を解消しC++の安全性を高める作業を最優先で進める
- Tier1: メンテナンスリリース
- C++26はメジャーリリースであり大きな機能がいくつも追加されているため、これらの機能が展開されるにつれて現実とのギャップが見つかることが想定される
- C++29はメンテナンスリリースとして位置付けてC++26機能のメンテナンスに努め、その作業に遅延をもたらすような提案を無理に通すのは避ける
- Tier2: C++26に間に合わなかった機能
- パターンマッチ
- ネットワーク
今月のP4024R0やP4023R0なども、この方向性に関連したDirections Groupによる文書です。