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

文書の一覧

全部で81本あります。

もくじ

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の提案。

以前の記事を参照

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

  • 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::tasksender/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チェーンおよびそれに接続されたreceivercoroutine_handle<>を持っていません。これは当然で、sender/receiverモデルにおいてはsenderreceiverも単なる構造体でありその合成は構造体のネストで行われ、コルーチンフレームの様な追加の状態を必要としない(スタックで完結する)ため、coroutine_handle<>の様なハンドルを必要としないためです。

コルーチンをsenderとして扱う場合、そこで使用されるreceiverawaitable-receiver)はコルーチンハンドルへアクセスすることができます。しかし、この場合でもsender/receiverプロトコルの制約(完了関数が戻り値を返さない)によってそのコルーチンハンドルを適切に返す方法が無いため、対称転送をサポートできません。

簡単にまとめると次のようになっています

  1. senderアルゴリズムはコルーチンではなく構造体であるreceiverを作成し、これにはコルーチンハンドルがない
  2. コルーチン上に構築されるreceiverであってもset_value()voidを返す
    • receiverの持つコルーチンハンドルを適切に返す方法がない
  3. .await_suspend()は合成レイヤーでもsender/receiverプロトコルでも提供されない値を返すことができない

いずれにしても対称転送に必要なcoroutine_handle<>がありません。

ただし、これはstd::executionモデルの欠陥を表すものではありません。std::executionはヘテロジニアスコンピューティング向け(典型的にはGPU)に設計されており、その領域では対称転送がメリットをもたらさないためそれが考慮されていません。これが問題となるのは、コルーチンと連携しようとした場合のみです。

これはstd::executionの設計の不備やexecution::taskの機能不足などではなく、sender/receiverモデルの設計選択から生じるアーキテクチャ上のトレードオフであるとこの文書は強く指摘しています。

文書では、この問題の解決のためには次の3つの方向性のいずれかを取ることができるとしています

  1. 既知のコルーチン型に対してreceiver抽象化をバイパスするドメイン固有のカスタマイズ方法を提供する
  2. 末尾呼び出しを保証する言語機能を提供する
  3. コルーチン間の合成を直接awaitableプロトコルで処理(senderとして扱わない)し、必要な境界でのみsenderモデルを使用するtask型を採用する

この文書はこれらの事を提案しているのではなく、このようなトレードオフがあることを議論の際に認識しておくことを促すものです。

P2728R11 Unicode in the Library, Part 1: UTF Transcoding

以前の記事を参照

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

  • コード単位アダプタにおける配列の拒否に関する記述を修正
  • バイトオフセットとエンディアンの処理例を追加

などです。

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代入演算子を禁止する提案。

以前の記事を参照

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

  • ambitious approach の実装と展開に関する検討事項を追加

などです。

P2964R2 Allowing user-defined types in std::simd

std::simdのデータ型としてユーザー定義型を使用できるようにする提案。

以前の記事を参照

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

  • カスタマイズ重視のアプローチから特性ベースのアプローチへ変更
  • カスタマイズに関する項目を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

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

以前の記事を参照

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

  • 対象読者の更新
  • いくつかの章を大幅に簡略化し、コンテンツの重複を削除
  • コンパイラエクスプローラーの例をmp-unisの最新版に合わせて更新
  • Creating distinct quantity kinds with is_kind セクションを追加
  • Safety セクションを再構成し、6つの異なる安全性レベルを中心に構成
  • AssociatedUnitコンセプトを削除
  • PrefixableUnitコンセプトを追加
  • non-ideal alternativesの議論を0リテラルとの直接比較に置き換え
  • named_constantのサポートを追加
  • pi mag_constantpi_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

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

以前の記事を参照

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

  • [[assume]]サポートを削除

などです。

P3411R5 any_view

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

以前の記事を参照

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

  • approximately_sizedをサポート
  • view_interfaceから派生するようにする

などです。

P3440R2 Add mask_from_count function to std::simd

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

以前の記事を参照

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

  • 関数名を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

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

以前の記事を参照

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

  • §4. Possible implementation に高速な実装例を追加
  • コンパイラ実装が純粋なライブラリ実装よりもすぐれている理由について追記
  • @llvm.clmulがLLVMに追加されたことを言及
  • wide_resultoperator<=>をhidden friendに戻した

などです。

P3666R3 Bit-precise integers

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

以前の記事を参照

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

  • 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に関連する文字の判定・処理関数群を提供する提案。

以前の記事を参照

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

  • §3.5. Case-insensitive comparison functions における設計議論を拡充
  • is_asciiの使用をascii_isに置換
  • ascii_is_punctuationの説明からu32string_viewへの言及を削除
  • ASCII-compatibleの定義を改訂

などです。

P3724R3 Integer division

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

以前の記事を参照

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

  • §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の実装自由度を制限する提案。

以前の記事を参照

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

  • §2.1. What the standard says でdata()について言及
  • §3. Motivation with implicit object creation and providing storage を拡張
  • data()の仕様を簡素化

などです。

P3816R2 Hashing meta::info

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

以前の記事を参照

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

  • “Ordered maps and sets”セクションを“Motivation”に統合
  • 注釈の例を追加

などです。

P3822R1 Conditional noexcept specifiers in compound requirements

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

以前の記事を参照

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

  • 実装とhistoryに関するセクションを追加
  • サンプルコードを追加
  • if/while/switchの条件との曖昧さを避けるためにconditionconstant-expressionに置き換え

などです。

P3844R3 Restore simd::vec broadcast from int

P3844R4 Reword [simd.math] for consteval conversions

simd::vecに対する数学関数の呼び出しにおいて、引数のsimd::vecへの変換を呼び出し側で行うようにする提案。

以前の記事を参照

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::vecconsteval変換/コンストラクタによって変換可能である場合に、数学関数内部で変換を行うと非constexpr/consteval関数内でconsteval関数を呼び出そうとして引数が定数式ではないことによってエラーを発生させるというものです。

例えばstd::pow(x, 3)xの型によらずx³を計算しますが、xsimd::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(定数テンプレートパラメータ)として使用可能であるかを調べる関数の提案。

以前の記事を参照

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-evenroundTiesToEvenへ変更
  • Error handling セクションを追加
    • 提案する関数のエラー処理方法は既存の数学関数と同じ方法で扱う
  • 機能テストマクロの追加
  • Design considerations セクションを追加

などです。

P3874R1 Should C++ be a memory-safe language?

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

以前の記事を参照

このリビジョンでは、R0の公開後の議論においてWG21内でメモリ安全な言語の定義が確立されていない事が判明したことから、その定義を提供するとともにC++がメモリ安全な言語を目指すべきかどうかという問いに答えを出し、明確な目標を提案することを目指すものに方向性が変化しています。それに伴ってタイトルの変更や著者の追加が行われ、内容も多くの部分が再構成されています。

この提案ではメモリ安全な言語を

  • 未定義動作がないこと
  • 抽象マシンのセマンティクスに違反するあらゆるメモリ操作に対して、自動的に強制される保証が存在すること

と定義しています。

この提案ではC++がこのメモリ安全な言語となることを達成するためのアプローチとして、subset-of-superset(スーパーセットのサブセット)戦略を提案しています。この戦略は次の要件を満たすものです

  1. サブセットは構文的に明示的かつ明確に定義されており、この範囲では未定義動作が発生することは無く、アクセス可能なコードもこのサブセット内に限定される
  2. サブセット外のコードは未定義動作を示す可能性があるが、明確に定義されたサブセット内のコードに直接アクセスすることは可能

この取り組みの参考になる比較対象としてはRustとCircleが挙げられています。

この提案の方向性は、現在のプロファイルや暗黙の契約アサーションなどの方向性とは異なるものを示しており、それらの取り組みを補完するものとしつつも、有用かつ体系的に未定義動作の無いサブセットを提供するスーパーセット機能と組み合わせなければC++をメモリ安全な言語にすることはできないとしています。すなわち、C++29に向けた現在の主要な取り組みとは異なる道筋を示すものです。

P3876R1 Extending support to more character types

std::from_chars/std::to_charschar以外の文字型による文字列をサポートするようにする提案。

以前の記事を参照

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

  • 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::simdinteger-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>はサイズNVに基づくsimd::vec/マスク型を返すもので、その際にNVに基づくABIタグ型の導出・変換を行うものです。Abis...[0]はパラメータパックに対するインデックス指定であり、resize_t<N, V>Vに対して引数の最初のABIタグ型を使用するようにしています。

LWG Issue 4414は4470が変更する箇所と同じ個所を変更しているため(かつ報告者が同じなため)、この提案に組み込まれています。

LWG Issue 4414では、simd::resizeresize_t)やsimd::bindで使用されていたdeduce-abi-tの動作がサポート外の入力に対して規定されていない事でsimd::resizesimd::bindの動作も不定になっていたことや、simd::resize/simd::bindにおけるdeduce-abi-tの使用方法がその定義と一貫していなかった事が問題として報告されています。

この提案では、deduce-abi-tの規定を明文化してサポート外の入力に対しては未規定の型を示すとしたうえで、その使用箇所における使用方法を変更後の定義と整合させています。

まとめると、この提案では

  1. LWG4470: integer-fromの定義の不備
  2. LWG4518: simd::catの戻り値型のABIタグの意図しない変更が発生しうる
  3. LWG4414: deduce-abi-tの定義の不備と使用方法の不一致

の3つの問題を協調的に修正しています。

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

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アルゴリズムの改善提案。

以前の記事を参照

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

  • 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に転送されています

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

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自体は実行時にキャストを行うものでもあるものの、このことは本来コンパイル時にその型(ToFrom)から静的に検査できるものです。現在のところこれは要求されておらず仕様の空白(未定義)となっているため、コンパイラは必ずしもこれを警告しません。

また、このことは_BitInt型(P3666で提案中)のキャストにおいて頻発することが予想されます。

この提案はこの問題を解決するために、2つのアプローチを提示しています。

  1. std::bit_castのこのような使用(変換元がパディングビットを含み、そのビットが宛先で値表現に含まれるような変換)をill-formedとする
    • パディングビットを不定値ではなく0として扱ってキャストするstd::bit_cast_zero_paddingを追加する
      • これはパディングビットの扱い以外はstd::bit_castと同じ動作をする
  2. 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_tstd::simd::resize_tstd::simdのためのリバインド(要素型の変更)とリサイズ(要素数の変更)を行った型を求めるためのユーティリティです。

この提案ではさらに、ベクトル型をsimd::vecに限定せず、std::arraystd::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)な契約とこの提案では定義します

  1. 呼び出し元に制御を戻す
  2. 主要な動作とは異なる動作をする

誠実な契約の定義は二次的動作によってのみ定義され、主要な動作は無関係です(例えば主要な動作が呼び出し元に制御を返さなくても構わない)。

誠実性の判定例

  1. 負の入力に対してプログラムを終了するfloat sqrt(float)は呼び出し元に制御を戻さない二次的動作を持つため、誠実ではない
  2. 事前条件付きのfloat sqrt(float)は呼び出し元に制御を戻さない二次的動作を持つため、誠実ではない
  3. void abort()の通常のもの(名前の通りの動作をするもの)は、二次的動作を持たないため、誠実な契約を持つ
  4. pre <quick_enforce> (false)を持つvoid abort()は、主要な動作が存在せず二次的動作が呼び出し元に制御を戻さないため、誠実ではない

この誠実性はこの提案の最終的なカテゴリではなく、そのうちの2つをまとめるためのカテゴリになります。

この提案においては、広い契約は4つのサブカテゴリに分類され、広い契約とは別に2つの契約分類は追加されます。

  • 広い契約
    • 誠実な契約
      • 自由な(Free)契約
      • 明確な(disambiguating)契約
    • 損失のある(Lossy)契約
    • 切断されている(Disconnecting)契約
  • 狭い契約
  • 病的な(pathological)契約
  • 無条件に堅牢化された(Unconditionally hardened)契約

追加されるのは次の6種類です

  • 広い契約
    • 自由な(Free)契約
      • 二次的動作がない
    • 明確な(disambiguating)契約
      • 誠実な契約かつ、二次的動作が主要な動作と重ならない
      • 呼び出し元は呼び出し結果からその動作が主要な動作か二次的動作かを区別できる
    • 損失のある(Lossy)契約
      • 誠実な契約かつ、二次的動作が主要な動作と重なる
      • 呼び出し元は呼び出し結果からその動作が主要な動作か二次的動作かを区別できない
    • 切断されている(Disconnecting)契約
      • 二次的動作が呼び出し元に制御を返さない
        • プログラム終了や無限ループなど
  • 病的な(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点を提案しています

  1. std::constant_wrapper[]()では、ラップ対象の型の演算子を直接呼び出すようにする
  2. std::constant_wrapper[]()staticメンバとして定義する
  3. std::constant_wrapper[]()においてINVOKEを一貫して使用するようにする
  4. 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);

  };
}

そのうえで、呼び出し方法は次のように決定されます

  1. すべての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...)>{}を返し
  2. それ以外の場合
    • 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点の変更を提案しています

  1. コルーチンフレーム確保用と、子senderに提供される環境用のアロケータを分離する
    • taskの環境から子senderに転送されるアロケータは、taskに接続されたreceiverの環境から取得したものであるべき
    • taskのコンストラクタで受け取ったアロケータはコルーチンフレームの確保に使用する
    • どちらのアロケータもプロミス型に保存する必要はないため、保存しないようにする
  2. アロケータ引数の位置の変更
    • 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_vectorstd::optional<T&>とほぼ同時期に並行して作業されていたため、その採用はほぼ検討されていませんでした。そのため、NBコメントでこれらの関数の戻り値型をstd::optional<T&>にすることが指摘されています。

それらのNBコメントも受けて、この提案ではこれらの関数の戻り値型をstd::optional<T&>に変更することを提案しています。

利点として次の事を挙げています

  1. .push_back()T&を返すため、.try_push_back()std::optional<T&>を返すことは本質的に正しい選択
    • これは失敗を許容する関数の一般的な構造
  2. T*には多くのセマンティクスがあるが、ここで必要なセマンティクス(単一オブジェクトへの参照か、何も返さない)はstd::optional<T&>がもつ単一のセマンティクスそのもの
  3. T*で利用可能なAPIはほとんどこのケースで適切ではない一方で、std::optional<T&>のAPI(モナディック関数など)は有用性が高い
    • パターンマッチング機能は検討中であるものの、そこではstd::optionalとポインタ型とでマッチング方法が異なる
  4. 標準ライブラリの堅牢化モードにより、*->はアクセスチェックが行われる
    • ポインタにはそのサポートはない

また、同じく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;
}

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つの解釈があります。

  1. 入力のmdspanに対する指定
    • 上記の通り
    • この場合、出力mdspanの要素数は1 + (extent - 1) / stride
  2. 出力のmdspanに対する指定
    • つまり、offsetは無視して、extentstrideは出力の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_slicefirst, 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_sliceextent_sliceにリネームすることを提案しています。

結局この提案では

  1. strided_sliceextent_sliceにリネームし、extent, strideを出力について指定するように意味を変更する
  2. range_sliceの新規追加
  3. 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::vecstd::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つの変更を提案しています

  1. std::simd::vec<T, native-abi<T>>が配列と同様のレイアウトを取ることを規定する
  2. レイアウトの保証を確認する型特性クエリを追加する
    • 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::executionsender/receiver)のモデルがネットワークライブラリ(I/O)には向いていないのではないか、という主張がなされていますが、この文書はそこから参照されているコルーチンベースのI/Oライブラリの設計について解説しています。

それらの文書も含めてこの文書の出発点は、「C++20コルーチンを直接I/O処理に使用して、言語が提供する機能を観察する」という指針の下でユースケースを重視した開発から生まれたライブラリ実装の経験を元にしています。したがって、この文書や後続の文書で主張されていることは理論上だけのものではなく、実践を通して実証されたパターンがベースになっています。

この文書で解説されているライブラリはcppallianceのcapyという参照実装で既にある程度実装されており、これを使用する派生ライブラリも実装されています(ただし、実用状況は不明です)。cppalliance/capyはC++の標準ネットワークライブラリのための基盤機能となる事を目標にしており、この文書はそのための最初の一歩でもあります。

解説しているライブラリの標準化のための文言も含まれています。

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つのオプションを提示しています

  1. CWG 1395の変更をrevertする
    • [temp.deduct.partial]/11 にある、順序付けが同等である場合にパックの無い方を優先する規定は残す
  2. CWG 1825の例が曖昧ではなく#2を選択するように調整する

提案ではオプション1を推しているようで、文言はそちらのみが提供されています。

P4005R0 A proposal for guaranteed-(quick-)enforced contracts

終了セマンティクスが保証された契約アサーションの提案。

この提案はP3911で提案されているような無視できず失敗時に終了することが保証されているアサーションを、P3911とは異なり全く別の構文によって追加しようとするものです。

この提案の内容は次の点です

  1. 関数宣言に関数本体の実行前に必ずtrueに評価される必要のあるエントリ条件を指定するentry_condを追加する
  2. 関数宣言に関数本体のリターン時に必ずtrueに評価される必要のあるリターン条件を指定するreturn_condを追加する
  3. mandatory_assert文を追加する

これらのいずれのアサーションにおいても、条件式がtrueに評価されない場合に違反ハンドラ経由(enforce)か直接(quick enforce)プログラムが終了されます。

この提案のアサーションは、例外の変換やconst化などの変換を全く行いません。また、P3910R0で提案されているようなセマンティクスとマングル名を対応させる方法を採用することも推奨されています。

P4006R0 Transparent Function Objects for Shift Operators

シフト演算子に対応する関数オブジェクトを追加する提案。

<functional>には加算等の算術演算や比較、論理演算に対応する関数オブジェクトがあります(例えばstd::plusstd::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つのギャップを報告しています

  1. エラー報告
    • I/Oに特有の、部分的な成功をsender/receiverの3つのチャネルにうまくマップできない
    • 部分的な成功: I/Oがキャンセルされたが、データは(途中まで)受信している
      • Asioではvoid(error_code, size_t)としてモデル化されており、多くのI/Oライブラリが同様に扱っている
    • sender/receiverset_stoppedチャネルは引数を取らないため、この場合に部分的に受信したデータが失われる
    • 回避しようとするとすべてをset_valueチャネルで扱うことになり、sender/receiverの通常のエラー処理モデルが利用できなくなる
  2. コルーチンからsenderへのエラー転送
    • 例えばexecution::taskによるコルーチン内で、失敗でそのtaskを終了させようとする場合にco_return errが使用できない
      • コルーチンのco_returnsendertask)のset_valueチャネル(成功)にマップされる
      • sendertask)のset_errorチャネルにシグナルする方法が無い
    • 検討されているco_yield with_error(...)のような方法は、既存のco_yieldの使用慣習(値の生成と中断、終了を意味しない)に反する
    • コルーチンのプロミス型においてreturn_voidreturn_valueがどちらかしか定義できないことが原因ではあるものの、これは他のコルーチンライブラリでは問題になっていなかった
  3. フレームアロケータの伝播
    • コルーチンではコルーチンフレームのアロケーションが必要となり、それをカスタムするためのアロケータの伝播がsender/receiverモデルの方法(環境による伝播)とマッチしない
      • 例えばexecution::taskコルーチンを起動しようとするときには、既にコルーチンフレームは確保済み
        • アロケータの伝播タイミングが遅い
      • コルーチンにアロケータを与えるには引数で指定する必要があるが、sender/receiverモデルの方法と整合しない
  4. 対称転送
    • senderでは対称転送をサポートできない
    • P2583R0(少し上)でより詳しく解説されている

これらのギャップは、std::executionの設計選択(がコルーチンの設計選択と異なることによる)のコストを示しており、これらのギャップはトレードオフです。

  1. エラー報告のギャップ: 結果報告チャネルのトレードオフ
    • std::execution: let_value, upon_error, upon_stoppedという3つのアルゴリズムは型レベルで分離されており、コンパイル時に3つの結果報告チャネルを分岐できる
    • コルーチン: (error_code, size_t)はまとめて返され、エラーとキャンセルにおけるバイト数は意味を持ち分離できない
  2. エラー転送のギャップ: co_yieldのトレードオフ
    • std::execution: コルーチンからsenderへの独立したエラーチャネル
      • co_yield with_error(ec);
    • コルーチン: 確立されたco_returnのセマンティクス
      • co_return std::unexpected(ec);
  3. フレームアロケータ伝播のギャップ: フレームアロケータのトレードオフ
    • std::execution: connect / startによる遅延実行
      • senderパイプラインはゼロアロケーション
    • コルーチン: promise_type::operator newは呼び出し元で適切なフレームアロケータとともに実行される
  4. 対称転送のギャップ: 対称転送のトレードオフ
    • 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::vecconstevalブロードキャストコンストラクタを追加する提案。

この提案はP3844R2から分離されたものです。P3844はこの提案の内容と数学関数オーバーロードに対する変更の2つを含んでいましたが後者だけがC++26に採択済みです。この提案の内容はLEWGのレビューで強い懸念(このような動作をおこなう型の前例がない)が示されていたため分離されました。

この提案の変更は

  • [simd.math]に関連する議論と提案を削除
  • LWGで提起された懸念事項に関する議論を追加
  • 設計空間に関する議論をまとめて、LEWGで承認されたものについてのみにする
  • 配置の変更

などです。

この提案はsimd::vec<float> x;に対してx + 1のような記述をできるようにするものです。このようなコードでは右辺オペランド(1)がsimd::vec<float>に暗黙変換されることで動作(この時1simd::vecの全ての要素に展開されるので、ブロードキャストコンストラクタと呼ばれる)しており、その際にint -> floatの縮小変換が発生するために現在は一律に禁止されています(縮小変換が発生しなければ使用可能)。

この提案では、縮小変換が起こる場合のコンストラクタをconstevalにして縮小変換によって情報の欠落が発生しない事をコンパイル時にチェックすることで定数に対してのみx + 1のような記述を許可するものです。

これには次のような懸念がLWGのレビューにおいて表明されていました

  1. 新規性、標準ライブラリ中にはこのような動作をする型が無い
  2. この提案による制約付きのコンストラクタはコンセプト/型特性で正しく検出できない
  3. Immediate-escalation によりユーザーが意図したものよりも多くのコードがコンパイル時実行になりうる
  4. Immediate-escalation 後のエラーは理解しづらくなる可能性がある

このような懸念に対してLEWGのレビュー中に十分な時間を取って対処されていなかったため、全体投票で議論を呼ぶような採決を強行することを回避するためにP3844から分離されました。

P3844R0~R2においては、constevalコンストラクタにおいて変換可能(convertible_to<value_type>)であり値の損失が発生しない(value-preserving-convertible-to<value_type>)ことで制約していましたが、これだとvec<float>() + 1.5vec<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::vecbasic_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モデルとコルーチンそれぞれがそれを必要とする領域で適切に使用できること(非同期処理モデルを押し付けない事)が望ましいとしています。

この文書は提案ではありませんが、次の投票を行うことを促しています

  1. std::executionはヘテロジニアスコンピューティングと比べて、コルーチンによる非同期I/Oに最適とは言えない
  2. コルーチン駆動の非同期I/Oも、ヘテロジニアスコンピューティングと同様にその処理領域に特化した最適化が自在に行えるべき

今月公開の同じ著者の提案文書(P2583R0、P4003R0、P4007R0など)と同様に、sender/receiverによる非同期処理モデルは特定の領域に特化したもので、すべての非同期処理にとって最適ではないということを主張しています。

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_conditionenforce_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つの要素によってその振る舞いが決定されます

  1. 式の構造
    • 実行される抽象的な計算内容
    • グループ化、かっこ付け、オペランドの順序
  2. アルゴリズム
    • 式のどの側面が明示的に定義されているか、あるいは意図的に未規定のままであるかを決定する意味論的な契約
  3. 実行方法
    • 式の評価をどのようにスケジュールするか、逐次・並列・ベクトル化など

C++の標準ライブラリのアルゴリズムおよび集計機能に関してはこの分離に基づいて設計されています。1の式構造及び2のアルゴリズムはアルゴリズム関数そのものによって指定され、3の実行方法は実行ポリシーによって指定されます。

実行ポリシーは実行の側面を制御するもので、スケジューリングや並行性を制約する一方で式構造やアルゴリズムの意味論には全く影響を与えません。execution::seqが指定されていたとしても式構造を指定するわけではないため、reduce(execution::seq, ...)std::accumulateと同じにはならず、非結合的な演算においては実行ごとに異なる結果を返しえます。

提案では標準として式構造を固定していることを式の所有権を持つと表現しており、std::accumulatestd::reduceの違いは式の所有権を持っているかどうかにあるとしています(std::accumulateは式を所有している)。

したがって、この提案が導入しようとしているのは、式の所有権を持ちながらも並列化が可能な範囲集計処理です。

式の所有権を持たないstd::reduceは式構造を全く指定していませんが、それを完全に指定しているstd::accumulateと部分的に指定しているstd::inclusive_scan/std::exclusive_scanの規定を見てみると、集計アルゴリズムの式構造には次の2つの要素があります

  1. オペランドの評価順序
    • 入力範囲の要素がどの順番で消費されるか
  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などの既存ライブラリでも採用されている

この提案による並列集計機能が所有すべき式の構造は、ユーザーが選択したトポロジ座標によってパラメータ化される規範的な式(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、レーン数L1 <= L)として

  1. 分割
    • XL個のインターリーブされたレーンに分割する
    • 要素X[i]i mod Lで指定されるレーンに割り当てられ、各レーン内では入力順序が維持される
  2. Stage1: レーン毎の集計
    • [0, L)の各レーンjについて、CANONICAL_TREE_EVAL(op, Yj)を評価する
      • YjK = ceil(N / L)個の要素を持つ
        • NLの倍数ではない場合、要素が足りないレーンにはダミー要素を追加する
          • N < Lの場合、L - N個のレーンにはダミー要素を追加する
    • CANONICAL_TREE_EVAL(op, Yj)の評価中にダミー要素が存在する場合(NLの倍数ではない場合)、そのダミー要素に対応する要素はopを呼び出すことなく木の評価の次のステップへ伝播される(ダミー要素は伝播しない)
      • グループにダミー要素しかない場合は、そのグループの結果をダミー要素とする(opは呼び出されない)
  3. Stage2: レーン間の集計
    • L個のレーンの結果R[0..L)を、CANONICAL_TREE_EVAL(op, R[0..L))によって集計する
      • ダミー要素は2のステップと同様にopの評価がスキップされる
  4. 初期値の統合
    • 初期値(init)が提供されN == 0の場合、A{init}を返す
      • Ainitの型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(すなわちNL)によって完全に特定され、この構造によって指定される集計処理は抽象的な式構造のみとなります。この規範的な式と同じ結果が得られるのであれば、独立した部分式を並行化して評価することが許可されます。

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のようにブロック分割するのではなく、範囲の先頭要素から順番にそれぞれのレーンに割り振っていく形になり、連続する要素はそれぞれ異なるレーンに割り振られることになります。

このインターリーブ方式が選択されたのはパフォーマンス上の利点のためです

  1. SIMDレジスタへのデータのロードと直接的に対応する
    • L個の連続した要素を単一のベクトルロード命令で読み込むことで、各要素がL個のレーンに分配される
    • ブロック分割ではレジスタを埋めるためにメモリ上の離れたL箇所からギャザーロードを行う必要がある
  2. 要素数NLの倍数でなくても、すべてのレーンの要素数がほぼ均一になる(ダミー要素をカウントすると完全に均一になる)
    • 最大でも1要素の差の範囲内ですべてのレーンの個数が均等化され、レーン間で偏りが生じない
    • これにより各レーン間でIterative Pairwise木の形状が完全に一致し、レーン内でのIterative Pairwise集計処理においてSIMDのロックステップ実行を極めて効率的に処理できる
  3. 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への対応方針の提言文書。

主に

  1. 標準化プロセスにおける生成AIの利用についてのガイドライン
  2. 生成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以降で作業を進めていく必要があります。

この文書は、それにあたっての優先項目のリストです。

  1. C++29の最優先事項
    1. データサイエンス基盤(std::data_frame)およびCSV読み込み機能
      • 提案や作業はなし
    2. コア数学機能(std::linalg/mdspan)、mdarray
      • std::linalg/mdspanはC++26までに導入済み、mdarrayは作業中
    3. グラフデータ構造
      • 開発中(SG19)
    4. 統計計算
      • 開発中(SG19)
  2. C++29以降の戦略的方向性
    1. 自動微分とJIT
      • リフレクションも活用して、微分をコンパイル時にもサポートする
      • JAXと同等の機能(コンパイル時の数学的最適化)を手に入れる
    2. 低精度演算
      • std::float8_t(FP8)とstd::int4_t、重み圧縮のためのnibble型のサポートが必要
    3. スマートテンソル
      • メタデータ認識型mdspan(名前付きテンソル)
      • コンパイル時に文字列または型によってテンソルの次元に名前を付けて扱うことで、テンソルの次元の取り違えを防止する

特に、C++がPythonの単なるバックエンドに留まらないようにすることを意識しているようです。

P4026R0 Core Issue 3123 "Global lookup for begin and end for expansion statements"

template forrangeを判定する条件についてのイシューの解説スライド。

template for(展開ステートメント: expansion statements)はコンパイル時にタプルやrangeに基づくイテレーションによって文を生成することのできるfor文で、主にリフレクションの文脈で使用されます。

template forは範囲forによく似ていますがそれとは異なり、タプルや構造化束縛可能なものもイテレーションすることができ、規格では3種類の対象が指定されています。その中にはrangeも含まれており、expansion-iterableな式(expansion-iterable expression)として指定されています。

Eがexpansion-iterableな式であるとは、次の事を満たす場合です

  • 配列型ではなく
  • 範囲forの場合と同じようにbegin-exprend-exprを決定したうえで
    • begin-exprend-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と関係ないこと、また範囲fortemplate 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以降にそれらの制約の下で標準機能の設計にどう関与していくかを説明しています。

  1. 安全性
    • Safe C++ / Profilesの採用を支持する
    • 安全性が最重要視されていることは認めつつも、安全のための機能が実行時オーバーヘッドやレイテンシの増大を引き起こさない事を保証する必要がある
    • SG14では、決定論的例外処理を安全性分野への具体的な貢献として位置付けている
  2. CPUバウンドI/Oおよびネットワーク処理
    1. ネットワーク処理をP2300(std::execution)から分離する
      • SG14はネットワークライブラリをP2300上に構築すべきではないと提言している
      • P2300で要求されるアロケーションパターンは低遅延ネットワークと互換性がない
    2. ダイレクトスタイルI/Oを標準化する
      • C++29ネットワークモデルとしてP4003(もしくは類似のダイレクトスタイルモデル)を優先する
      • これはASIO/Beastのパフォーマンスとコルーチンの使いやすさを両立させ、ゼロオーバーヘッド原則を維持する
  3. C++をゲーム開発者により使いやすくするための優先課題
    1. P3442R1([[invalidate_dereferencing]]属性)
      • この属性はカスタムアロケータと手動メモリ管理における静的安全性を高める
      • この属性によりuse after freeバグがコンパイル時に発見しやすくなり、実行時オーバーヘッドが無い
    2. P3707R0 (std::is_always_exhaustive特性)
      • ゲームロジックは基本的に状態駆動型であり、コンパイル時にある種の網羅性を強制することのできる機能により、処理されない状態などのロジックバグを実行時オーバーヘッドなく削減できる
  4. 超低遅延と高頻度取引
    1. ロックフリー並行処理とメッセージング
      • 低遅延金融分野では、異なるスレッドがOSレベルのロックやブロック無しで通信する必要がある
      • リングバッファ(P0059)
      • 並行キュー(P0260)
      • ハザードポインタとRCU
        • C++26で導入、C++29で拡張予定
    2. キャッシュ局所性とメモリレイアウト
      • 低遅延においては、アルゴリズムの計算量よりも予測可能なメモリアクセスの方が重要な場合が多い
      • メンバレイアウト制御(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_xxxto_xxxの組は同じことをします。名前を変えて提供しているのは、上記例のようにパイプラインにおいて可読性を高めるためです。

これらのRangeアダプタは入力範囲の要素型が整数型(std::integral)である必要があり、エンディアン変換は整数値をstd::byteswapすることで行われます。

そのため、ユニコード文字専用の機能というわけではなく、ネットワークバイトオーダーの変換や一部のファイルフォーマットを扱う際など、エンディアン変換が必要となる他の場所でも使用することができます。

P4031R0 Rename system_context_replaceability namespace

system_context_replaceability名前空間の名前をリネームする提案。

std::execution::system_context_replaceabilityparallel_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_schedulersystem_contextという名前だったことの名残です。

しかしparallel_schedulerに変更された後でもなぜかこの名前空間名にだけsystem_contextが残り続けており、変更しようとした提案(P3804R1)もありましたがリジェクトされています。

system_contextという対象が不明なことによって、system_context_replaceability名前空間にあるものが何を置き換えているのか(置き換えようとしているのか)が分からなくなっています。

この命名は一貫性が無く、C++26をこのままにすると大惨事を招くとして、この提案では改めてsystem_context_replaceabilityの名前の変更を提案しています。

提案では次の2つの候補を提示しています

  1. std::execution::parallel_scheduler_replaceability
  2. std::execution::parallel_scheduler_replacement

筆者の方は2番目の方が若干好ましいとしていますが、どちらを主としているわけではありません。

P3804R1が他の変更も含んでいたのに対して、この提案はこのリネームだけを目指すものです。

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++の安全性を高める作業を最優先で進める
      • プロファイル
      • ライブラリのセキュリティ強化
  • Tier1: メンテナンスリリース
    • C++26はメジャーリリースであり大きな機能がいくつも追加されているため、これらの機能が展開されるにつれて現実とのギャップが見つかることが想定される
    • C++29はメンテナンスリリースとして位置付けてC++26機能のメンテナンスに努め、その作業に遅延をもたらすような提案を無理に通すのは避ける
  • Tier2: C++26に間に合わなかった機能
    • パターンマッチ
    • ネットワーク

今月のP4024R0やP4023R0なども、この方向性に関連したDirections Groupによる文書です。

おわり

この記事のMarkdownソース

[C++]入れ子クラステンプレートの推論補助

最近少しはまったことのメモです。

入れ子クラス

入れ子クラスとは、別のクラスの内部で定義されたクラスの事です。

struct S {
  struct N {
  };
};

このS::Nの事を入れ子クラスとここでは呼びます。

入れ子になってはいてもクラスとしての制限は特になく、テンプレートにすることもできます。

struct S {
  template<typename T>
  struct N {
    T t;
  };
};

この時、入れ子クラステンプレートの推論補助を書きたくなった場合、どうすればよいのでしょうか?Sのスコープには書けないような気がしますが、名前空間スコープに書いても見つけてもらえなさそうな気もします。

この回答は、推論補助は関連するクラステンプレートと同じスコープで定義する、になります。

struct S {
  template<typename T>
  struct N {
    T t;
  };

  N(const char*) -> N<std::string_view>;  // Nと同じスコープで定義する
};

int main() {
  S::N n{"test"}; // ok、S::N<std::string_view>が推論される

  assert(n.t.length() == 4);  // ✔ string_viewになっている
}

godbolt

これはC++17でCTADが入った時から一貫した仕様です。・・・ですが、GCCは11まではこの入れ子クラステンプレートの推論補助の記述に対応していなかったようで、12から修正されています。今回見事にGCC11で困りました。

GCC11までのバグを回避する簡単な方法は、対応するコンストラクタを書くことです。CTADにおいてはコンストラクタから推論補助が導出されて使用されるため、クラステンプレートのテンプレートパラメータを使用するようなコンストラクタを書くことで推論補助が自動で生成されます。ただし、コンストラクタテンプレートにしてしまうとCTADを成功させることができなくなる(かかなり困難になる)ので注意が必要です。

ただし、上の例のような推論補助と同じ振る舞いをするコンストラクタは書くことができません(たぶん)。

リーガルチェック

[temp.deduct.guide]/3の一節より

A deduction-guide shall inhabit the scope to which the corresponding class template belongs and, for a member class template, have the same access.

翻訳(powered by PLAMO翻訳)

推論補助は、対応するクラステンプレートが属するスコープ内に配置され、入れ子クラステンプレートの場合は同じアクセス権を持つ必要がある。

すなわち、正確には同じスコープかつ同じアクセス指定を持つ必要があります。

struct S {
  template<typename T>
  struct N {
    T t;
  };

private:
  N(const char*) -> N<std::string_view>;  // ng、Nと同じアクセス指定を持っていない
};

godbolt

この時、アクセス指定が一致してればいいだけで、privateprotectedな入れ子クラステンプレートで推論補助が書けないわけではありません。

struct S {
protected:
  template<typename T>
  struct N {
    T t;
  };

  N(const char*) -> N<std::string_view>;  // ok
};

struct D : public S {
  void f() {
    S::N n{"test"}; // ok

    assert(n.t.length() == 4);  // ✔
  }
};

godbolt

ローカルクラス

入れ子クラスとよく似たクラスにローカルクラスがありますが、こちらの場合は推論補助はどうなるのか気になる人もいるかもしれません。

しかし、ローカルクラスはテンプレートにすることができないため、そもそも推論補助が必要にならず、推論補助を書くこともできません。

参考文献

この記事のMarkdownソース

[C++]std::start_lifetime_as()によるtype punning

C++ type punning環境最前線における最新のtype punning手法の紹介です。

std::start_lifetime_as()

std::start_lifetime_as()はC++23で追加された関数で、指定されたストレージでimplicit-lifetime typeな型のオブジェクトの生存期間を明示的に開始させるための関数です。

#include <memory>

// 独自アロケータのようなもの
namespace my_alloc {
  alignas(alignof(void*)) std::byte storage[1000];

  template<typename T>
  auto allocate_memory() -> T* {
    // 適当実装です
    return static_cast<T*>(static_cast<void*>(storage));
  }
}

// Xはimplicit-lifetime type
struct X { int a, b; };

static_assert(std::is_implicit_lifetime_v<X>);  // ✅

int main() {
  X* memory = my_alloc::allocate_memory<X>();

  // Xはimplicit-lifetime typeではあるものの、生存期間が開始されていない(allocate_memory()がユーザー定義関数であるため)
  X* ub_ptr1 = reinterpret_cast<X*>(memory);  
  ub_ptr1->a = 1; // 💀 UB
  ub_ptr1->b = 2; // 💀 UB

  // start_lifetime_as()を通すことで生存期間を開始する
  X* valid_ptr = std::start_lifetime_as<X>(memory);
  valid_ptr->a = 1; // ok
  valid_ptr->b = 2; // ok
}

このオブジェクトの生存期間の開始はコンストラクタ呼び出しを伴うものではなく、論理的なものです。std::start_lifetime_as()は実質的には何もしない関数です。

std::start_lifetime_as()の効果についての規定

std::start_lifetime_as()がオブジェクトの生存期間を開始する仕組みは関数の効果(Effects)として次のように規定されています

Implicitly creates objects ([intro.object]) within the denoted region consisting of an object a of type T whose address is p, and objects nested within a, as follows: The object representation of a is the contents of the storage prior to the call to start_lifetime_as. The value of each created object o of trivially copyable type ([basic.types.general]) U is determined in the same manner as for a call to bit_cast<U>(E) ([bit.cast]), where E is an lvalue of type U denoting o, except that the storage is not accessed. The value of any other created object is unspecified.

翻訳(Powered by PLAMO翻訳)

指定された領域内に、アドレスが p である型 T のオブジェクト a 、および a 内にネストされたオブジェクトを暗黙的に作成します。
a のオブジェクト表現は、start_lifetime_as() 関数呼び出し前のストレージの内容となります。
トリビアルコピー可能な型([basic.types.general]) U の作成された各オブジェクト o の値は、bit_cast<U>(E)([bit.cast])への呼び出し時と同様に決定されます。ここで、 E は評価結果が o となる型 U の左辺値式ですが、この場合(pの)ストレージにはアクセスしません。
その他の場合の作成されたオブジェクトの値は未規定です。

ここでのpTstd::start_lifetime_as<T>(p)のように呼び出した時の引数のポインタと作成対象のオブジェクト型です。

implicit-lifetime typeが言語の仕組みとして特定の操作(malloc()memcpy()など)において暗黙的にオブジェクトが作成される場合は単にそのコンテキストで適切なオブジェクトが作成されるというだけでそれ以上の事はせず、特に作成されたオブジェクトの持つ値やオブジェクト表現についての保証は何もありません。

しかし、std::start_lifetime_as()はトリビアルコピー可能な型に関して言語の仕様よりも拡張された保証を持っています。つまり次の二文です

a のオブジェクト表現は、start_lifetime_as() 関数呼び出し前のストレージの内容となります。

トリビアルコピー可能な型([basic.types.general]) U の作成された各オブジェクト o の値は、bit_cast<U>(E)([bit.cast])への呼び出し時と同様に決定されます。

そして、その動作はstd::bit_castを参照する形で指定されています。

std::bit_castの規定は様々なケースを考慮してさらに複雑に書かれていますのでここでは割愛します。基本的にはビットレベルの再解釈キャストが可能であるということを理解しておけばよいでしょう。

興味ある型はこちらを参照してください

なお、オブジェクト表現というのは簡単に言えば型をバイト配列として読み出した際のビット列の事です。このビット列のうち、その型の値として有効であるビット列の事を値表現と呼びます。構造体のパディングはオブジェクト表現に含まれるものの値表現には含まれません。

type punning

結果的に、T* vp = std::start_lifetime_as<T>(p)の様な呼び出しにおいては次のことが保証されています

  • *vpのオブジェクトのオブジェクト表現はpのストレージの内容と一致する
  • Tがトリビアルコピー可能である場合、*vpのオブジェクトの値はpのストレージのビット列を再解釈して決定される

すなわち、Tがトリビアルコピー可能な型であればstd::start_lifetime_asは合法的にtype punningが可能です。

#include <stdfloat>
#include <cstring>
#include <memory>
#include <bit>
#include <array>

int main() {
  const std::float32_t f = 3.14f;
  alignas(alignof(std::int32_t)) std::byte storage[sizeof(std::int32_t)];  

  // オブジェクト表現のコピー
  std::memcpy(storage, &f, sizeof(std::int32_t));

  // std::int32_tの生存期間開始
  auto* p = std::start_lifetime_as<std::int32_t>(storage);

  // bit_cast()
  auto rv = std::bit_cast<std::int32_t>(f);

  std::println("start_lifetime_as: {}", *p);
  std::println("bit_cast         : {}", rv);
}
start_lifetime_as: 1078523331
bit_cast         : 1078523331

この*pの値がstd::bit_cast<std::int32_t>(f)として再解釈キャストを行った場合と一致することが保証されます。

これを利用するには、std::start_lifetime_asする際の型Tがimplicit-lifetime type(多分こっちを違反するとコンパイルエラーになる)かつトリビアルコピー可能である必要があります。

std::bit_castが有効ではない場合、不定値やEVなど

std::start_lifetime_asによるtype punningの保証はstd::bit_castに委ねられているため、そちらの規定で未定義だとか不定値だとか言われている場合は同様に有効ではなくなります。

例えばstd::bit_castの規定では、ソースのオブジェクト表現を再解釈した結果がキャスト先の型の値表現と対応しない場合(ビットキャスト後のビット列がその型で有効なビット列ではない場合)には結果が未定義とされています。また、不定値を持つビットをbit_castした結果が値表現のビットに含まれてしまう場合はその値も不定値となります。さらにはerroneous valueも伝播します。

パディングビットを再解釈しようとする例

#include <stdfloat>
#include <cstring>
#include <memory>
#include <bit>
#include <array>

// パディングがある
struct X {
  std::int8_t b;
  std::int32_t n;
};

static_assert(std::is_implicit_lifetime_v<X>);      // ✅
static_assert(std::is_trivially_copyable_v<X>);     // ✅
static_assert(sizeof(X) == sizeof(std::float64_t)); // ✅

int main() {
  const X x{ .b = 0, .n = 0 };
  alignas(alignof(std::float64_t)) std::byte storage[sizeof(std::float64_t)];

  // オブジェクト表現のコピー
  std::memcpy(storage, &x, sizeof(std::float64_t));

  // std::float64_tの生存期間開始
  auto* p = std::start_lifetime_as<std::float64_t>(storage);
  
  // *pの値は不定値
  auto ub = *p; // 💀 UB
}

この例では*pを読み出すと不定値を読み出すことになり未定義動作になります。std::float64_tオブジェクトそのものは作成済みであるため、上書きすることは問題なくできます。

利点

基本的にはstd::bit_castを使った方がほとんどの場合に良いでしょう。implicit-lifetime typeに限定されず、定数式で使用でき、手動memcpyステップが無いので使いやすいなどがあります。

あえてこの方法の利点を挙げるなら、ビット再解釈のためにコピー的操作が不要であることがあります。

std::bit_castT r = std::bit_cast<T>(src)のように呼び出してsrcからrへビットコピーを行ったうえでrのオブジェクトの生存期間を開始するような動作をします。これは操作としてコピーを暗に含んでいます。

もしあるストレージにすでに所望のオブジェクト表現が存在していることが分かっていて、単にその領域をそのまま再解釈したい場合、std::bit_castのコピー動作は不要であり、省きたいかもしれません。

その場合は、std::start_lifetime_asのこの方法が有効であるかもしれません

#include <stdfloat>
#include <cstring>
#include <memory>
#include <bit>
#include <array>

int main() {
  {
    std::float32_t f = 3.14f;

    // 暗にコピーを含む
    std::int32_t r = std::bit_cast<std::int32_t>(f);
  }
  {
    std::float32_t f = 3.14f;

    std::destroy_at(&f);  // 多分不要

    // ストレージを再利用してstd::int32_tへ再解釈
    auto* p = std::start_lifetime_as<std::int32_t>(&f);
    
    std::int32_t v = *p; // ok

    std::destroy_at(&p);  // 多分不要
  }
}

とはいえstd::bit_castの呼び出し程度は最適化で簡単に消せる気がするので、このことが本当に有用であるかはよく分かりません・・・

std::start_lifetime_as_array()

std::start_lifetime_as_array()は指定領域に指定サイズの配列型の生存期間を開始する関数で、その動作は要素ごとにstd::start_lifetime_as()によって指定されているため、同様のことが可能になります。配列全体ではなく配列の各要素ごとにですが、配列の場合各要素間にパディングが無いためある型を別の型の配列として読み替えるということもできます。

#include <stdfloat>
#include <cstring>
#include <memory>
#include <bit>
#include <span>

int main() {
  const std::float64_t f = 2.718;

  alignas(alignof(std::int16_t)) std::byte storage[sizeof(std::float64_t)];
  
  // オブジェクト表現のコピー
  std::memcpy(storage, &f, sizeof(std::float64_t));

  // std::int16_t[4]の生存期間開始
  auto* p = std::start_lifetime_as_array<std::int16_t>(storage, 4);

  std::println("{}" , std::span{p, 4});
}
[14680, -14156, -16778, 16389]

ちなみにこの場合、配列型を返却できないことによりstd::bit_castが素直には使用できないため、こちらも利点となりえるかもしれません。

int main() {
  const double f = 2.718;

  auto rv1 = std::bit_cast<std::int16_t[4]>(f); // ng、一般的に関数は生配列を返せないことによるエラー

  auto rv2 = std::bit_cast<std::array<int16_t, 4>>(f);  // ok、std::arrayはreturnできる
}

参考文献

この記事のMarkdownソース

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

文書の一覧

全部で26本あります。

もくじ

N5034 WG21 Agenda 23-28 March 2026, Croydon, UK

2026年3月に予定されている全体会議のアジェンダ。

開催日時や大まかなスケジュールが記載されています。

P1000R7 C++ IS Schedule (proposed)

C++29策定までのスケジュールなどを説明した文書。

このリビジョンではC++29策定までの3年間のスケジュールが追記されています。それ以外の部分はR6と同じ内容のようです。

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

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

以前の記事を参照

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

  • P3834の提案を採択する場合でもこの提案が役立つことを動機に追加
  • C++26に含まれなかった標準バージョンや機能への参照を更新
  • "ambitious"としていた提案をデフォルトにする

などです。

P3039R1 Automatically Generate operator->

<=>演算子の様な書き換えによってoperator->を導出する提案。

以前の記事を参照

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

  • 著者の追加
  • operator->を右辺値に適用することに関する2つ目の動機を追加
  • 提案する文言の追加
  • operator->を生成するのではなく書き換えすることを説明する表現に変更

などです。

P3373R2 Of Operation States and Their Lifetimes

P2300のstd::executionにおいて、構成された非同期アルゴリズムの内部状態の生存期間を特定の場合に限り短くする提案。

以前の記事を参照

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

  • execution::splitが削除されたことに伴って、その言及を削除
  • 文言の追加
  • 呼び出し可能なsenderファクトリ生存期間に関する議論を追加
  • 実装経験を追加

などです。

P3795R1 Miscellaneous Reflection Cleanup

リフレクション関連提案の間での文言の整合性を調整する提案。

以前の記事を参照

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

  • annotations_ofdata_member_specの記述漏れを修正
  • 最新のWDにリベース
  • is_inline, is_constexpr, is_constevalに関する提案を削除

などです。

P3826R3 Fix Sender Algorithm Customization

senderアルゴリズムのカスタマイズ方法を修正する提案。

以前の記事を参照

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

  • R2に寄せられたフィードバックへ対応
  • get_completion_domain<>(attrs, env...)クエリを追加
    • デフォルトはget_completion_domain<set_value_t>(attrs, env...)と同等になる
  • アルゴリズムのカスタマイズをC++29へ延期する提案を削除

などです。

この提案は以前はsenderアルゴリズムのカスタマイズ機能をC++29まで延期しようとしていましたが、このリビジョンからはC++26で修正する方向に舵を切ったようです。

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

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

以前の記事を参照

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

  • constant template parameterに関する議論を追加
  • P3863R0からの文言変更を追加

などです。

P3873R0 2025-10 Library Evolution Poll Outcomes

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

次の提案が投票にかけられました

P3774はC++26、P3798はC++29を目標としてLWGに転送されました。投票の際のコメントが記載されています。

P3911R1 RO 2-056 6.11.2 [basic.contract.eval] Make Contracts Reliably Non-Ignorable

P3911R2 RO 2-056 6.11.2 [basic.contract.eval] Make Contracts Reliably Non-Ignorable

契約アサーションの評価セマンティクスから、ignoreの選択を制御できるようにする提案。

以前の記事を参照

R1での変更は

  • EWGの指示事項の実施に注力する
    • Option1の方向性を追求
  • 謝辞の追加
  • TL;DRセクションの追加
  • EWGの合意があるまで、セクション7の文言を削除する
  • §8 Frequently Asked Questions の追加

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

  • Section §1 (introduction): 動機を補足
  • Section §3 (proposal): §3.2 (prior work) と §3.3 (ABI implications)を追加
  • FAQの更新
    • ヘッダファイルへの依存が不要であることを明記
    • §8.11 (tooling), §8.12 (unchanged semantics), §8.13 (predicate evaluation count), §8.14 (previous implementation experience)の追加

などです。

EWGの議論においては、この提案の方向性(enforceセマンティクス固定アサーション)には強い合意があったものの、結局リジェクトされています。C++26 Contractsに関してはこの提案の変更は適用されません。

P3927R0 task_scheduler Support for Parallel Bulk Execution

task_schedulerparallel_schedulerを介してバルク実行する場合に並列化されるようにする提案。

execution::task_schedulerexecution::taskのために導入されたスケジューラのラッパ型で、execution::taskがその直接のコルーチンの実行に使用するスケジューラ(current scheduler)を保存するための型消去ラッパです。その性質上、task_schedulerはラップするschedulerで可能な操作を可能な限りそのまま提供できる必要がありますが、parallel_schedulerexecution::bulkによる並列バルク実行に関してはそうなっておらず、適切にラップできていません。

この提案は、execution::task_schedulerparallel_scheduler実装をラップしている状態でexecution::bulkで使用された時でも、ラップしない場合と同等のサポート(すなわち並列バルク実行)がされるように改修するものです。

改修のためにまず、execution::task_schedulerの型消去方法を見直します。execution::task_schedulerは次のような構造になっており、shared_ptr<void>メンバによってschedulerの型消去を行っています。

namespace std::execution {
  class task_scheduler {
    class sender; // exposition only
    template <receiver R>
    class state;  // exposition only

  public:
    using scheduler_concept = scheduler_t;

    template <class Sch, class Allocator = allocator<byte>>
      requires (!same_as<task_scheduler, remove_cvref_t<Sch>>)
        && scheduler<Sch>
    explicit task_scheduler(Sch&& sch, Allocator alloc = {});

    sender schedule();

    friend bool operator== (const task_scheduler& lhs, const task_scheduler& rhs) noexcept;

    template <class Sch>
      requires (!same_as<task_scheduler, Sch>)
      && scheduler<Sch>
    friend bool operator== (const task_scheduler& lhs, const Sch& rhs) noexcept;

    private:
      shared_ptr<void> sch_; // exposition only 👈
  };
}

提案ではこのsch_の型をshared_ptr<void>からshared_ptr<parallel_scheduler_backend>に変更することを提案しています。parallel_scheduler_backendは同様に型消去ラッパであるparallel_schedulerが型消去を行う際のbulkschedulerのインターフェース基底クラス型で、バルク実行の3つのsenderアダプタに対応した操作を備えています。

namespace std::execution {
  struct parallel_scheduler_backend {
    virtual ~parallel_scheduler_backend() = default;

    virtual void schedule(receiver_proxy&, std::span<std::byte>) noexcept = 0;
    virtual void schedule_bulk_chunked(size_t, bulk_item_receiver_proxy&, std::span<std::byte>) noexcept = 0;
    virtual void schedule_bulk_unchunked(size_t, bulk_item_receiver_proxy&, std::span<std::byte>) noexcept = 0;
  };
}

これは具体的なscheduler型によって、例えば次のように継承して使用します

template<scheduler Sch>
struct task-scheduler-backend : parallel_scheduler_backend {           // exposition only
  explicit task-scheduler-backend(Sch sch) : sched_(std::move(sch)) {}

  void schedule(receiver_proxy& r, span<byte> s) noexcept override;
  void schedule_bulk_chunked(size_t shape, bulk_item_receiver_proxy& r,
                             span<byte> s) noexcept override;
  void schedule_bulk_unchunked(size_t shape, bulk_item_receiver_proxy& r,
                               span<byte> s) noexcept override;

  Sch sched_;
};

task_schedulerに対してschedule()を呼んだ段階では、task_schedulerが後の処理のためのsenderを返すだけでこれらのクラスはまだ何も呼ばれません。task_schedulersenderが他のsenderとチェーンされ、最後にreceiverconnectされて生成されたoperation_stateオブジェクトに対してstartされた時に、task_schedulersendertask_schedulerが保持するshared_ptr<parallel_scheduler_backend>メンバを介してこのparallel_scheduler_backend実装が呼ばれます。

parallel_scheduler_backendの実装クラスでは、3つの操作はいずれも型消去対象のschedulerに処理を転送します。その際、task_schedulersenderconnectされたreceiverbulk_item_receiver_proxy経由で渡して(これも型消去ラッパ)、そのbulk_item_receiver_proxyと型消去対象のschedulerschedule()の結果senderとをconnectしてstartします。

schedule_bulk_chunked()などの場合は、型消去対象のschedulerexecution::bulkに渡されるように構成することによって、型消去対象のschedulerexecution::bulkの最適化を提供している場合(parallel_schedulerの場合)にその最適な実装が選択されるようになります(そうではない場合でもexecution::bulkのデフォルト実装である逐次実装にフォールバックする)。

なお、これらのparallel_scheduler_backendおよびその実装クラスはその名前やメンバ関数とは裏腹にschedulerではありません。これらのクラスはあくまで型消去schedulerにおいて型消去を行い、型消去対象のschedulerに巧妙にディスパッチを行うレイヤを担う部品です。

もう一つ、task_schedulerのドメインにおいて、bulk_chunked/bulk_unchunked操作に対応したsenderを適切に変換するためのtransform_senderを追加します。これはこれらの様なバルクsenderのカスタマイズを検出し、task_scheduler(のsender)においてsch_->schedule_bulk_chunked(...)/sch_->schedule_bulk_unchunked(...)を使用するように変換するものです。

namespace std::execution {
  class task_scheduler::ts-domain : public default_domain {
  public:
    template<class BulkSndr, class Env>
      static constexpr auto transform_sender(set_value_t, BulkSndr&& bulk_sndr, const Env& env)
        noexcept;
  };
}

transform_sendersenderアルゴリズムの呼び出し時とsenderreceiverconnect時のそれぞれにおいてその時点のドメインを用いてsenderを適切に型変換する仕組みです。これは、senderアルゴリズムのカスタマイズ検出とディスパッチを担っています。senderとドメインと環境を用いて、execution::transform_sender(domain, sndr, env)のように呼ばれ、ドメインのものが使用される場合はdomain.transform_sender(sndr, env)のように呼ばれて、sndrの型が変わらなくなるまで再帰的に呼び出しを行います。

この大きく2つの変更によって、task_schedulerは型消去を行いながらバルクschedulerへの対応を行うことができるようになります。

この提案の内容はNVIDIAのCCCLというライブラリにおいて実装されているようです。また、これには関連するNBコメントがあるようです。

P3941R1 Scheduler Affinity

execution::affine_onアルゴリズムの改善提案。

以前の記事を参照

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

  • 提案する文言の追加

などです。

P3950R0 return_value & return_void Are Not Mutually Exclusive

コルーチンのプロミス型において、.return_value().return_void()を同時に宣言できるようにする提案。

プロミス型の.return_value().return_void()関数はコルーチンにおけるco_returnの2つの形式(オペランドのあり無し)に対応しており、プロミス型においてはどちらか片方しか書くことができません。この制限はオーバーロード解決やコンセプトなどよりも強い部分で行われており、この2つの名前の関数が同時に宣言されているとコンパイルエラーとなります。

template<typename ReturnType> 
struct some-promise-type { 
  
  void return_void() requires std::is_same_v<void, ReturnType> {
    /* ... */
  } 

  template<typename T = ReturnType> 
    requires std::is_same_v<T, ReturnType> 
  void return_value(T t) {
    /* ... */
  } 

  ...
}; 

このコードはテンプレートパラメータReturnTypeに関わらずコンパイルエラーとなります。

co_returnはそのオペランドのあるなしによって.return_value().return_void()の呼び出しに展開されるため、あるコルーチン内ではco_returnの2つの形式を同時に使用することができません。

my_coroutine<T> coro_f() {

  co_return;

  co_return ...;  // ng
}

この制限によりまず、上記のように型に対して適応的なテンプレートなプロミス型を定義することができません。

また、コルーチンではプロミス型に.return_void()が定義されていない場合にコルーチンの制御フローの終端に到達すると未定義動作となるのですが、.return_value()を定義してしまっているとそれを回避することができなくなります。

// my_coroutine<T>のプロミス型は.return_value()を定義しているとすると
my_coroutine<T> coro_f() {

  ...

  co_yield ...;

  ...
} // コルーチン終端への到達は未定義動作

.return_value().return_void()を同時に使用できない制限はおそらく、通常の関数のreturnでオペランドあるなしを同時に使用できないことから来ています。しかし、コルーチンは通常の関数とは明確に異なるもので、標準でもそれは認識されており、コルーチン定義の直接の関数は呼び出されるとプロミス型からコルーチンハンドルをラップする型を返す薄いラッパにすぎません。

コルーチン宣言の直接の戻り値型はコルーチンが返す値の型とは無関係であり、コルーチンおよびプロミス型はすでに1つのコルーチンで様々な値を返すことができるようになっています。

このことはC++26のstd::executionの存在によって特に重要になります。std::executionにおける処理の抽象であるsenderはまず関数の終了に3種類の方法があるうえ、値を返す場合でも複数の戻り値型を取ることができるようになっています。そして、コルーチンは変換を介してsenderとして扱うことができ、また逆にsenderもコルーチンとして扱えるため、std::executionにおいて非同期処理を記述する際はコルーチンが使用されることが多くなっていくと予想されます。また、execution::taskというそれを補助するライブラリ機能も提供されます。

その際に、senderは値完了において戻り値なしと戻り値ありを同時に宣言(完了シグネチャとして宣言)することができますが、上記の制限によってコルーチンではこれはできません。そのため現在はコルーチン -> senderにおいては

  • co_returnの様々な型のオペランドでの呼び出しは.return_value()の呼び出しが有効である限り許可され、異なる型を受ける.return_value()の呼び出しを介して内部的に異なるexecution::set_value完了シグネチャに変換することができる
  • タプルプロトコルを適切に使用すれば、異なる型の.return_value()の呼び出しを一つのexecution::set_value完了シグネチャに解決できる

一方で、sender -> コルーチンでは

  • execution::set_value_t()(値無し値完了)と他の形式のexecution::set_value_t(...)(値完了、異なる型を含みうる)が同時に完了シグネチャにある場合、コルーチンでは受け入れられない

このことはコルーチンの意味論的あるいは概念的な制約によるものではなく、単に.return_value().return_void()の同時宣言が禁止されている(オペランドありなしのco_returnを同時に使用できない)ことによるようです。これによって、コルーチンはstd::executionの強力な機能(通常の関数を超えている部分)を十分にサポートできなくなっています。

この提案は、これらのモチベーションより、コルーチンのプロミス型において.return_value().return_void()を同時に使用可能にすることを提案しています。そしてこれにより、1つのコルーチン内でco_returnのオペランド有無両方の形式が利用可能になります。

この提案はすでにC++29に向けてCWGでレビューを受けています。

P3951R0 String Interpolation Objects

フォーマット文字列補完を行うためのテンプレートリテラル(t"...")の提案。

この提案は、P3412で提案されているfリテラルと同じ方向性の機能を異なるアプローチで提案するものです。モチベーションなどについては以前の記事を参照して下さい。

P3412R3では、言語に深く組み込む形でfリテラルを導入しようとしています。f"{name}"のようなリテラルは、文脈に応じてstd::stringになったり、std::printに渡すことのできる引数列になったりします。その処理はコンパイルフェーズ中にリテラル文字列をパースしてフォーマット文字列と対象の式列を区別しておき、オーバーロード解決フェーズでstd::format呼び出しまたは引数列への展開が行われます。

このアプローチは非常に複雑かつほとんどstd::format/std::print専用の機能となり、それらライブラリ機能へ強く依存する機能をコア言語機能として組み込むことになります。P3412の現在の設計は現在のstd::format/std::printに強く依存していますが、これらの関数が何かしら変更されたときに言語機能の変更も同時に必要になる可能性があります。

また、f""のような一文字のリテラルプリフィックスがstd::stringの生成というコストのかかる操作を隠蔽していることにも問題があります。

この提案はそれに対して、文字列補完(フォーマット文字列補完)を行うオブジェクトを生成するためのテンプレートリテラルt"..."によるアプローチを提案するものです。

この提案のテンプレートリテラルはPythonのテンプレートリテラルを参考にしたもので、t"{name}"のようなフォーマット文字列を含むテンプレートリテラルは、このフォーマット文字列をある程度解析して分解した結果を保持するオブジェクトを返します。

例えば次のようなテンプレートリテラルの使用は

auto get_result() -> int { return 42; }

auto example() -> void {
  auto interp = t"The result is {get_result()}\n";
}

次のようなコードに展開されます

auto example() -> void {
  struct Template {
    static consteval auto fmt() -> char const* { return "The result is {}\n"; }
    static consteval auto num_interpolations() -> size_t { return 1; }
    static consteval auto string(size_t i) -> char const* {
      constexpr char const* data[] = {"The result is ", "\n"};
      return data[i];
    }
    static consteval auto interpolation(size_t i) -> std::interpolation {
      constexpr std::interpolation data[] = {{"get_result()", "{}", 0, 1}};
      return data[i];
    }

    int _0;
  };

  auto interp = Template{get_result()};
}

型名のTemplateは仮のものです。このテンプレートリテラルオブジェクト型からは次の公開情報が提供されます

  • fmt(): リテラルをパースして置換フィールド内の式を取り除いた後のフォーマット文字列
  • num_interpolations(): 置換フィールドの数
  • string(i): 置換フィールドを区切りとしたときの、i番目の部分文字列
    • 置換フィールドで始まっている/終わっている場合は、先頭/最後の文字列が空文字になる
  • interpolation(i): i番目の置換フィールドの補完情報
    • 1つの置換フィールドについて、fmt()と同様の文字列、式の文字列、置換フィールドのインデックス、ネストしたものも含めた式の数、を保持する集成体オブジェクトを返す
  • データメンバ: 各置換フィールドから抽出した式を評価した結果を保持する
    • メンバ名は未規定
    • 順番は置換フィールドの登場順と一致する
    • 型は対応する式をEとするとdecltype((E))

このようにテンプレートリテラルは文字列リテラルやフォーマット後文字列、あるいはフォーマット引数列へ展開されるのではなく、テンプレートリテラルをある程度分解した情報を保持した型のオブジェクトに展開されます。

std::printstd::format()では、オーバーロードを追加してテンプレートリテラルが生成するオブジェクトを受け取るようにし、その中でパースされたフォーマット文字列や式の情報を取得してから通常のオーバーロードを呼び出すようにして対応することができます。

template <TemplateString S>
auto print(S&& s) -> void {
  auto& [...exprs] = s;
  std::print(s.fmt(), exprs...);
}

テンプレートリテラルの字句解析

テンプレートリテラルのパースにおいては少なくとも置換フィールドを認識してそこから式に対応する文字列を抽出する必要があり、これはライブラリではなくコンパイラによって行われます。

テンプレートリテラルにおいての置換フィールドは次の2つの形式に分類されます

replacement-field:
  { expr }
  { expr : format-spec }

1つ目はそのままt"{expr}"のような形で、2つ目は{var:s}のようにフォーマットオプションが指定されたものです。ここではこれ以上の解析(フォーマットオプションの解析)は行われず、通常のフォーマット文字列とは異なり:の前にインデックス指定が来ることはありません。

このexprに対応する部分には任意の式(balanced token sequence)を記述することを許可するものの、()に囲まれない:が出現すると置換フィールド内の式の終了として認識されます。

テンプレートリテラルと字句解析結果の例

テンプレートリテラル フォーマット文字列部分 式部分
t"{x}" "{}" x
t"{[]{ return 42; }()}" "{}" []{ return 42; }()
t"{co_await f(x) + g(y) / z}" "{}" co_await f(x) + g(y) / z
t"{a::b}" "{::b}" a
t"{(a::b)}" "{}" (a::b)
t"{cond ? a : b}" "{:b}" cond ? a
t"{(cond ? a : b)}" "{}" (cond ? a : b)

テンプレートリテラルの字句解析はプリプロセッサの実行(翻訳フェーズ4)よりも前に行われます。この時、字句解析の結果(:の発見など)として無効な式が抽出される場合でも、字句解析フェーズではエラーにしません(テンプレートリテラルの展開後のオブジェクト初期化式のパース時にエラーになる)。

また、:は文字としてかっこに囲まれない最初の出現が式の終端として認識されます。トークン単位(:::<など)の判定ではありません。これは、rangeフォーマットのフォーマットオプションなどにおいて::の指定に意味があるためです(あるいは、:+記号のような将来のオプション拡張を受け入れることができます)。

namespace v { int x = 2; }

auto example() -> void {
  std::vector<int> v = {10, 20, 30};

  std::println(t"{v::x}");    // [a, 14, 1e]
                              // "{::x}", v としてパースされる
                              
  std::println(t"{(v::x)}");  // 2
                              // "{}", (v::x) としてパースされる
}

プリプロセッサの実行前に字句解析が行われてテンプレートリテラルが展開されることによって、テンプレートリテラル内にはマクロを含めることができます。ただし、マクロの展開はテンプレートリテラルの字句解析とオブジェクト初期化式への展開よりも後であり、マクロの展開結果に含まれる:が式終端として認識されることはありません。

namespace v { int x = 2; }

auto example() -> void {
  #define SCOPED v::x
  std::println(t"{SCOPED}");  // 2([a, 14, 1e]ではなく)
                              // "{}", SCOPED としてパースされる
}

デバッグオプション

このテンプレートリテラルにおいては、単純な拡張によってPythonのようなデバッグフォーマットとの短縮指定({x=})を実現できます。

std::println(t"{x=}, {y=}, {z=:?}");

この実装は、字句解析時に置換フィールド内の式部分が=で終わる場合は=を削除して削除前の式を文字列化したものを置換フィールドの前に追加することで行えます。すなわち、t"Hello {name=}"t"Hello name={name}"として書いたのと同じになります。

ただし、Pythonのこの機能においては出力される文字列はデバッグ形式になりますが、C++のstd::formatではデバッグ形式の一般定義がないことやデバッグオプションが一部の型でしか使用できないなどの制限があるため、デバッグ形式で出力せず、そのためのオプションも追加していません。デバッグ形式をサポートしている型の場合は、フォーマットオプションとして?を指定することでこれを有効化できます。

この提案はこれを含んでいます。

ネストする置換フィールド

フォーマットオプションの中には置換フィールド内で置換フィールドを指定することでオプションの引数として動的な値を取ることができるようになっているものがあります。例えばstd::format("{:>{}}", name, width)name変数のフォーマット時の幅の指定としてwidthを使用します。これはテンプレートリテラルで記述するとt"{name:>{width}}"のようになります。

std::formatにおいて置換フィールド内のフォーマットオプションの構文は実はほぼ自由であるため(例えばchronoのオプションなどのように)、このようなネストは任意に現れる可能性があります。そのオプション構文はstd::formatter:parse()で指定されているため、テンプレートリテラルの字句解析中にオプション構文を認識することはできません。

そのため、テンプレートリテラル内で置換フィールド内にネストしている置換フィールドは、それもまた一つ置換フィールドとしてパースされます。すなわち、置換フィールド内で{が現れると、必ず:or}で終わる式が開始されたものとして扱われます。

仮にtupleのフォーマットオプションを拡張して要素に対応する{}によって各要素のオプションを個別に指定するようにしている場合(The Surprising Complexity of Formatting Ranges で紹介されている)、次のようになります

int main() {
  auto elems = std::tuple(10, 20, 30);

  fmt::print(t"{elems:{x}{}{x}}\n");    // fmt::print("{:{}{}{}}", elems, x, x); と展開される(識別子xが無いのでエラー
  fmt::print(t"{elems:{:x}{}{:x}}\n");  // fmt::print("{:{:x}{}{:x}}", elems);   と展開される、ok
}

テンプレートリテラルオブジェクトの構造

テンプレートリテラル内の置換フィールドに指定された式は、その評価結果がテンプレートリテラルオブジェクトのメンバ変数として保存されます。その型はテンプレートリテラル内の置換フィールドの式Eに対してdecltype((E))になり、順番は置換フィールドの出現順に並びます。decltype((E))によって型を取得することで、不要なコピーを回避する最適な型を得ることができます。

置換フィールド内にネストした置換フィールドの式も同様に保存されるため、メンバの数はテンプレートリテラル内の補完数(num_interpolations())よりも多くなる場合があります。

補完数(num_interpolations())はテンプレートリテラルに含まれる直接の置換フィールドの数であり、ネストしたものを含みません。interpolation()が返すのはそれらの置換フィールド(補完部分)に関する詳細な情報で、次のような集成体型によって表されます。

struct interpolation {
  char const* expression;
  char const* fmt;
  size_t index;
  size_t count;
};

interpolation(i)i番目の補完部分に関してこのような型のオブジェクトを返します。

fmtはテンプレートリテラルオブジェクトの同名のメンバ関数と同じく、対応する置換フィールドから式部分を除いた文字列です。それに対して、expressionは式部分の文字列になります。

indexはテンプレートリテラル内でその補完部分の現れる順番(0-index)であり、countはその補完部分内でネストしている置換フィールドの数です。ネストしている置換フィールドの式の結果もテンプレートリテラルオブジェクトの直接のメンバとして直列化されて保存されているため、これらの情報はネストしている置換フィールドを正しく処理するために必要となります

例えば、"{name=:>{width}}"は次のように展開されます

struct Template {
  static consteval auto fmt() -> char const* { return "name={:>{}}"; }
  static consteval auto num_interpolations() -> size_t { return 1; }
  static consteval auto string(size_t n) -> char const* {
    constexpr char const* data[] = {"name=", ""};
    return data[n];
  }
  static consteval auto interpolation(size_t n) -> std::interpolation {
    consteval std::interpolation data[] = {{
      .expression = "name",  // note that "name" is preserved
      .fmt = "{:>{}}",       // but "width" is not
      .index = 0,
      .count = 2
    }};
    return data[n];
  }

  std::string const& _0;
  int const& _1;
};

string(i)が返すのは、補完部分を文字列の区切りとして分割したときの、i番目の補完部分ではない文字列です。

t"{name=:>{width}}"の場合は次のようになります

  • 文字列リテラル: "name="
  • 補完部分: "{:>{}}"と2つの式name, width
  • 文字列リテラル: ""

string(i)は0-indexでi番目の文字列リテラルを返し、補完部分の文字列は返しません。例えば、string(0)"name="であり、string(1)""を返します。

テンプレートリテラルオブジェクトの利用例

テンプレートリテラルオブジェクトはフォーマット文字列が記述されたリテラルについての情報を提供するため、std::format/print以外のもの(ユーザー定義のもの)でも利用することができます。テンプレートリテラルオブジェクトから提供される情報はstd::format/print以外のユースケースを満たすために必要性が想定されるものについて定義されています。

補完部分の文字列(引数からフォーマットされた部分)を強調表示(緑+太字)する例

template <TemplateString S>
auto highlighted_print(S&& s) -> void {
  constexpr size_t N = s.num_interpolations();

  auto& [...exprs] = s;

  template for (constexpr int I : std::views::indices(N)) {
    fmt::print(s.string(I));

    constexpr auto interp = s.interpolation(I);
    constexpr auto [...J] = std::make_index_sequence<interp.count>();
    fmt::print(fmt::emphasis::bold | fg(fmt::color::green),
               interp.fmt,
               exprs...[interp.index + J]...);
  }

  fmt::print(s.string(N));
}

テンプレートリテラルをJSONにシリアライズする例

template <TemplateString S>
auto into_json(S&& s) -> boost::json::object {
  constexpr size_t N = S::num_interpolations();

  auto& [...exprs] = s;

  boost::json::object o;
  template for (constexpr int I : std::views::indices(N)) {
    constexpr auto interp = s.interpolation(I);
    o[interp.expression] = exprs...[interp.index];
  }
  return o;
}

テンプレートリテラルでprintf()をラップする例

template <TemplateString S>
auto with_printf(S&& s) -> void {
  constexpr char const* fmt_string = [&]() consteval {
    std::string fmt = s.string(0);
    auto nsdms = nonstatic_data_members_of(remove_cvref(^^S), std::meta::access_context::current());

    for (size_t i = 0; i < s.num_interpolations(); ++i) {
      // in theory, this logic would be more interesting
      auto interp = s.interpolation(i);
      auto type = remove_cvref(type_of(nsdms[interp.index]));
      if (type == ^^int) {
        fmt += "%d";
      } else {
        if (interp.fmt == std::string_view("{:?}")) {
          fmt += "\"%s\"";
        } else {
          fmt += "%s";
        }
      }

      fmt += s.string(i + 1);
    }

    return std::meta::define_static_string(fmt);
  }();

  auto adjust = []<class T>(T const& arg){
    if constexpr (std::same_as<T, std::string>) {
      return arg.c_str();
    } else {
      return arg;
    }
  };

  auto& [...exprs] = s;
  constexpr auto [...Is] = std::make_index_sequence<s.num_interpolations()>();
  std::printf(fmt_string, adjust(exprs...[s.interpolation(Is).index])...);
}

これらの例それぞれではテンプレートリテラルオブジェクトの情報の一部だけしか使用していませんが、それぞれで必要とする情報は異なっています。テンプレートリテラルオブジェクトはこのように、ユーザーがそれを自由に使用できるように関連する全ての情報を公開しようとしています。

また、これらの例の関数は全て共通のテンプレートリテラルオブジェクトを用いて呼び出すことができます

auto verb() -> std::string { return "die"; }

auto main() -> int {
  std::string name = "Inigo Montoya";
  int width = 5;

  // テンプレートリテラルオブジェクト
  auto msg = t"Hello, my name is {name:?}. You killed my {relation}. Prepare to {verb():*^{width}}.\n";

  // msgを使用して呼び出せる
  std::print(msg);
  highlighted_print(msg);
  std::println("{}", into_json(msg));
  with_printf(msg);
}

TemplateStringコンセプト

例で使用されていたTemplateStringコンセプトはテンプレートリテラルオブジェクトを識別するために必要なものですが、これをどのように識別すべきかはまだ未定です。

テンプレートリテラル型はユーザーが任意に定義して使用できた方が便利なユースケースがあるため(テンプレートリテラル型の変換を行う場合など)、少なくともユーザーが定義できる必要がありそうだとしています。そのためコンパイラの組み込みの不透明型のような型を判定するようなコンセプトにはならないようです。

この提案は著者の方によってclangをフォークして静的リフレクションを実装しているブランチをさらにフォークして実装されているようです。このことは、P3412と比較しても実装可能性が高いことを示しています。

P3952R0 is_pointer_in_range

ポインタがあるメモリ範囲の内側にあるかどうかを調べるis_pointer_in_range()の提案。

このis_pointer_in_range()のような関数は在野のライブラリで度々再実装されており、WG21のメーリングリストでもたびたび必要性が言及されます。そして、すでに何回か提案が提出されています(P3234R1, P3852R0)。

この一見単純な関数の問題点は、正確な実装が難しいことにあります。特に、未定義動作や偽陽性/偽陰性(FP/FN)を排除しようとすると実装難易度が高くなり、正しい実装がされない傾向にあります。

例えば、あるポインタpが配列の先頭と終端のポインタb, eの間にあることを判定するのにb <= p && p < eと記述するのは一見問題なさそうに見えます。しかし、これはpが実際に[b, e]の範囲外にある時に比較そのものが未定義動作とされます(ポインタの比較は配列内でのみ有効)、

また、std::lessが実装定義の狭義全順序による比較を提供していることを利用して、std::lessによって比較を行う方法も知られています。このことは連想コンテナがポインタをキーとして扱うために必要な要件でもあります。

この場合、p[b, e)の間を指しているかを判定するのに!std::less<T const*>{}(p, b) && std::less<T const*>{}(p, e)という比較が行われます。これは狭義全順序があくまで実装定義のものであるためにFP/FNの可能性を抱えています。例えば、ある配列の外側にあるオブジェクトのアドレスpが、配列の先頭と終端のアドレス[b, e)の間にある場合であっても、それが実装定義の狭義全順序に従っていれば合法であるためです。

これはセグメント化ポインタやセキュリティタグ付きポインタなどのポインタ値のインターリーブによっておこりえます。ポインタの全順序といえば素朴な連続アドレス空間を想定しがちですが、実装においてメモリ領域がそうなっていることとポインタ値の順序がどう定義されるかは別の話です。

ここで例に挙げたような実装でも、現在の多くの環境においては意図通りに動作はします。しかし、それは将来にわたって保証されたものではなく、移植性がありません。

その一方で、このようなポインタの包含判定は主に配列外参照を検出する目的でニーズが高く、それを受けて在野のライブラリでよく実装されています。

そのため、正確な実装を移植性がある状態にするために、is_pointer_in_range()のような関数は標準化に値します。

提案ではこの関数の提案に当たって、次の3つの要件を定義しています

  1. すべてのポインタ値に対してwell-definedであること
  2. FP/FNが発生しないこと
  3. 定数式でも使用可能であること

is_pointer_in_range()はポインタがある範囲内にある場合にtrueを返し、上記の要件を満たす関数です。

namespace std {
  template<typename T> 
  constexpr bool is_pointer_in_range(T const* p, std::span<T const> s) noexcept; 
}

この提案では、std::spanによってあらわされた範囲s内にポインタpが包含されているか、を判定するインターフェースになっています。これはstd::spanが暗黙的にポインタ範囲の有効性を要求していることと、3つのポインタを取るインターフェースではその順序を間違えやすいなどの問題があることによります。ポインタペアで利用したい場合でも、is_pointer_in_range(ptr, {begin, end})とすれば簡単に利用可能です。

実装に関しては、コンパイラの提供する組み込み関数を用いるか、std::lessによる実装が有効な環境ではそれによっても実装可能だとしています。

P3953R0 Rename std::runtime_format

std::runtime_format()std::dynamic_format()にリネームする提案。

C++26のstd::runtime_format()は実行時文字列による実行時のフォーマットの(コンパイル時のフォーマット文字列チェックを実行時に遅延させる)ために用意されたフォーマット文字列をラップする関数です。

一方、C++26に向けてはstd::formatconstexpr化が導入されており、これに伴ってstd::runtime_format()そのものもコンパイル時実行可能になっています。

constexpr auto f(std::string_view fmt, int value) {
  return std::format(std::runtime_format(fmt), value);  // ok
}

こうなると、std::runtime_format()という名前は適切ではない可能性があります、std::runtime_format()はフォーマットがいつ行われるかではなくフォーマット文字列がどのように取得され検証されるかを制御するためのAPIであるため、定数式でも実行可能であることは正当であり(文字列リテラルではないフォーマット文字列を使用可能になるため)、runtimeというワードは誤解を生む可能性があります。

そのため、この提案ではstd::dynamic_formatという名前に変更することを提案しています。これにより

  • フォーマット文字列が動的に提供されること
  • フォーマット文字列がコンパイル時定数ではない事
  • その検証が遅延されること
    • ただし結果的にコンパイル時に行われることもある

を表現できるようになります。

constexpr auto f(std::string_view fmt, int value) {
  return std::format(std::dynamic_format(fmt), value);
}

この提案はその性質上C++26に向けて提出されており、2026年3月の全体会議でC++26への適用が承認されたようです。

P3955R0 It's Scopes All the Way Down

std::executionにおいて、非同期RAIIを導入する提案。

C++の通常の同期処理のコードにはスコープがあり、RAII(コンストラクタとデストラクタ)によってスコープの入り口と出口で任意の処理を挿入することができます。これは特にリソース管理において非常に強力な機能であることは今更説明するまでもありません。

C++26 std::executionsenderによる非同期処理チェーンの記述は、非同期処理専用の関数とその呼び出しフローを定義するものと見ることもできます。その場合のsender/receiverによる非同期関数呼び出しグラフには非同期スコープの概念はあっても非同期RAIIの概念はありません。

そのため、sender/receiverによる非同期関数呼び出しグラフにおいて、ある非同期スコープ内でしか使用しない非同期リソースの生存期間を適切に管理するためには、そのスコープの入り口と出口で手動の非同期構築・解放が必要になります。これは同期コードに直してみれば、RAIIが存在しないCの世界でのコーディングに近いものです。

非同期RAIIは、TCPの二段階初期化(ソケットの初期化が完了した後、接続が確立されるまでは初期化されたとみなされない)やio_uringのIORING_OP_CLOSEの様な非同期クリーンアップ処理を適切に扱うためにも必要になります。

この問題を解消するために以前にP2849R0という提案において非同期オブジェクトという概念を中心にしたsender/receiverチェーンにおける非同期RAIIの設計が提案されていましたが、これにはいくつか問題点がありました。

  • const左辺値の非同期オブジェクトしか扱えない
    • 構築のための引数をムーブできない
    • 非同期オブジェクトをムーブして消費できない
  • 引数の数が固定されていない
    • 非同期コンストラクタはカリー化しておくこともしておかないこともできたため、非同期オブジェクトを表現するために2つのコンセプトが必要になっていた
    • 非同期オブジェクトと言っても二種類あるため、ジェネリックな扱いが困難になっていた
    • 責任の分離が完全ではなく、std::executionの他の部分の設計と一貫しない
  • オブジェクト至上主義
    • 従来の同期的なRAIIにとらわれすぎている
    • スコープガードイディオムのように、RAIIへの要求は必ずしもオブジェクトの構築と破棄を必要としない
    • 非同期オブジェクトとそのストレージが一体になっていることで、スコープ出入りでのコードの実行とオブジェクトの構築と破棄が密接に結びつけられていた
    • senderが戻り値に関して通常の(同期)関数の制約を継承していないように、非同期RAIIもまた同期RAIIの制約に固執すべきではない

この提案は、これらの問題を解消した新しい非同期RAIIのための設計を提案するものです。

この提案の設計の中心となる概念はオブジェクトではなくスコープです。以前の提案とは異なり、スコープはストレージやオブジェクトとは関連付けられてはおらず、スコープに入る時に何らかの処理を実行し、出るときにもまた何らかの処理を実行します。このアクションはsenderで指定されるため非同期処理を実行することができます。

そして、スコープに入るためのsenderenter scope sender: スコープ入り口で実行するアクションを表す)は、その処理が完了(値完了)するとスコープから出るためのsenderexit scope sender: スコープ出口で実行するアクションを表す)を返します(このsenderは高階senderです)。

スコープを確立するためのものがスコープアルゴリズムです。スコープアルゴリズムはenter scope senderとスコープ内でネストさせる処理(sender)を受け取り、enter scope senderを実行してexit scope senderを取得し、ネストさせる処理を実行してから取得したexit scope senderを実行します。これにより、senderチェーンで非同期スコープを導入します。

非同期オブジェクトはスコープとストレージを組み合わせたものです。スコープに入るとそのストレージにオブジェクトが構築され、スコープから出るときにそのストレージのオブジェクトが破棄されます。非同期オブジェクトは次のようなシンプルなインターフェースを持ちます。

struct async_object { 
  using type = /* ... */; 
  execution::enter_scope_sender auto operator()(type*); 
}; 

この呼び出し演算子で受け取っているポインタがストレージのポインタであり、typeは非同期オブジェクトの実際の型です。この呼び出し演算子が返しているものがenter scope senderであり、このsenderがオブジェクトを構築した後でそれに応じてexit scope senderを返すため、これによりムーブによる消費やムーブ構築を行ってもスコープ出口でそれを考慮することができます。

このような設計をコンセプトとそれを使用するもので構成しています。

コンセプト

template<typename Sender>
concept exit_scope_sender = execution::sender<Sender> && ...;

Senderは次の場合にexit_scope_senderのモデルとなる

  • Senderexecution::senderのモデルである
  • Senderはdecay-copyおよびムーブにおいて例外を送出しない
template<typename Sender, typename Env>
concept exit_scope_sender_in = 
  exit_scope_sender<Sender> &&
  execution::sender_in<Sender, Env> && ...;

Sender, Envは次の場合にexit_scope_sender_inのモデルとなる

  • Senderexit_scope_senderのモデルである
  • Senderexecution::sender_in<Env>のモデルである
  • Senderは環境型Envを持つreceiverに例外を送出せずにconnect可能であること
  • execution::completion_signatures_of_t<Sender, Env>execution::completion_signatures<execution::set_value_t()>であること
template<typename Sender>
concept enter_scope_sender = execution::sender<Sender>;
template<typename Sender, typename Env>
concept enter_scope_sender_in = 
  enter_scope_sender<Sender> &&
  execution::sender_in<Sender, Env> && ...;

Sender, Envは次の場合にenter_scope_sender_inのモデルとなる

  • Senderenter_scope_sender_inのモデルである
  • Senderexecution::sender_in<Env>のモデルである
  • execution::completion_signatures_of_t<Sender, Env>には値完了シグネチャがちょうど一つだけ含まれており、その完了シグネチャは単項であり、唯一の引数の型がexecution::exit_sender_in<Env>のモデルとなる
    • このexit_scope_senderはそれを送信したenter_scope_senderのアクションを取り消す
template<typename O>
concept object = ...;

非同期オブジェクトを表すコンセプト。

  • (CV修飾と肩修飾を削除した後で)、オブジェクト型を示すネストされた型エイリアスtypeをもつ
  • type*型で呼び出し可能であり、その呼び出しによってenter_scope_senderのモデルとなる型が返される
template<typename O, typename Env>
concept object_in = 
  object<O> && ...;
  • 単項呼び出しの結果はexecution::enter_scope_sender_in<Env>のモデルとなる型を生成する
  • 呼び出し結果のenter scope senderは、提供されたポインタが示すストレージ内にtype型のオブジェクトを構築する非同期操作である

型エイリアス

template<enter_scope_sender Sender, typename Env>
  requires enter_scope_sender_in<Sender, Env>
using exit_scope_sender_of_t = ...;

enter_scope_senderであるSenderの非同期操作が完了した時に返されるexit_scope_sender型を取得する

template<typename T>
using type_of_object_t = std::remove_cvref_t<T>::type;

非同期オブジェクトの実際のオブジェクト型を取得する。

template<typename T>
using enter_scope_sender_of_object_t = std::invoke_result_t<T, execution::type_of_object_t<T>*>

非同期オブジェクトがその呼び出し結果として返すenter_scope_sender型を取得する。

senderファクトリ

  • enter_scopes

enter_scope_senderを受け取るN項senderファクトリ。結果のsenderオブジェクトは次のような動作をする

  • 接続時: 提供された各子senderを接続し、子operation_stateを構築する
  • operation_state開始時: 各子operation_stateを開始する
  • 各子操作の完了時
    • 値完了の場合: 結果のexit_scope_senderを保存する
    • それ以外の場合: 最初の非値完了の場合: この完了を保存し、他の全ての子操作に停止要求を行う
  • すべての子操作の完了時: 最後に完了した子操作に対して完了時処理を行った後
    • 非値完了で完了した子操作が無い場合
      • execution::set_valueで完了し、保存されているすべてのexit_scope_senderに対してexecution::when_allの結果を送信する
    • それ以外の場合: 保存されている非値完了を送信して終了

呼び出しシグネチャ

template<enter_scope_sender... Senders>
auto enter_scopes(Senders&&... s) -> enter_scope_sender auto;
  • nest_scopes

enter_scope_senderを受け取るN項senderファクトリ。N個のenter_scope_senderを一つのenter_scope_senderに合成し、N個のenter_scope_senderが表すスコープに順番に入る。結果のsenderオブジェクトは次のような動作をする

  • 接続時: 提供された各子senderを接続し、子operation_stateを構築する
  • operation_state開始時: 最初の子operation_stateを開始する
  • 各子操作の完了時
    • enter_scope_senderから作成された操作の場合
      • 値完了の場合: 結果のexit_scope_senderを保存し、次の子operation_stateを開始する
      • それ以外の場合: この完了を保存して、保存されているすべてのexit_scope_senderを接続し、最後のexit_scope_senderから開始する
    • exit_scope_senderから作成された操作の場合
      • 一つ前のexit_scope_senderを開始する(逆順で実行していく)
  • すべての子操作の完了時:
    • すべてのenter_scope_senderの処理が失敗しなかった場合: 全てのexit_scope_senderを逆順で開始する
      • exec::sequenceと同等のexit_scope_senderで完了する
    • それ以外の場合: 保存されている非値完了で完了する

呼び出しシグネチャ

template<enter_scope_sender... Senders>
auto nest_scopes(Senders&&... s) -> enter_scope_sender auto;
  • within

enter_scope_sender1つとsender1つ(他のsender)を受け取るsenderファクトリ。enter_scope_senderのスコープで他のsenderの非同期処理を実行するsenderを構成する。

結果のsenderオブジェクトは次のような動作をする

  • 接続時: enter_scope_senderを接続したoperation_stateに、他のsenderをdecayコピーする
  • operation_state開始時: 子operation_stateを開始する
  • enter_scope_senderの完了時
    • 非値完了の場合: その完了で全体の操作を直ちに完了する
    • 値完了の場合: 結果のexit_scope_senderをdecayコピーし、必要に応じて他のsenderをムーブし、他のsenderを開始する
  • 他のsenderの完了時: 完了を保存して、必要に応じて他のsenderをムーブし、exit_scope_senderを接続し、そのenter_scope_senderを開始する
    • exit_scope_senderの処理が完了した場合: 保存済みの完了情報を送信

呼び出しシグネチャ

template<enter_scope_sender Sender, sender OtherSender>
auto within(Sender&& s1, OtherSender&& s2) -> sender auto;
  • lifetime

最初の引数が呼び出し可能オブジェクト、末尾引数が非同期オブジェクトであるN項senderファクトリ。渡された非同期オブジェクトの生存期間の管理と、それらのスコープ内で実行される非同期操作(呼び出し可能オブジェクトが返すsenderによる)を実行するsenderを返す。

各非同期オブジェクトの::typeTs...とすると、呼び出し可能オブジェクトはTs&...で呼び出し可能である必要がある。結果のsenderオブジェクトは次のような動作をする

  • 接続時
    1. 提供されたすべての非同期オブジェクトを、operation_state内の適切なストレージへのポインタで呼び出す
      • 非同期コンストラクタの呼び出しを行い非同期オブジェクトの指定する型のオブジェクトを構築する、enter_scope_senderを返す
    2. 1で生成されたenter_scope_senderをすべてexecution::enter_scopesに渡す
      • 提供された非同期オブジェクトの並列構築とそれによるスコープを作成するenter_scope_senderを返す
    3. 接続時及び起動時に次のような動作をするsenderを構成する
      1. 前述のストレージ内の各オブジェクトを参照する参照パックを用いて、呼び出し可能オブジェクトを呼び出す
      2. 生成された`sender`を接続
      3. 生成された`operation_state`を開始
      
    4. 2と3で構成されたsenderをそれぞれwithinに渡す
      • 非同期オブジェクトのスコープ内(2)で非同期操作(3)を実行するsenderを返す
        • まず2のsenderを開始することで非同期オブジェクトの構築が行われ
        • その後3のsenderが実行される
          • 構築済みの非同期オブジェクト(の実体への参照)を用いて呼び出し可能オブジェクトからsenderを取得し
          • そのsenderを実行する
    5. 4で生成されたsenderを接続する
  • operation_state開始時: 子operation_stateを開始

この操作のconnectに渡されるreceiverはステップ5でwithinsenderの接続時にそのまま使用される。

呼び出しシグネチャ

template<typename F, object... Objs>
  requires std::invocable<F, type_of_object_t<Objs>&...> &&
           requires(F& f, type_of_object_t<Objs>&... objs) {
             {f(objs...)} -> sender;
           }
auto lifetime(F&& f, Objs&&... objs) -> sender auto;

クラス

  • sync_object

通常のコンストラクタ/デストラクタを持つ型をlifetimeで管理できるように変換する。objectおよびobject_inコンセプトの表現するものを最小限に実装した型。

template<typename T, typename... Args> 
  requires 
    is_constructible_v<T, Args...> && 
    is_destructible_v<T> 
struct sync_object {
  using type = T; 
 
  template<typename... Ts> 
    requires (is_constructible_v<Args, Ts> && ...) 
  constexpr explicit sync_object(Ts&&... ts) noexcept( (is_nothrow_constructible_v<Args, Ts> && ...) )
    : args_((Ts&&)ts...) 
  {} 
 
  template<typename Self> 
  constexpr execution::enter_scope_sender auto operator()( 
    this Self&& self, 
    type* storage) 
      noexcept( 
        is_nothrow_constructible_v< 
          tuple<Args...>, 
          decltype(std::forward_like<Self>(declval<tuple<Args...>&>()))
        >
      )
  {
    return 
      execution::just(std::forward<Self>(self).args_) | 
      execution::then([storage](tuple<Args...>&& tuple) noexcept(is_nothrow_constructible_v<T,Args...>) 
      { 
        const auto ptr = new(storage) T(make_from_tuple<T>(std::move(tuple)));

        return 
          execution::just() | 
          //  It's important we capture ptr not storage because storage 
          //  just points to storage whereas ptr actually points to an 
          //  object 
          execution::then([ptr]() noexcept { 
            ptr->~T(); 
          }); 
      }); 
  }

private: 
  tuple<Args...> args_; 
};

execution::lifetime(..., execution::sync_object<...>(...))は、lifetimeの最初の引数の呼び出し可能オブジェクトによって生成されるsenderの非同期操作のスコープに変数をアタッチする。

提案文書より、非同期ミューテックスの例。

この提案無しでの単純な実装

struct async_mutex { 
  template<std::execution::sender Sender> 
  std::execution::sender auto with(Sender&& sender); 
}; 

この実装は.with()の引数のsenderの処理の前後でミューテックスのロック取得と解放を行おうとするものです。しかし、.with()が引数無しではないことにより、このAPIでは複数のミューテックスを並列に扱うことは困難です。

この提案を利用した単純な実装は次のようになります

struct async_mutex { 
  std::execution::enter_scope_sender auto acquire(); 
}; 

これは次のように使用できます

async_mutex m;

auto critical_section = 
  std::execution::just() | 
  std::execution::then([&]() { 
    std::cout << "Holding the async mutex" << std::endl; 
  }); 

auto snd = std::execution::within(m.acquire(), std::move(critical_section)); 

最初の実装とは異なり、複数のミューテックスを同時に扱うこともできます

async_mutex a, b, c;

auto critical_section = 
  std::execution::just() | 
  std::execution::then([&]() { 
    std::cout << "Holding all async mutexes" << std::endl; 
  }); 

auto snd = std::execution::within( 
  std::execution::enter_scopes(a.acquire(), b.acquire(), c.acquire()), 
  std::move(critical_section)); 

しかし、これはasync_mutexオブジェクトが安全に存在できる同期スコープの存在を必要としています。つまり、a, b, cの生存期間内でsndの接続~完了が完了する必要があります。

この提案の機能抜きでこの問題を解決しようとすると、execution::just(...) | execution::let_value(...)を使用することができます。

auto snd = 
  std::execution::just(async_mutex{}, async_mutex{}, async_mutex{}) | 
  std::execution::let_value([]( 
    async_mutex& a, async_mutex& b, async_mutex& c) 
  { 
    auto critical_section = 
      std::execution::just() | 
      std::execution::then([&]() { 
        std::cout << "Holding all async mutexes" << std::endl; 
      });

    return std::execution::within( 
      std::execution::enter_scopes(a.acquire(), b.acquire(), c.acquire()), 
      std::move(critical_section)); 
  }); 

しかしこれは、async_mutex型に対して次の操作が可能である事を暗黙的に要求しています

  • execution::justsender内に格納できる
  • execution::justexecution::let_valueを合成できる
  • それに対して接続できる
  • execution::justoperation_stateからexecution::let_valueoperation_stateにミューテックスを伝播できる。

これには何度かのムーブを伴いますが、通常ミューテックスはムーブ不可能です。

この提案のsync_objectによってこの問題を解決できます

auto snd = std::execution::lifetime( 
  [](async_mutex& a, async_mutex& b, async_mutex& c) { 
    auto critical_section = 
      std::execution::just() | 
      std::execution::then([&]() { 
        std::cout << "Holding all async mutexes" << std::endl; 
      }); 
    return std::execution::within( 
      std::execution::enter_scopes(a.acquire(), b.acquire(), c.acquire()), 
      std::move(critical_section)); 
  }, 
  std::execution::sync_object<async_mutex>{}, 
  std::execution::sync_object<async_mutex>{}, 
  std::execution::sync_object<async_mutex>{}
);

execution::sync_object<async_mutex>{}async_mutexを構築せず、その構築のための引数無し非同期コンストラクタを提供するだけのラッパ型です。この非同期コンストラクタはlifetimesenderが接続されそのoperation_stateが開始されるまで実行されません。

lifetimeはそのoperation_state内のストレージに非同期オブジェクト(が指定する型のオブジェクト)を非同期的に構築・破棄するため、ここでのasync_mutexオブジェクトは一切のムーブを受けず、ムーブ可能であることは要求されません。

提案文書にはio_uringを扱う例などが掲載されています。

P3959R0 Let layout_stride::mapping with zero extent(s) accept zero strides

layout_stride::mappingの変換コンストラクタの事前条件を緩和し、エクステントがゼロであることを許容するようにする提案。

layout_stride::mappingstd::mdspanのレイアウトクラスのうちストライドによるレイアウトを表現するクラスであるlayout_strideのマッピング(指定されたインデックスから配列上インデックスへの変換)を行うクラスです。

mdspanやストライドレイアウトに関しては別の記事などを参照してください

layout_strideはC++26時点で定義されている標準の他レイアウトから情報損失無しで変換することができるように設計されています。その変換を行う変換コンストラクタの事前条件では、すべてのエクステント(次元)のサイズが正であること(0より大きい)を要求しています。

この要件によって、layout_strideにおいてはエクステントの一部が0の場合でも対応するストライドを0ではなく1にする必要があります。例えばエクステントが(3, 5, 0, 11)の場合、ストライドは(1, 3, 1, 105)のようにする必要があります。

この提案は、この要件を削除してlayout_stride::mappingにおいて、あるエクステントが0である場合にそれに対応するストライドを0にできるようにする提案です。

この提案の利点・モチベーションは次の2つです

  1. 空のレイアウトをlayout_strideへ変換できるようにする
  2. Pythonとmdspanの相互運用性向上

1について、layout_strideは現在標準に存在する他のレイアウトからほとんど任意に変換することができるため、レイアウトクラスのある種の型消去をサポートしています。このような利用法は、mdspanを引数型に取る時などにABIの安定化を図る際に有効となる可能性があります。

しかし、layout_left/layout_rightがストライド0を生成する可能性があるため、現在のlayout_strideはこの用途を完全にサポートできていません。

例えば、1つ以上のエクステントが0のlayout_left/layout_rightを作成すると、1つ以上のストライドが0のレイアウトマッピングが生成される場合があります(これは実装の選択次第ではあります)

#include <cassert>
#include <mdspan>
#include <print>

template<class Layout>
using mdspan_2d = std::mdspan<float, std::dims<2>, Layout>;

int main() {
  mdspan_2d<std::layout_right> mr(nullptr, 1, 0);
  std::print("{} {}\n", mr.stride(0), mr.stride(1));
  assert(mr.stride(0) == 0);  // ストライドが0になる(実装次第
  assert(mr.stride(1) == 1);

  mdspan_2d<std::layout_left> ml(nullptr, 0, 1);
  std::print("{} {}\n", ml.stride(0), ml.stride(1));
  assert(ml.stride(0) == 1);
  assert(ml.stride(1) == 0);  // ストライドが0になる(実装次第


  // この提案の目指すところ、現在は変換コンストラクタの事前条件違反
  mdspan_2d<std::layout_stride> mrs(mr);
  assert(mrs.stride(0) == 0u);
  assert(mrs.stride(1) == 1u);

  mdspan_2d<std::layout_stride> mls(ml);
  assert(mls.stride(0) == 1u);
  assert(mls.stride(1) == 0u);
}

少なくとも、kokkosのリファレンス実装とlibc++の実装ではこれが起こるようです。そして、現在どちらの実装でもこの事前条件をチェックしていないため、layout_strideへの変換は期待通りに動作するようです。

2について、mdspanのカバーする多次元配列の計算処理は近年Pythonが利用されることが増えたことで、PythonをユーザーインターフェースとしてC/C++の処理を呼び出すという使われ方が増加しており、このためPythonが定義する多次元配列のレイアウトをC++側でも受け入れ可能にすることの重要性が増加しています。

このため、Pythonにおいては多次元配列データのための共通バイナリインターフェースが定義されるようになっています。例えば、Buffer Protocol・numpyのndarray Protocol・DLPack format・CUDA Array interfaceなどです。

これらのレイアウトはlayout_strideが表現可能なレイアウトよりもより広いレイアウトを表現可能になっています。

  1. layout_strideは要素ストライドなのに対して、DLPackを除いてすべてバイトストライドを使用している
  2. 4つのレイアウトにおいて、0または負のストライドおよび、一意ではないレイアウトを許容している
    • layout_strideは常にそのマッピングが一意(単射)である必要がある
  3. 4つのレイアウトにおいて、要素0の配列(エクステントの積が0)についてストライドの値に要件が無い
    • layout_strideは0を許容しない

この提案ではこのうち3のみが解決されます。残りの2つはlayout_strideの設計選択による意図的な制限であるため変更しません。

Pythonの多次元配列フォーマットでは、エクステントが0の場合対応するストライドを0にするという慣習があるようで、layout_strideのこの制約はPython開発者にとって直観的ではない可能性があります。

layout_strideのこの制約は、BLAS/LAPACKなどのライブラリが通常0ストライドを許容しないことから来ているようです。そのため、この制限を緩和した場合<linalg>の実装やmdspanによるBLAS/LAPACKライブラリラッパでは0ストライドをもつlayout_strideが入力されることを考慮する必要があります。

一方、この要件を緩和した後でもlayout_strideの設計要件が壊れることはありません。提案ではその簡単な証明がなされています(layout_strideによるマッピングは全単射である必要があり、0ストライドを許容しても空集合から空集合の写像が全単射であることからこれが維持される)。

P3960R0 Define copy-constructibility-from-bytes

バイト列を単にコピーするだけでオブジェクトを有効にコピーすることができるクラス型を判別する型特性の提案。

C++26では並行アルゴリズムのRange版が追加されたことで、Rangeアダプタを適用した結果のviewを用いて並行アルゴリズムを利用しやすくなっています。並行アルゴリズムはExecutionPolicyで指定した実行方法によって、ホストのCPU以外のHW(アクセラレータ)で実行される可能性があります。最も典型的な例はGPUです。

GPUの様なアクセラレータはほとんどの場合、ホストのCPUとは独立したメモリを持っています。そして、アクセラレータとホストはお互いのメモリ領域に相互的にアクセスすることができないか、できても著しく速度が遅い場合がほとんどです。

このようなアクセラレータ上で並行アルゴリズムを実行しようとする場合、実行ステップの最初にまず並行アルゴリズムの引数をすべて(範囲の要素列そのものやイテレータ、viewオブジェクト、追加の値引数やcallable引数など)アクセラレータのメモリにコピーする必要があります。このコピーはmemcpyと同様の動作をする実装定義の関数を用いて行われ、これはバイト単位かつビットを保ったコピーになります。

このようなコピーが不可能である場合、並行アルゴリズムの起動時にその引数のシリアライズとデシリアライズを行ってアクセラレータのメモリ上で引数列を再構成する必要があります。C++26のリフレクションが無い場合このようなことは標準的な方法では行えず、またmemcpyと比較するとはるかに大きなオーバーヘッドがあります。

結果として、アクセラレータ向けの並行Rangeアルゴリズム実装においては、引数のバイトコピーが可能ではない場合にホストのCPUによる実行にフォールバックする実装を取ることになります。範囲の要素がすでにGPUメモリにある場合、これは逆方向のオーバーヘッドとなります。

特に、transform_viewzip_transform_viewなどのview型において、受け取るラムダ式がキャプチャをしているかどうかによってそのview型のトリビアルコピー可能性が変化してしまうことが問題となっています(この点については、少し下のP3963R0で詳しく解説しています)。

並行アルゴリズムの起動を試みる前にそのすべての引数が参照するデータそのものはアクセラレータのメモリにコピーされているものとすると、並行アルゴリズムの引数列(イテレータ/view、関数オブジェクト)は実データに対してその処理対象を表すメタデータとなります。このメタデータに対して、アクセラレータ上で並行アルゴリズムを動作させる場合は次のようなことが行われます

  1. アルゴリズムの引数列を格納するためのメモリ領域を確保
  2. 引数をバイト単位でアクセラレータメモリにコピー
  3. 並行アルゴリズムのカーネルメイン関数をアクセラレータ上で実行
    • この関数内では、ホスト上で保持されていた時と同じ値を持ち、かつ通常通りに生存期間が開始された状態で引数を取得する

ステップ3において、アクセラレータ上でコピーされた引数列を取得するための要件は次の2つです

  1. ホスト上で保持していたものと同じ値を持つ
  2. 生存期間が開始されている

1の要件に最も合致するのはトリビアルコピー可能性です。しかし、2の要件を加味すると少し考慮すべきことが増えます。

トリビアルコピー可能性は2つのオブジェクトの間でバイト単位のコピーを行えることは保証するものの、コピー先のオブジェクトの生存期間が開始されることは保証しません。そのためには、あらかじめオブジェクトの生存期間を開始しておくか、コピーしてから生存期間を開始する必要があります。

あらかじめオブジェクトの生存期間を開始しておく場合はデフォルトコンストラクタを呼び出す必要がありますが、そのためにはさらにデフォルト構築可能性を要求することになります(さらにはデフォルトコンストラクタが何もしない事を要求するためにはトリビアルデフォルト構築可能性を要求する必要がある)。

C++20からはこのような場合に最適なimplicit-lifetime typesというカテゴリの型があり、implicit-lifetime typeはストレージが確保された後特定の操作(標準関数によるメモリ確保、memcpystd::start_lifetime_asなど)の後に生存期間が自動的に(コンストラクタ呼び出しを必要とせずに)開始されます。

transform_viewzip_transform_viewなどのview型がしばしばトリビアルコピー可能ではなくなるのは、単純にはCallableの保持のために使用しているmovable-boxのコピー代入演算子の削除基準にあります。保持対象のコピー代入演算子が削除されている場合、movable-boxはトリビアルではないコピー代入演算子を定義します。この非トリビアルコピー代入演算子によって、movable-boxはトリビアルコピー可能ではなくなります。ただしこのことは、movable-boxがより多くの場合にcopyable性を確立するために重要な性質であり、view型の扱いやすさに直結しています。

この提案では、view型のシリアライズ/デシリアライズの様な複雑かつ課題の多いソリューションもmovable-boxの現在の有用な性質を破壊する変更も行わず、かつシンプルな解決策として代入演算子の存在に左右されずにバイトコピー可能性を保証したimplicit-lifetime typesに含まれる、新しい型のカテゴリを追加することを提案しています。

提案ではこのカテゴリの型のことをcopy-constructible-from-bytesと呼んでいます(trivial copy-constructibleが最適ですが、それはすでに使われているためこの名前になった)。あるクラス型が次の条件を満たす場合、その型はcopy-constructible-from-bytesクラス型となります

  1. 少なくとも1つの資格のあるコピーコンストラクタを持つ
  2. それらの資格のあるコピーコンストラクタはすべてトリビアルである
  3. 資格のあるムーブコンストラクタが存在する場合、それらはすべてトリビアルである
  4. トリビアルで削除されていないデストラクタを持つ

そして、スカラ型、copy-constructible-from-bytesクラス型、あるいはそれらの配列型(またはこれらのCV修飾型)をcopy-constructible-from-bytes typesと定義します。

この条件はトリビアルコピー可能性から代入演算子の要件を無くして条件を緩和したものであり、copy-constructible-from-bytes typesな型はトリビアルコピー可能な型でもあります(逆はかならずしも成り立ちません)。

このcopy-constructible-from-bytes typesは自然にimplicit-lifetime typesになります(implicit-lifetime class型の非集成体の場合の要件を常に満たすため)。

そして、copy-constructible-from-bytes typesはバイト列からコピー構築可能であることを保証します。これにより、次のように既存のオブジェクトからコピーされた値表現のみから暗黙的にオブジェクトを作成できるようになります

void* dst_mem = std::malloc(sizeof(T)); // ストレージ確保
std::memcpy(dst_mem, &src, sizeof(T));  // 値表現のバイトコピー
T* dst_ptr = std::start_lifetime_as<T>(dst_mem);  // オブジェクト作成(コンストラクタを呼び出していない

この特性を持つ型の検出のためにstd::meta::is_copy_constructible_from_bytes()メタ関数とstd::is_copy_constructible_from_bytes型特性を追加することも提案しています。

最後に、std::start_lifetime_as()およびstd::bit_cast()の動作をcopy-constructible-from-bytes typesに対して拡張します。これらの関数の保証は、現在トリビアルコピー可能な型についてのみ制約されているため、これをcopy-constructible-from-bytes typesに緩和します。

copy-constructible-from-bytesクラス型においては、資格のあるコピーコンストラクタがトリビアルであることが要求されており、これが満たされるためにはそのメンバの(その資格のあるコピーコンストラクタから参照される)コピーコンストラクタが全てトリビアルである必要があります。これにより、copy-constructible-from-bytes typesだけをメンバに持つ型はcopy-constructible-from-bytes typesになります。

P3961R0 Less double indirection in function_ref (RU-220)

std::function_refが二重化するのを抑制する提案。

std::function_refは関数引数などで使用して、Callableの型消去を可能にする軽量なCallableラッパ型です。これは名前にあるように、渡されたCallableを所有せずに参照することで、std::function等に比べて軽量かつオーバーヘッドの小さい型消去を可能にします。

現在の仕様だと、std::function_refstd::function_refを渡すとstd::function_refのアンラップを行わずに参照の参照のような状態で保持してしまう問題があります。std::function_refは再代入可能な参照をモデルとしている点で言語参照とは異なりますが、言語参照と同様に参照は参照を参照するべきではありません。

LWG Issue 4264においてその解決のベースラインが提示されており、この提案は、その変更を調整してnoexceptの互換性を考慮した変換を行うようにするものです。

まず、LWG Issue 4264の変更においてはstd::function_refstd::function_refを用いて初期化する場合(呼び出しシグネチャの互換性はあるとして)、初期化元のstd::function_refをアンラップして(そのメンバを直接的にコピーして)構築するかどうかは未規定としています。これはstd::function_refの二重化を防止することは意図しているもののこの領域において将来別の種類の最適化を導入する余地を残すためのものです。未規定としているため、std::function_refの利用者はstd::function_refが二重(あるいはそれ以上)になっている挙動に依存することは利用者の責任となります(実装や将来のバージョンで変更されうる)。

void f1(std::string);
void f2(std::string);

std::function_ref<void(std::string)> r1(&f1);
std::function_ref<void(std::string&&)> r2(r1);

r2("");   // f1が呼ばれる
r1 = &f2;
r2("");   // f1とf2のどちらが呼ばれるかは未規定

この提案ではそれに加えて、noexceptの互換性がある場合のstd::function_refからの構築・代入を許可するようにしています。

void foo(int);
void bar(int) noexcept;

// 素の関数ポインタにおける変換
void core_language() {
  auto p1 = foo;
  auto p2 = bar;

  p1 = p2;  // ok
  p1(3);    // barが呼ばれる
  p2 = p1;  // error
}

// function_refにおける変換
void library() {
  std::function_ref r1 = foo;
  std::function_ref r2 = bar;

  r1 = r2;  // ok(この提案
  r1(3);    // barが呼ばれる
  r2 = r1;  // error
}

この提案の変更により、このように素の関数ポインタの変換動作と一致するようになります。また、おそらくこのケースにおいてはstd::function_refの二重化防止は未規定ではなく指定された動作になります。

関数ポインタ/std::function_refの関数シグネチャにnoexceptが指定されている場合noexceptな関数ポインタ/Callableしか代入できなくなり、指定されていない場合はnoexceptあり無しどちらも代入可能になります。

具体的にはまず、次のような判定を行うメタ関数を導入します

template<class F>
static constexpr bool is-convertible-from-specialization = see below;

このis-convertible-from-specialization<F>

  • Fが任意のcv2noexc2によるfunction_ref<R(Args...) cv2 noexcept(noex2)>の特殊化である場合
    • is_convertible_v<R(&)(Args...) noexcept(noex2), R(&)(Args...) noexcept(noex)> && is_convertible_v<int cv&, int cv2&>
  • そうではない場合
    • false

となるbool値です。cv2noexc2はコンストラクタ/代入演算子の引数に渡されているstd::function_refのもので、cvnoexは構築/代入しようとしているstd::function_refのものです。

このis-convertible-from-specialization<F>trueとなる場合というのはFstd::function_refの呼び出しシグネチャのnoexcept指定(ありにはありのみ変換可能、なし/noexcept(false)はどちらからも変換可能)とCV修飾(constありはどちらも、なしはなしからのみ変換可能。Vも同様)に互換性がある場合です。

これを任意のF&&から構築するコンストラクタの構築時に使用して、falseならばアンラップ等せずに通常通りに引数の参照を保持し、trueとなる場合はアンラップ動作を指定します。

ただし、is-convertible-from-specialization<F>falseとなる場合でもremove_cvref_t<F>std::function_refの特殊化である場合(シグネチャは問わない)は、構築されたstd::function_refが引数のstd::function_refをアンラップしているかどうかは未規定としています(書き方的にはもしかしたら、is-convertible-from-specialization<F>の結果によらず未規定かもしれません。読み取れず...)。

P3962R0 Implementation reality of WG21 standardization

C++処理系実装者による標準仕様とその実装においての課題と、改善策の提案。

この文書は、2025年11月に行われたKona会議において、C++の処理系(コンパイラ、ライブラリ、その他?)の実装を担当しているメンバーを集めて行われた非公開の議論においての要点をまとめ、それら課題への対応についての提案をするものです。

主なトピックは次のものです

  1. コストとリソース、および経済的現実
    • ここの提案において、実装・保守・テスト・サポートにかかるコストが考慮されていない
    • コンパイラおよびライブラリ実装者のリソースは著しく制約されている
    • 新機能が追加されると、必然的にバグ修正・適合性の向上・パフォーマンスのチューニング・ライブラリの機能強化などの他の作業のリソースが削減される
  2. 実装者の声と実装複雑性の理解
    • 特に設計の初期段階において、実装者の役割と専門知識がプロセスに十分に反映されていない
    • 実装に関するフィードバックは後から遅れて導入され、しばしば敵対的なものあるいは設計の障害として扱われてしまう
    • 簡単に実装できるとする提案でも、実際のコードベースやツールチェーン全体の中での相互作用はそれほど簡単ではない場合がある
    • 実装経験は、実運用のためにコードベースへマージし、保守、テスト、展開をするために必要な労力を反映していない
  3. コア言語機能の設計(EWG)作業と標準文言策定(CWG)作業への参加における課題
    • 実装者はEWG/CWGの両方に継続的に関与し続けることが非常に困難
    • 提案の量や平行するグループの数、議論のペースは限られた実装者のリソースに大きな負担をかけている
    • 実装者は初期設計に関する議論に有意義に貢献できなくなっており、実装上の懸念事項が設計が進んだ後に表面化することがある
    • 逆に、設計議論は後で文言策定時に対処する必要がある意味論的制約に関する適切な理解や指摘がないまま進行する可能性があり、手戻りやグループ間の意識のずれのリスクが生じる
  4. 委員会の優先事項、ユーザーニーズ、および経営陣の期待との整合性
    • 委員会が標準化を目指す機能とユーザーや組織が採用したいと考えている機能との間に大きなギャップが生じている
    • 最終的に何が実装され、展開されるかは経営陣の決定に大きく左右される
    • 委員会が採用した機能であっても組織の優先事項やユーザーニーズに合致しなければ実装されない可能性がある
  5. 新機能とバグ修正、そして移植性
    • 新機能の開発は、既存の欠陥や未解決のコア言語の問題への対応に費やす時間を奪うだけでなく、標準規格に新たな欠陥を追加することもある
    • 未解決の問題は実装の差異に繋がり、移植性に影響を与える
    • 新機能がそれを実装し統合・調整される速度よりも早く積み重なると、不完全または矛盾した基盤の上に機能が積み重なり、移植性のリスクが高まる

これに対して今後の行動についての推奨事項を提示しています

  1. コストとトレードオフの可視化
    • 委員会は、コスト・リソース・技術的負債の影響をより明確にするための仕組みの検討、あるいはリリースごとの総コスト予算の策定に着手してほしい
    • 標準規格に機能を追加することが最終目標ではないことを理解し、利用可能なリソースについてより現実的に考える必要がある
  2. 実装者からのフィードバックを早期に取り入れる
    • 委員会は、実装者からのフィードバックをプロセスの早い段階で収集する方法を検討してほしい
    • その方法として、advisory implementer study group を設置することが考えられる
    • このようなグループは提出される提案に対して次の例のような情報を提供できる
      • 仕様通りにこの機能は実現可能か?
      • 特定の実装におけるこの機能のコストはいくらか?
      • 特定の顧客層や経営陣からこの機能に対する要望はあるか?
      • 参照実装に関するフィードバック
    • このようなSGを設置するもう一つの利点は、実装者間の連携が促進されること
  3. ペース調整とリリースの重点化
    • 委員会は、標準規格への機能追加ペースを緩める方法を検討してほしい
    • これにより、実装者が追いつく時間を確保し、実装品質の向上を図ることができる
    • 標準化サイクルを延期するか、機能追加に重点を置いたリリースと統合に重点を置いたリリースを交互に行うことを検討してほしい
    • また、新機能開発と並行して、不具合の解消・規格への適合性・移植性に関する作業を明確に優先すべき
  4. スケジュールと参加の重複を減らす
    • 委員会は、可能な限りEWGとCWGを並行して運営しないでほしい
    • これにより、詳細な知識を持つメンバーが設計の初期段階から関与でき、また通常は設計にしか関与しないメンバーが文言策定に関与する時間を費やすことができるようになる
    • このような体制は委員会内で特定の機能に対する焦点の一貫性を高めることにもつながる

これは明確な提案というよりは、委員会の運営体制への提言です。

P3963R0 Assignable lambdas with capture

キャプチャしているラムダ式のクロージャオブジェクトを、代入可能にする提案。

C++20より、キャプチャしていないラムダ式のクロージャ型にはデフォルトコンストラクタとコピー/ムーブコンストラクタが定義されます。一方、キャプチャをしているラムダ式は逆にデフォルトコンストラクタとコピー代入演算子は(それによってムーブ代入演算子も)delete定義されます。

このことはRangeライブラリのview型(特にラムダ式を頻繁に受け取ることになるviews::transform)において影響があり、view型のトリビアルコピー可能性を阻害する場合があります。

std::vector<int> v = {1, 2, 3, 4, 5};

// キャプチャしていないラムダ式のクロージャ型はトリビアルコピー可能
auto captureless = [](auto x) { return x + 1; };
static_assert(std::is_trivially_copyable_v<decltype(captureless)>); // ok

// キャプチャしているラムダ式のクロージャ型もトリビアルコピー可能
auto with_capture = [y = 1](auto x) { return x + y; };
static_assert(std::is_trivially_copyable_v<decltype(with_capture)>); // ok

// この transform_view 型はトリビアルコピー可能
auto view1 = v | std::views::transform(captureless);
static_assert(std::is_trivially_copyable_v<decltype(view1)>); // ok

// この transform_view型はトリビアルコピー可能ではない
auto view2 = v | std::views::transform(with_capture);
static_assert(std::is_trivially_copyable_v<decltype(view2)>); // ng 👈

トリビアルコピー可能性においては、コピーコンストラクタかコピー代入演算子(あるいはムーブのそれら)のうち1つでも有効(使用可能かつユーザー定義されていない)であればよいため、代入演算子が削除されていてもコピーコンストラクタが利用可能であれば(キャプチャしているラムダ式の場合はコピー/ムーブコンストラクタはdefault宣言されている)トリビアルコピー可能となります(そのほかの条件は満たしているものとして)。

そのためwith_captureの型はトリビアルコピー可能となります。

しかし、それを保持するtransform_viewがトリビアルコピー可能ではないのは、transform_viewが渡されたCallableをmovable-boxという型で保持しているためで、movable-boxは保持する型がcopyableのモデルではない場合に非トリビアルなコピー/ムーブ代入演算子を持つためです。

この場合、with_captureの型はトリビアルコピー可能でありながらコピー代入演算子はdeleteされているため、transform_viewmovable-boxはトリビアルコピー可能にならず、それを直接保持してるtransform_view型もトリビアルコピー可能ではなくなっています

通常のRangeアダプタの使用ではこのことは問題になりませんが、並行Rangeアルゴリズムの場合に影響があります。並行アルゴリズムの場合、実行方法によっては渡されたCallableオブジェクトを実行場所(各スレッドなど)へコピーする必要があり、その際にトリビアルコピー可能ではないとmemcpy相当の単純なコピーができず、コピーする分のコピーコンストラクタ呼び出しが伴ってしまいます。

この提案はこの問題の対策のために、キャプチャしているラムダ式のクロージャ型は、手描きの関数オブジェクトと同様の規則によって代入演算子を定義するようにすることを提案しています。

すなわち

  • コピー代入演算子: すべてのキャプチャ(コピーキャプチャに対応するメンバ変数)がコピー代入可能である場合、クロージャ型のコピー代入演算子はdefault定義されるべき
    • そうでない場合は削除されるべき
  • ムーブ代入演算子: すべてのキャプチャ(コピーキャプチャに対応するメンバ変数)がムーブ代入可能である場合、クロージャ型のムーブ代入演算子はdefault定義されるべき
    • そうでない場合は削除されるべき

とすることを提案しています。

このようにすると先ほどのmovable-boxの場合でも、コピー代入演算子がdefault定義されることによってクロージャ型はcopyableを満たし、movable-boxもコピー代入演算子をdefault定義することができるようになります。

// この transform_cview型もトリビアルコピー可能になる
auto view = v | std::views::transform([y = 1](auto x) { return x + y; });
static_assert(std::is_trivially_copyable_v<decltype(view)>); // ok(この提案による

提案文書より、コピーキャプチャの例

int y = 1;
auto f = [y](int x) { return x + y; };
auto g = f;  // 現在もok
g = f;       // 現在はng、この提案ではok

static_assert(std::is_copy_assignable_v<decltype(f)>); // 現在は失敗する、この提案ではパスする
static_assert(std::is_move_assignable_v<decltype(f)>); // 現在は失敗する、この提案ではパスする

参照キャプチャの例

int y = 1;
auto f = [&y](int x) { return x + y; };
auto g = f;  // 現在もok
g = f;       // 現在はng、この提案でもng

static_assert(std::is_copy_assignable_v<decltype(f)>); // 現在は失敗する、この提案でも失敗する
static_assert(std::is_move_assignable_v<decltype(f)>); // 現在は失敗する、この提案でも失敗する

参照キャプチャの場合(正確にはメンバが未規定になるものの)クロージャ型は参照メンバを持つことになり、参照メンバを持つクラス型は代入演算子が暗黙deleteされます。これは、参照そのものの再代入が不可能であるためです。

この提案はEWGのレビューをパスしてCWGでレビュー中です。

P3965R0 2026-01 Library Evolution Polls

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

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

P3505R2はDRとしてC++29を目指しており、他のものはNBコメント解決としてC++26をターゲットにしています。

P3967R0 Dual compiles of functions with contracts

単一のコンパイルコマンドで、チェックされる契約アサーションとされない契約アサーションの両方をコンパイルするようにする提案。

この提案は、C++26 Contracts機能が契約アサーションのセマンティクスを十分に制御できないことによる問題を解決するために、一回のコンパイルでチェックされるコードとされないコードをコンパイルし、マングル名で区別して同居させるとともに、エントリポイントにおいてチェックされるされないをブロックによって明示的に指定するようにすることを提案するものです。

bool log(const char* msg)
{
  std::cout <<msg <<std::endl;
  return true;
}

void subfunc()
  pre(log("Running subfunc"))
{
}

void func()
  pre(log("Enter func"))
  post(log("Leave func"))
{
  subfunc();
  unchecked_contracts {
    subfunc();
    checked_contracts {
      subfunc();
    }
  }
}

int main()
{
  func();

  unchecked_contracts {
    func();
  }
}

出力

Enter func
Running subfunc
Running subfunc
Leave func
Running subfunc

この例は例示のために契約アサーションで副作用を伴うコードを実行していますが、契約アサーションでそれらの関数を呼べるようにする意図はこの提案にはありません。

main()においてはチェックあり無しどちらのコードを呼び出すかをunchecked_contracts/checked_contractsブロックを用いて選択できます。unchecked_contractsブロック内では、チェックされないコードが呼び出され、checked_contractsブロックではチェックされるコードが呼び出されます。

チェックされるされないの実行時の選択は、thread_localなチェックモード変数を用いて行われます。各関数呼び出しはコンパイル時にこのチェックモード変数を参照してディスパッチを行うサンク関数を経由して呼ばれるようになり、関数ポインタ経由の呼び出しや仮想関数の呼び出しにおいても適切にチェックされる/されないをディスパッチすることができます。

この提案による実装においては、コンパイル後のバイナリには契約チェックの有無で1つの関数あたり2つの実体が含まれるためバイナリサイズが単純に倍になり、コンパイル時間もその分増加します。また、サンクとチェックモード変数によるディスパッチはわずかながら素の関数呼び出しと比較してオーバーヘッドがあります。提案では、LTCGやチェックモード変数のレジスタ配置などの最適化によってこれらのデメリットを緩和できるとしています。

また、このような変更と相いれなくなるため、コンパイラはある翻訳単位内のすべての契約アサーションについてignore/observeセマンティクスでコンパイルする機能を提供すべきではないことも提案しています。

P3968R0 A Framework For Contracts

契約アサーションをユーザー定義型で実現する提案。

P2900で提案されているC++26のContracts機能には様々な批判が投げかけられていますが、その中にセマンティクスの選択にあいまいな点が多い(標準外のコンパイラオプションに委ねられている)ため、プログラマが意図した契約アサーションのセマンティクスが選択されない可能性がある、という問題があります。

この提案はこの問題の解決のために、契約アサーションのセマンティクスをクラス型で実装・表現して、そのconstexprオブジェクトを契約アサーションとして扱うライブラリベースソリューションを提案しています。

namespace myns {

  struct MyAssertionType {
    // The existence of the assertion_object_kind data member is used to
    // determine that objects of this type are usable as contract assertions. This can be
    // detected using a concept or innately by the compiler.
    int assertion_object_kind; // pre, post or assert.

    // These data members, if they exist, modify the core language behavior, reverting to
    // these P2900-compatible defaults if they don't.
    bool constify; // Constify condition if true.
    bool ignorable; // Contract assertions are ignored in unchecked builds.
    bool assumable; // Don't assume predicate was true in following code.

    void operator()(string_view comment, exception_ptr ep, int,
                    source_location loc = source_location::current()) const {
      if (ep == nullptr)
        std::println(f"Assertion {comment} at {loc} failed.");
      else
        std::println(f"Exception evaluating assertion {comment} at {loc}");
      abort();
    }
  };

  inline constexpr MyAssertionType myPre {
    .assertion_object_kind = 1, // Values are the same as in the assertion_kind enum.
    .constify = false, // I don't want constification in my contract assertions.
    .ignorable = false, // These contracts can't be ignored.
    .assumable = true // Assume the contract was not violated in succeeding code.
  }

  void myFun(int v) myPre(v > 0);
}

このmyPreはアサーションオブジェクトと呼ばれるオブジェクトで、その型はアサーションオブジェクト型と呼ばれるクラス型です。この例にあるようにアサーションオブジェクト型では特定のメンバを定義しておくことでその識別とセマンティクスの制御を行うことができ、特にoperator()では契約が破られた場合の振る舞いをユーザーコードで指定し制御することができます(すなわち違反ハンドラにあたります)。

とはいえ機能の全体がユーザー定義である訳では無く、関数宣言においてアサーションオブジェクトを識別することおよび、アサーションオブジェクトを契約アサーションとして扱ってその評価を(必要なら)行ってその結果に応じてアサーションオブジェクトのoperator()を呼び出すコードを生成するのもコンパイラの役割です。

アサーションオブジェクトとその型をそれと認識する方法は、assertion_object_kindメンバが存在しint型に変換可能であることによってコンセプト的に検出することを提案しています。

アサーションオブジェクトが識別されて以降、必要なメンバが見つからないとか、名前検索におけるエラー、多重定義時の挙動などはほぼ従来のC++の関数や変数におけるルールに従うことになります。

このような方法により、契約アサーションの評価セマンティクスの柔軟な制御とその選択の確実性を両立することができます。また、同時にContracts機能がライブラリ機能である<contracts>に依存しなくなるというメリットもあるとしています。

この提案によるフレームワークを用いることでC++26のContracts機能を再実装することができ、それによって後方互換性を保つことができるためC++29以降にこの提案を導入しても破壊的変更にはならないとしています。ただし、非互換な部分があるなどの理由により、この提案を将来の方向性として採択する場合はP2900をC++26から削除することを推奨しています。

namespace std::contracts {
  struct assertion_object_t {
    assertion_kind assertion_object_kind;
  
    bool constify;
    bool ignorable;
    bool assumable;
    
    void operator()(string_view comment, evaluation_semantic semantic, exception_ptr ep,
                    source_location loc = source_location::current()) {
      if (semantic != evaluation_semantic::quick_enforce) {
        contract_violation violation(comment,ep, assertion_object_kind, loc, semantic);
        handle_contract_violation(violation);
      }
      if (semantic != evaluation_semantic::observe)
        abort();
    }
  };
}

// In the global namespace
inline constexpr std::contracts::assertion_object_t pre {
  .assertion_object_kind = std::contracts::assertion_kind::pre,
  .constify = true,
  .ignorable = true,
  .assumable = false
};
inline constexpr std::contracts::assertion_object_t post {
  .assertion_object_kind = std::contracts::assertion_kind::post,
  .constify = true,
  .ignorable = true,
  .assumable = false
};
inline constexpr std::contracts::assertion_object_t contract_assert {
  .assertion_object_kind = std::contracts::assertion_kind::assert,
  .constify = true,
  .ignorable = true,
  .assumable = false
};

おわり

この記事のMarkdownソース

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

文書の一覧

全部で113本あります。

もくじ

N5011 Brno 2026

2026年06月に予定されている全体会議の案内。

2026/06/08~2026/06/13にチェコのBrnoで開催される予定です。

文書では会場や行き方などの案内がされています。

N5029 WG21 2025-10 Kona Admin telecon minutes

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

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

N5031 WG21 2025-11 Kona Minutes of Meeting

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

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

N5032 Working Draft, Standard for Programming Language C++

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

N5033 Editors' Report - Programming Languages - C++

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

P1317R2 Remove return type deduction in std::apply

std::applyの戻り値型推論をやめる提案。

以前の記事を参照

このリビジョンでの変更はよくわかりませんが、文言の変更のみのようです。特に、R1で提案していた関連するコンセプトが削除されています。

この提案は2025年6月の全体会議でC++26に向けて採択済みです。公開を忘れていた様子です。

P1789R2 Library Support for Expansion Statements

P1789R3 Library Support for Expansion Statements

std::integer_sequenceを構造化束縛および展開ステートメントで使用できるようにする提案。

以前の記事を参照

R2での変更は

  • 関連する機能テストマクロを更新
  • NBコメント対応
    • NCIT-002
    • FR 007-011-142
    • CZ 2-143

これらのNBコメントはいずれも、この提案をC++26に入れることを推奨するものです。

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

  • LWGレビューを反映
  • 不要なコメントの削除
  • const integer_sequenceの特殊化を追加

などです。

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

P2243R0 Language linkage for templates

テンプレートに対するCリンケージ指定を許可する提案。

言語リンケージの指定とはextern "C"のような指定を宣言に付加したり、あるいはブロックに付加したりすることを言います。これはエイリアス宣言や関数宣言、変数宣言などに対して行うことができますが、テンプレートに対する言語リンケージの指定は明確に禁止されています。

ただし、現在の規定ではこの「言語リンケージ(language linkage)」という言葉の指すものが非常にあいまいであり、特にテンプレートに対する言語リンケージという言葉がほとんど定義されていないという問題があります。

関数に対する言語リンケージとは、ABIにおける呼び出し規約を反映したものになっています。

// C++言語リンケージの関数宣言
void f();

// `cf`は「voidを返すCの関数」型
extern "C" typedef void cf();

cf *p = f;  // ng: 型が合わない

(ただし一部の実装ではこれを受け入れるようです)

すなわち、関数に対する言語リンケージの指定は、その関数の呼び出し規約を指定することに対応しています。

一方、変数に対する言語リンケージは、ABIにおける名前マングルを反映したものになっています。

namespace N {
  typedef void* mk(int);      // C++言語リンケージで型`mk`(関数型)を用意
  extern "C" mk make_vector;  // 変数宣言、"C"言語リンケージは関数型には適用されない
  void* make_vector(int i)    // 再宣言は言語リンケージを保持する
  {
    return new std::vector<int>(i);
  }
}

int make_vector;              // ng、N::make_vector と衝突する

グローバル名前空間スコープの変数に対しては名前マングルが行われないため、この衝突が起きています。

すなわち、変数に対する言語リンケージの指定は、その変数名の名前マングル方法を指定(C++マングルの抑制)することに対応しています。

ちなみに、グローバル名前空間スコープの変数名に対するこの規則はなぜか演算子関数に対しても同様に適用されます。

struct A {};
struct B {};

extern "C" {
void operator+(A);
void operator+(B);  // ng、関数型が衝突している
}

一方、ユーザー定義リテラルではこのようなC言語リンケージの指定が禁止されています。

演算子関数はCリンケージで呼び出されることはないので、本来は不要なはずです(このことは言語リンケージというものの曖昧さを表しています)。

そして、テンプレート(関数・変数・クラス・コンセプトを含む)は一律に言語リンケージの指定が禁止されています。ここでの言語リンケージが何を指す(上記のいずれの意味なのか、また別の意味なのか)のかは特に規定されていません。

テンプレートは特殊化ごとに異なる名前を持つことになる(独自の名前マングル方法を持つ)ため名前マングルの指定は不要です。一方、呼び出し規約の指定の側面は有用である可能性があります。しかし現在はその区別なく禁止されているため、関数テンプレートや関数型を示すエイリアステンプレートに対して呼び出し規約を指定することができなくなっています。

例えば次のように、いくつかの種類のオブジェクトを受け取るC APIがあるとき

/* getters.h */
#ifndef GETTERS_H
#define GETTERS_H

#ifdef __cplusplus
extern "C" {
#endif

struct things {unsigned a,b;};

void get_int(int*);
void get_int_fast(int*);
void get_float(float*);
void get_things(struct things*);

#ifdef __cplusplus
}
#endif

#endif

これを次のようにラップして使いたくなることもあるでしょう

#include"getters.h"

// ラップ関数
namespace wrap {
  // C言語リンケージを持つ関数ポインタ型
  extern "C" typedef void (*int_getter)(int*);

  int get_int(int_getter f) {
    int ret;
    f(&ret);
    return ret;
  }
}

// 使用例
namespace client {
  void g() {
    auto val = wrap::get_int(get_int_fast);
    // ...
  }
}

これを一歩進めてテンプレート化したくなるかもしれません

namespace wrap {
  
  template<class T>
  T get(/* ... */ f) {
    T ret;
    f(&ret);
    return ret;
  }
}

このとき、このget<T>の引数型(fの型)を正しく記述することが非常に困難になります。なぜなら、void (*f)(T)と書くとこれはC++リンケージを持つ関数ポインタ型となってしまいC言語リンケージを持つ関数ポインタ型として記述できないためです。

テンプレートに言語リンケージを指定できないことから、エイリアステンプレートとして記述することもできません。

namespace wrap {
  extern "C"
  template<class T>
  using getter=void(*)(T);    // ng、テンプレートは言語リンケージを持てない

  template<class T>
  T get(getter<T> f) {
    T ret;
    f(&ret);
    return ret;
  }
}

このように、関数テンプレートおよびエイリアステンプレートに対して呼び出し規約を指定するためにC言語リンケージを指定することには価値がある可能性があります。

別の例として、次のようにオブジェクトの破棄の責任を受け取るC APIを考えます

/* cleanup.h */
#ifndef CLEANUP_H
#define CLEANUP_H

#ifdef __cplusplus
extern "C" {
#endif

void transfer_ownership(void*,void(void*));

#ifdef __cplusplus
}
#endif

#endif

これもまたラップしてデストラクタで使用できるようにしたくなることもあるでしょう

#include "cleanup.h"
#include <memory>

class A {/* ... */};

extern "C" void del_A(void *p) {
  delete static_cast<A*>(p);
}

void transfer_A(std::unique_ptr<A> a) {
  ::transfer_ownership(a.release(), del_A);
}

これを一歩進めてテンプレート化しようとすると、同様にC言語リンケージを指定できない問題にぶつかります

// テンプレートには言語リンケージを指定できない
extern "C" template<class T>
void del(void *p) {
  delete static_cast<T*>(p);
}

このように、関数テンプレート自体にもC言語リンケージを指定できると有用である可能性があります。

この提案は、現在のテンプレートに対する言語リンケージの禁止という制限を削除し、テンプレートに対するCリンケージの指定を許可しようとするものです。この提案の後では、上記の2例はそのまま許可されるようになります。

この提案は次のコードを許可するものではなく

template<class>
extern "C++" void f() {}  // ng、この提案後も

次のコードを許可するものです

extern "C" {
  template<class T>
  void f(T);

  template<class T>
  void g(void(T));
}

template<class T>
void f(T) {}

template<class T>
void g(void(T)) {}

このコードでは、関数テンプレートfを1つ宣言(定義)し、fの特殊化の型はCリンケージを持ちます。一方、関数テンプレートgは2つ宣言されており、そのうちの一つはC関数へのポインタを受け入れ、Cの呼び出し規約を使用します。

この提案はC++29をターゲットにしていますが、EWGではDRとすることで合意が取れているようです。

P2728R9 Unicode in the Library, Part 1: UTF Transcoding

P2728R10 Unicode in the Library, Part 1: UTF Transcoding

以前の記事を参照

R9での変更は

  • コピー不可能なinput_iteratorに関する文言におけるconstバグの修正
  • buf_std::arrayではなくstd::inplace_vectorを使用することで、説明専用のbuf_last_を削除
  • to-utf-view-implの演算子実装を簡略化
  • utf-iteratorto-utf-view-impl::iteratorにリネーム
  • to-utf-view-impl::sentinel型を追加
  • begin()をキャッシュする
  • reserve_hint()を追加
  • input_iteratoroperator==のバグ修正
  • 設計に関する議論を追加
  • UTF-32との間で変換を行う場合にsize()を追加

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

  • bool OrErrorCTPをranges::subrange_kindのようなto_utf_view_error_kind列挙型に置き換え
  • to_utfX_viewクラスをto_utf_viewに統合し、CTADを動作させるためにコンストラクタタグを追加
  • _or_error CPOを使用した場合にempty_viewの値型が正しく設定されないバグを修正
  • CPO templateについて、views::adjacent_transform<N>などの前例があったため新規性があるとする記述を削除
  • begin()をキャッシュしない
  • CPOでcharN_tの配列を拒否する
  • CPOで二重変換の最適化を実装する

などです。

P3064R3 How to Avoid OOTA Without Really Trying (Informational)

C++コンパイラによる実装においては、OOTA問題が発生しないことを解説する文書。

以前の記事を参照

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

  • RFUBとNOOTABOGAのリトマステストを追加
  • 情報提供文書であることを明記
  • 対応する提案文書としてP3692を指定

などです。

P3097R1 Contracts for C++: Virtual functions

契約プログラミング機能において、仮想関数に対する契約の指定をサポートする提案。

以前の記事を参照

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

  • R0にあった他言語の例や代替案などの設計空間の分析に関する記述がP3600R0へ分離された
  • 「Design goals and principles」セクションの追加
  • P2900とこの提案(リビジョン)に関する背景を追記
  • N5014へのリベース

などです。

この提案は一旦はP2900に取り込まれていたものの、EiffelやDで確立され実展開されているモデルから逸脱しており十分な導入と実装の経験が不足している、という懸念が提起され、C++26 Contracts仕様からは削除されC++29以降に向けて再検討することになりました。

再検討のためにContracts機能におけるユースケースや設計空間、およびトレードオフに関する詳細な分析を行い(P3600R0)、その分析に基づいてR0の設計がC++にとって最適なソリューションであるとして再提案しています。ただし、以前とは異なりGCCにおいてこの提案の実装が行われており、それについても言及されています。

したがって、このリビジョンではその提案内容や設計はR0から変化していません。設計根拠がより強化されており、契約アサーション機能を実装しているEiffel/D/Aidaの設計がC++にそのまま採用できるものではないことが詳しく説明されています。

P3099R1 Contracts for C++: User-defined diagnostic messages

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

以前の記事を参照

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

  • "Empty string vs. no string"セクションを追加
    • メッセージが指定されていない場合に、contract_violation::message()から何を返すか?
      • nullptr or 空文字列
      • メッセージが指定されていない事と空文字が指定されていることを区別できるため、nullptrを選択

などです。

P3100R5 Implicit contract assertions

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

以前の記事を参照

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

  • 未定義動作リストに、見落とされていた未定義動作を1件追加

などです。

P3216R2 views::slice

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

以前の記事を参照

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

  • std::optionalの特殊化を追加

などです。

P3220R2 views::take_before

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

以前の記事を参照

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

  • 要素型がtidy-objを満たす(かつview型がborrowed_rangeである)場合にborrowed_rangeとなるようにする
    • tidy-obj: 空のクラスかつトリビアルにデフォルト構築可能かつトリビアルに破棄可能

などです。

P3371R5 Fix C++26 by making the rank-1, rank-2, rank-k, and rank-2k updates consistent with the BLAS

<linalg>の一部の関数を対応するBLAS関数の仕様と整合させる提案。

以前の記事を参照

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

  • LWGレビューを反映

などです。

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

P3388R3 When Do You Know connect Doesn't Throw?

execution::connectによる操作が例外を送出するかどうかを早期に判定できるようにする提案。

以前の記事を参照

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

  • LWGレビューを反映

などです。

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

P3391R2 constexpr std::format

std::format()constexpr化する提案。

以前の記事を参照

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

  • std::to_stringstd::to_wstringconstexprを付加

などです。

P3395R5 Fix encoding issues and add a formatter for std::error_code

std::error_codeをフォーマット可能にする提案。

以前の記事を参照

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

  • std::filesystem::pathと整合性を持つようにwchar_tの文言中の扱いを修正
  • 今後の変更によるABIへの影響について明確化
  • デバッグフォーマットがエラーコード全体に適用される理由を明確化
  • error_categoryのフォーマッタの提案へのリンクを追加
  • 謝辞を追加

などです。

P3400R2 Controlling Contract-Assertion Properties

契約アサーションに対してラベルを指定する機能の提案。

以前の記事を参照

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

  • introductionを再構成
  • 違反ハンドラからラベルオブジェクトにアクセスする際の仕様を修正
  • ベースラインの要素と今後の作業内容を分離
  • 代替設計の方向性について明確化
  • チェックされないセマンティクスを許容しない契約アサーションで発生する問題に対処

などです。

P3412R3 String Interpolation

std::format/std::print向けの引数となるフォーマット文字列と対象引数列の組を生成する、文字列補完リテラルの提案。

以前の記事を参照

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

  • printfのサポートを削除
    • 将来の拡張としては残しておく
  • stringize argumentfリテラルが含まれる場合の文字列化の処理方法に関する説明を追加
  • _Pragmaは式フィールドで使用できることを明確化
  • 式フィールド内のラムダ・statement expression(GCC)・^block(Clang)は式フィールドの終了検出に問題を起こさないことを指摘
  • 生文字列リテラルはfリテラルと特別な方法で相互作用しないことを明確化
    • ただし、フェーズ2(行の連結)の反転処理はfリテラルのうち式フィールド以外の部分で実行されなければならない
  • ユーザーが記述した__format__呼び出しが連結と文字列化に与える影響について議論
  • モジュール関連の相互作用に関するセクションを追加
    • 基本的に影響はない
  • ::がフォーマット指定子を開始できることを指摘(rangeフォーマッタの場合)
    • このリビジョンではこの問題は解決されず、::は一つのpp-tokenとして認識され、フォーマット指定子を開始しない
  • std::formatconstexprになったため、__format__にもconstexprを付加
  • 実装可能性に関する各ベンダからのフィードバックを追加
  • 式フィールドの最上位レベルで条件演算子の使用を禁止する
    • これにより、prefix演算子として?の使用を許可する
      • 例えば値の存在を確認するなど
    • 式フィールドの終了判定も多少簡素化される
  • R2で議論された2つの記述戦略を組み合わせた記述の最初のバージョンを追加
  • cpp_string_format属性を削除し、代わりにオーバーロード解決が失敗するか、暗黙的な変換シーケンスでconsteval関数を呼び出す必要がある場合には、__format__引数リストを展開するようにする
  • デバッグ機能を削除
    • この機能があると、式フィールドの内容を字句解析する前に、フェーズ3で先行する文字列リテラルpp-tokenを出力できなくなる
    • clangでの実装は容易だったものの、フェーズ3でpp-tokenをバッファリングできないコンパイラでは実装不可能

などです。

std::printstd::format()を経由しないためにR3で導入された[[cpp_format_string(N)]]属性はオーバーロード解決フェーズに影響を及ぼすことでそれを実現するものでした。このリビジョンではそれを省いて、単にstd::print(f"Value {1}")のように書いたときにそのままfリテラルを展開してオーバーロードに失敗する場合に、fリテラル展開後の__format__(args...)の引数args...をそこに展開するようにしています。

ただし、std::format()がコンパイル時に呼び出せる場合はコンパイル時のstd::format()で完了させます。

std::string a;

a = f"Value {0}";                                   // #0
std::print(f"Value {a}");                           // #1
std::print(f"Wrong value {1}");                     // #2
std::print("String value: {}", f"Some value {3}");  // #3

これらの使用コードは次のように展開されます

a = __format__("Value {}", 0);                      // #0
std::print("Value {}", a);                          // #1
std::print("Wrong value {}", 1);                    // #2
std::print("String value: {}", __format__("Some value {}", 3));  // #3

P3424R1 Define Delete With Throwing Exception Specification

delete演算子オーバーロードが例外を送出しうる例外仕様を持つことを禁止する提案。

以前の記事を参照

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

  • 最新のWDにリベース
  • 非推奨に関する記述を削除
  • CWG 2024が解決されることを確認

などです。

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

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

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

以前の記事を参照

R2での変更は

  • std::to_charsを修正するオプションを追加
  • __cpp_lib_to_chars, __cpp_lib_constexpr_charconv機能テストマクロを更新

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

  • LEWG投票結果の追加
  • std::formatだけでなくstd::to_charsも修正されていることを明確化
  • オプション1を文言から削除し、LEWGが承認したオプション2のみを残す

などです。

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

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

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

以前の記事を参照

このリビジョンでの変更は編集上の修正のみです。

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

P3642R3 Carry-less product: std::clmul

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

以前の記事を参照

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

  • operator<=>に不足していた<T>を追加
  • §4. Possible implementation にSIMDに適した実装を追記
  • 文言のリベースと改善

などです。

P3657R1 A Grammar for Whitespace Characters

P3657R2 A Grammar for Whitespace Characters

C++規格文書における空白(whitespace)の扱いを明確化する提案。

以前の記事を参照

R1での変更は

  • N5014にリベース
  • NBコメントUS 5-108への対応を明確化
  • 文言を大幅に再構成
  • 動作を変更する可能性のある提案をすべて削除
  • トークンを区切る空白文字に関する記述を削除
  • P2348R3との比較を簡略化
  • C++26 CDに採択されている他の提案との相互作用を記録
  • 縦方向の空白文字の処理に関する今後の方向性を追加

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

  • SG16のフィードバックを反映
  • CWG2002解決を明記
  • 最後のプリプロセッシングトークンが影響を受けない理由を明確化

などです。

P3666R2 Bit-precise integers

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

以前の記事を参照

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

  • §4. Core design にSG22/SG6の投票結果を追加
  • §4.8. Raising the BITINT_MAXWIDTH で提案されていた代替値BITINT_MAXWIDTHを65535から32767へ変更
  • SG22のフィードバックにより、§5.1. Naming of the alias template を拡張
  • §5.4. Preserving integer-class types を追加し、それに伴い文言を変更
  • §5.6. New abs overload および本文全体における戻り値型の欠落を修正
  • §5.15. Passing bit_int into standard library function templates を拡張し、戻り値型に関する考察を追加
  • §5.16. The problem of representing widths as int を追加
  • §5.17. Library policy for function templates accepting bit_int を追加し、提案されたポリシーを §5.6. New abs overload に適用
  • 参照をN3699 から N3747に更新
  • <cmath>だけでなく<cstdlib>にもstd::absのオーバーロードを追加
  • § [utility.intcmp] を追加

などです。

P3684R1 Fix erroneous behaviour termination semantics for C++26

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

以前の記事を参照

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

  • NBコメントGB 02-036の解決を明記
  • 文言の改善

などです。

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

P3688R5 ASCII character utilities

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

以前の記事を参照

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

  • is_ascii_*関数をascii_is_*に変更
  • §3.8. Naming を追加
  • §3. Design において、複数のオーバーロードをすべての関数に対して単一の関数テンプレートに変更する
  • §3.5. Case-insensitive comparison functions において、異なる文字型を混在させることがサポートされていない理由を明記
  • <ascii>のsynopsisにおけるascii_is_digitの不要なデフォルト引数を削除
  • Cの用語に合わせるため、is_ascii_printableascii_is_printingにリネーム
  • Cの用語に合わせるため、is_ascii_graphicalascii_is_graphicにリネーム
  • ascii_is_alphanumericとの不整合を回避するために、is_ascii_alphaascii_is_alphabeticに変更する
  • §5. Wording においてASCII-compatibleの定義を正しくする

などです。

P3692R3 How to Avoid OOTA Without Really Trying

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

以前の記事を参照

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

  • 規格に追加予定の注釈の更新
  • 著者リストの更新

などです。

P3695R3 Deprecate implicit conversions between char8_t and char16_t or char32_t

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

以前の記事を参照

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

  • char8_twchar_tの間の変換の非推奨化を撤回
  • 文言の改善

などです。

P3724R2 Integer division

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

以前の記事を参照

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

  • div_to_even, div_to_odd, div_rem_to_even, div_rem_to_oddを削除
  • §4.4.4. ISO/IEC 60559 Rounding modes を追加
  • div_to_infdiv_to_pos_infにリネーム

などです。

P3726R1 Adjustments to Union Lifetime Rules

トリビアルな共用体を定数式で使用可能にするために制限を緩和する提案。

以前の記事を参照

R0では次の3点を提案していました

  1. 共用体の最初のメンバの生存期間を暗黙的に開始するP3074ルールの撤回
  2. 集成体メンバ変数に対するplacement newは集成体そのものの生存期間を暗黙的に開始する
  3. 構成要素の値(constituent value)のルールを変更し、共用体の配列要素が(親の共用体or配列の)生存期間外にある場合でも扱えるようにする

このリビジョンでは、1を維持して、2をP3074R0で提案された解決策に変更しています。すなわち、std::start_lifetime()を再導入し、暗黙の生存期間をこの関数によって明示的に開始するようにします。

P3735R1 partial_sort_at_most, nth_element_at_most

中間イテレータを指定する必要があるアルゴリズムについて、その必要のないバージョンを追加する提案。

以前の記事を参照

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

  • SG9のフィードバックを反映
  • サフィックス_n_at_mostに変更
  • §4.2. Additional variants of partial_sort を追加
  • 文言から不要なSizeテンプレート引数を削除

などです。

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

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

以前の記事を参照

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

  • LWG4276の採択を踏まえて、議論と文言を更新

などです。

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

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

以前の記事を参照

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

  • 提案する文言の追加

などです。

P3744R0 Explicit Provenance APIs

ポインタのProvenanceを扱うためのライブラリAPIの提案。

CHERI(Capability Hardware Enhanced RISC Instructions)という技術が組み込まれたCPU(ISA)においては、ポインタにいくつかのメタデータを埋め込むことで、ポインタを介したメモリアクセスにおいてメモリ安全性の問題が起こることをハードウェアレベルで防止することができます。

CHERIのメタデータにはいくつかのフィールドがありますが、その内のタグビットはポインタ(CHERIのメタデータ)の改竄を防止するためのフィールドであり、ポインタをコピーしたりする際に自動でセットされ、CHERIメタデータの一部を上書きすると削除されるなどの性質を持ちます。これは結果的に、ポインタのProvenanceの性質のハードウェアによる表現になっています。

CHERIはすでにいくつかのアーキテクチャに組み込まれて実装されており、LLVM/Clangの対応を通してCとC++の実装がすでに確立されています。CHERI環境のC/C++実装では、CHERIメタデータはポインタを用いて表現可能である一方で、ポインタからアドレスを取得してそれを整数として扱う処理においては現在のC++のモデルのままだと問題があることが分かっています。

それは、ポインタ(アドレス)を整数値に変換する際に、後でポインタに戻される操作とポインタに戻されない操作の2つを区別できない点で、現在のC++実装及びモデルでは、どちらの場合も後でポインタに戻される操作とみなして過度な最適化を制限します。

CHERI C++におけるポインタ型はメタデータが追加された分大きくなっており、ポインタのうちアドレスが占める部分はおよそ半分になります(例えばvoid*128bitに対して、size_t64bit)。それにより、Provenanceが不要である場合の整数型はポインタ型のサイズに対してその差の分小さくすることができます。逆に、Provenanceが必要である場合はメタデータを保持する必要性からポインタ型と同じサイズを維持する必要があります。このため、最適化の観点から、ポインタから変換された整数値のうち後でポインタに戻さないもの、を区別できる必要が生まれます。

CHERI C++では、後からポインタに戻されない操作(変換)を区別するためにptraddr_tという専用の整数型を用意しています。ポインタ値がptraddr_tの値に変換された後でポインタに戻されても、デリファレンスできない(CHERIの仕組みによる拒否される)ポインタが生成されることが保証されます。すなわち、ポインタをptraddr_tに変換するということは後からポインタに戻さない事を表明します。

逆に、あとからポインタに戻すような整数値への変換は、現在のC++コードで記述しているのと同じように行えます。すなわち、CHERI C++においてのデフォルト動作はポインタの整数値への変換はあとからポインタに戻すことを仮定するものです。これはポインタの整数型への変換でも同じです。

したがって、CHERI C++のようなハードウェアによるProvenance実装を活用するためには、通常のポインタと整数型の経路とは別の経路でもって、後からポインタに戻されない変換とその操作を区別する必要があります。この提案はこのサポートのためのライブラリAPIを追加しようとするものです。

提案ではまず、ptraddr_t型を導入します。これは、provenance-freeの整数型であり、次の特性を持つものです

  • 異なるオブジェクト(のポインタ)からのptraddr_t値は異なる値となる
  • 同一オブジェクト内の異なるオフセットを指す2つのポインタ(メンバポインタ相当)は、異なるptraddr_t値を生成する
  • 2つのポインタ型をchar*に変換し一方から他方を減算した結果は、両方のポインタをまずptraddr_tに変換してから減算した結果と等値になる
  • ptraddr_tからポインタ型へのキャストは未定義動作となる
    • 実装ではこれらの操作に対して診断を行うことが推奨される

ポインタ型とのサイズ比の違いや後方互換のために、ptraddr_tsize_tとは異なる型として定義される必要があります。

前述のように、ポインタからptraddr_tの値を得るには通常の変換ではなく専用の関数による変換を必要とします。

// <cstdint>
using ptraddr_t = /*ポインタアドレスを保持するのに適した整数型*/;

// ポインタのアドレスを取得する
ptraddr_t get_address(void* p) noexcept;

ポインタpからget_address(p)で整数を取得することは、この整数値を後からポインタに戻すことは無いことを表明します。

ポインタを数値として扱う際に最適化を最大限に活用しつつも、なお後からポインタに戻したいユースケースがあるようです。そのために、ptraddr_tの値をポインタに復帰する関数も用意されます。

// `p`のメタデータで`address`のアドレス値を持つポインタを作成する
template <typename T>
T* with_address(T* p, ptraddr_t address);

// (utility): Maps the address of a pointer
// 
template <typename T, typename UF>
constexpr T* transform_address(T* p, UF f) 
  requires (invokable<UF, ptraddr_t> &&
            same_as<invokable_result_t<UF, ptraddr_t>, ptraddr_t>)
{
  return with_address(p, f(get_address(p)));
}

CHERI C++におけるポインタはCHERIのメタデータを内包しています。メタデータにはタグビットとアドレス以外にも権限や型の互換性を表すフィールドがあります。そのため、アドレス部分のみが取り出されているptraddr_t値をポインタに戻すためにはそのほかの部分のメタデータが必要となり、with_address(p, addr)addrpのメタデータを合成してポインタを復帰する関数です。

ただし、with_address()を用いて間違ったメタデータによって復帰されたポインタのデリファレンスは未定義動作となります。

transform_address()はポインタからptraddr_t値を取得して何か処理をしてまたポインタに戻す、という操作を複合的に行うためのユーティリティです。

この2つの関数は、アトミック操作におけるCASやXORリンクリストなどのように途中で整数値として扱いつつも最後はポインタに戻す場合でパフォーマンスが重要となる用途で使用できるものです。

CHERIのような環境におけるアトミック操作の効率化のために、atomic_refatomicT*特殊化に対してcompare_exchange_(weak|strong)を追加することも提案しています。

// <atomic>

// See below:
template <typename T>
class atomic_ref<T*> {
  constexpr bool compare_exchange_weak(
    T*&, T*,
    T*& provenance_target,
    memory_order, memory_order
  ) const noexcept;

  constexpr bool compare_exchange_strong(
    T*&, T*,
    T*& provenance_target,
    memory_order, memory_order
  ) const noexcept;

  constexpr bool compare_exchange_weak(
    T*&, T*,
    T*& provenance_target,
    memory_order = memory_order::seq_cst
  ) const noexcept;

  constexpr bool compare_exchange_strong(
    T*&, T*,
    T*& provenance_target,
    memory_order = memory_order::seq_cst
  ) const noexcept;
};

// analogous APIs for atomic<T*> and free functions

これらのアトミック操作はABA問題による未定義動作を回避しつつ、ptraddr_tを使用した最適化を適用するものです(提案には実装例があります)。

また、現在その操作がほぼ実装定義とされているintptr_t/uintptr_tの値に対する操作について、ポインタ演算との等価性を保証することを提案しています。ただしこれは、ポインタから変換されたintptr_t/uintptr_tの値に対する操作についてのみ保証し、無関係なintptr_t/uintptr_tの値の演算はptraddr_tの演算と同じ意味論を持つべきとしています。

P3751R0 A gentle introduction to pointer authentication

P3751R1 A gentle introduction to pointer authentication

ARMv8.3で導入されたPointer Authentication Codesと呼ばれる機能についての解説文書。

Pointer Authentication Codesとは、ポインタに対してコンテキストに応じた認証を行い、その認証コードをポインタのビット内に埋め込んで利用する仕組みの事です。これは現在ARMv8.3以降のISAで実装され、利用されています。

Pointer Authentication Codesの存在は、トリビアルリロケーションやポインタの利用されないビットを利用できるようにする提案に影響を与えているものの、まだ登場したばかりの技術であるため知名度が高くありません。

この提案はPointer Authentication Codesの概要と、Clangにおけるサポートとモデル化について解説するものです。特に、C++言語機能や型システムに対してどのような影響を与え、また与えないのか、について重視しています。

P3763R1 Remove redundant reserve_hint members from view classes

標準のview型がsize()を提供している場合にreserve_hintを削除する提案。

以前の記事を参照

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

  • take_viewについて、そのベースの範囲がapproximately_sized_rangeでない場合にのみreserve_hint()を有効化する

などです。

この提案については、関連するNBコメントがリジェクトされたようで、この提案の追及も停止されている可能性があります。

P3772R1 std::simd overloads for bit permutations

<bit>のビット置換系操作APIのstd::simdオーバーロードを追加する提案。

以前の記事を参照

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

  • 参照する提案の番号を修正
  • simd::bit_repeat()の不要なnoexcept削除
  • N5014にリベース

などです。

P3793R1 Better shifting

整数のビットシフトを行うライブラリ関数の提案。

以前の記事を参照

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

  • シフト量の引数を、任意の整数型を取れるようにした
  • 負のシフト量に対する操作を、逆方向のシフトにした
  • §4.3.1. Emulating possible behaviors using std::shl を追加
  • §4.3.2. Benchmarks of std::shl variants を追加
  • §5. Possible implementation を追加
  • N5014にリベース

などです。

P3804R1 Iterating on parallel_scheduler

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

以前の記事を参照

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

  • SG1+LEWGからのフィードバックを反映
  • P3826R2に基づいて、文言を修正

などです。

P3815R1 Add scope_association concept to P3149

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

以前の記事を参照

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

  • フィードバックを受けて、背景と同期に関するセクションを強化

などです。

P3816R1 Hashing meta::info

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

以前の記事を参照

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

  • ハッシュの様々なセマンティクスと、現在の動作を選択した理由について説明するセクションを追加
  • ABIに関する潜在的な懸念事項について説明するセクションを追加
  • 初期設計とサポートに関する潜在的な拡張についてのセクションを追加
  • モチベーションをさらに追加

などです。

P3824R2 Static storage for braced initializers NBC examples

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

以前の記事を参照

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

  • 文言の改善

などです。

理由は分かりませんが、この提案はリジェクトされているようです。

P3826R1 Fix or Remove Sender Algorithm Customization

P3826R2 Fix or Remove Sender Algorithm Customization

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

以前の記事を参照

R1での変更は

  • C++26のアルゴリズムのカスタマイズ方法を改善する代替案を提示

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

  • indeterminate_domain<>を追加

などです。

R1で追加された代替案のカスタマイズ方法の修正は、senderに対して完了場所を問い合わせる際は開始場所を明示するようにすることです。元々、senderは自身がどこで開始されるかを知らない限りどこで完了するかが分からない、という点が問題だったため、完了場所を問い合わせる際に開始場所を指定するようにすることで解決します。

これはsenderに対して完了ドメインを問い合わせる際にreceiverの環境を渡すことで実現されます。get_domain(get_env(sndr))の代わりにget_completion_domain<set_value_t>(get_env(sndr), get_env(rcvr))のようにクエリするようにします。

この変更ではクエリの際にreceiverの環境が必要となるためEarly customizationは削除されます。

この修正はC++29を目指す場合でもおそらくほぼ同様となります。この変更は大きな設計変更でありC++26のこの時期に入れるにはリスクがあるとして、C++26ではカスタマイズについてを削除し、C++29で修正しつつ復帰という当初の案がメインの提案です。

P3833R0 std::multi_lock

複数のmutexに対してより柔軟なロックをかけることのできるstd::multi_lockの提案。

C++26時点では、標準ライブラリには次のミューテックスラッパクラスが用意されています

  • std::lock_guard
    • 単一ミューテックス用のシンプルなRAIIラッパ
  • std::unique_lock
    • 単一ミューテックス用の柔軟なRAIIラッパ
    • 遅延ロック、ロック取得の試行、所有権移転などの機能を備えている
  • std::scoped_lock
    • 複数のミューテックス用のRAIIラッパ
    • デッドロック回避機能を備えている

ただ、ここにはstd::scoped_lockのように複数のミューテックスを取りながらstd::unique_lockのように柔軟で多彩なロックをサポートしているようなものがありません。

表に起こすと、次の表の右下の部分をカバーするものがありません。

性質\ミューテックス数 単一ミューテックス 複数のミューテックス
常に所有する(ムーブ不可) std::lock_guard std::scoped_lock
柔軟なロック制御方法 std::unique_lock

このため、この提案ではそのような特徴(std::scoped_lockstd::unique_lockの両方の利点)を持つ(上記表の右下の部分をカバーする)新しいミューテックス用のRAIIラッパ型であるstd::multi_lockを追加しようとしています。

namespace std {

  // 宣言例
  template<class... MutexTypes>
  class multi_lock;
}

この提案のstd::multi_lock<Ms...>は次のような特徴を持ちます

  1. 0個以上のミューテックスを受け入れる
  2. RAIIによる自動ロック解除機能を提供
  3. 遅延ロック、 try-lock, adopt-lock戦略をサポート
  4. 時間制限付きロック操作をサポート
    • すべてのミューテックスがCpp17TimedLockableの場合
  5. ムーブセマンティクスによる所有権移転をサポート

遅延ロックの例

std::mutex m1, m2;
std::multi_lock lock(std::defer_lock, m1, m2);
// ... prepare work ...
if (condition) {
  lock.lock(); // 複数のミューテックスを扱ってもデッドロックを起こさない
  // critical section
}

時間制限付きロックの例

std::multi_lock lock(100ms, m1, m2);
if (lock) {
  // 指定された時間以内にすべてのロックを獲得できた場合
  // critical section
}

手動制御による条件付きロックの例

std::multi_lock lock(std::try_to_lock, m1, m2);
if (!lock) {
  // すべてのロックを取得できなかったため、ハンドルする
  return;
}

P3834R2 Defaulting the Compound Assignment Operators

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

以前の記事を参照

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

  • B&で渡される右辺オペランドの許容範囲を拡大
  • § 4.1 Minor Edge Casesで、列挙型のステータスを明確化
  • noexceptに関する説明を追加
  • 投票結果を追加

などです。

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

P3836R2 Make optional<T&> trivially copyable (NB comment US 134-215)

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

以前の記事を参照

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

  • タイトルにNBコメント番号を追加
  • LEWGの投票に従い、オプション1のみを提案する
    • オプション2はトリビアルコピー可能であることに加えてポインタとレイアウトが同じになることを指定するもの

などです。

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

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

P3843R2 std::function_wrapper

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

以前の記事を参照

R1での変更は

  • 現在の方向性を反映して、タイトルを変更
  • std::nontype_tの完全な代替案としては提案しなくなった

などです。

P3844R1 Restore simd::vec broadcast from int

P3844R2 Restore simd::vec broadcast from int

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

以前の記事を参照

R1での変更は

  • constevalコンストラクタをbasic_vecの値型に自然に変換可能な算術型に限定する
  • common_typeへの影響について議論を追加
  • [simd.math]への影響について議論を追加
  • constevalコンストラクタをimmediate-escalating expressionとして扱うことについて議論を追加
  • [simd.math]への潜在的な影響について議論を追加

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

  • 提案していた投票を削除
  • 不要なsimd-broadcast-argを削除するように文言を更新
  • [simd.math]の文言を元の設計意図(明示的なオーバーロードセットであるかのように扱う)に合わせるように変更
  • [simd.math]の文言に対する修正/改善
    • math-common-simd-t<V0, V1>がスカラ型を含むデフォルト以外のABIタグの組み合わせで動作しなかった
      • 整数絶対値に関する事前条件を追加

などです。

P3847R0 Lambdas capture left to right

ラムダ式のクロージャ型において、明示的なキャプチャの初期化順序を規定する提案。

ラムダ式のクロージャ型は、そのメンバの宣言順は未規定でありメンバ変数の初期化順も未規定とされています。これは初期化キャプチャが導入されるよりも前からの仕様で、おそらく暗黙的なキャプチャの使用順序を追跡しない実装を許可するための自由度だったと思われます。

しかし、このような自由度は現在の実装よって使用されておらず、仮に使用したとしても有用なメリットが無さそうです。例えば、最も可能性がありそうなのはクロージャ型のパディングを最小化することですが、クロージャ型のメモリフットプリントが問題になることはめったにありません。

一方で、次のようなコードを合法ではなくしています

// 初期化の順序が重要になる例
void construction() {
  auto x = std::make_unique<int>();
  [value = *x,  // valueが初期化されるときには`x`がmove後である可能性がある
   lifetime = std::move(x)
  ] {};
}
// 破棄の順序が重要になる例
struct last_call {
  std::function<void()> f;
  ~last_call() {f();}
};

void destruction() {
  auto x = std::make_unique<int>();
  const auto user = [&i = *x] {std::cout << i << '\n';};

  [lifetime = std::move(x),
   trigger = last_call{user}  // userが呼ばれるころにはlifetimeが破棄されている可能性がある
  ] {};
}

特にこのように、初期化キャプチャにおいてはデフォルトメンバ初期化子などと類似していることもあり、書いた順序と異なる順序で初期化される場合はプログラマの意図とは明らかに異なるでしょう。

ただ現在のところ、主要な実装はこの自由度を利用していません。すべての主要な実装では、すべての非参照明示的キャプチャ(簡易キャプチャと初期化キャプチャ)の順に対応するメンバが定義され、その後に暗黙キャプチャがその使用順(キャプチャの出現順)に定義され、初期化はその順序で行われるようです。

また、Itanium ABIではクロージャ型のレイアウトにこの順序を使用して規定を行う予定があるようです。

そのため、この提案ではこれらの既存実装における慣例に従って、明示的なキャプチャ(簡易キャプチャと初期化キャプチャ)はすべてその出現順に宣言され初期化されるように規定することを提案しています。そして、これを欠陥報告(DR)とすることも提案しています(おそらくC++14)。

ただしここでは、暗黙キャプチャについての順序を明記することは提案していません(EWGが望むのであればそれも可能とはしていますが)。なぜなら、デフォルトキャプチャがある場合にソースコード順と異なる結果をもたらすためです。

またついでに、参照初期化キャプチャ用のメンバが存在しない場合にその初期化方法について全く指定がないという別の問題も修正しています。

EWGのレビューでは、この提案をDRとしてC++29ターゲットで導入することに合意が取れているようです。その際、暗黙キャプチャの順序を含むようにはしていないようです。

P3849R1 SIS/TK611 considerations on Contract Assertions

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

以前の記事を参照

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

  • 参考文献と補足説明を追加
  • dependency セクションを拡張
  • marketing に関するセクションを追加

などです。

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

あるポインタが別のポインタ範囲内にあるかどうかをチェックする関数の提案。

何かしらオブジェクトや配列要素を指すポインタがあり、それが別のポインタによって構成される範囲内に含まれているかを調べたい、あるいは出自の分からないポインタが2つあるときにその等価性を調べたい、という場合がたまにあります。しかし、標準の範囲内でこのような比較を行う方法は用意されていません(直接の比較結果は未規定)。

一応、std::less<T*>などでは実装定義の全順序でポインタの比較を行うことができるため、実装のサポートがあればそのようなチェックを行うことはできます。しかしこの方法は定数式で使用できません。

この提案では、実装定義の全順序に依存しない形で直接的にポインタ同士の関連性をチェックすることのできる関数を標準ライブラリで用意しようとするものです。

提案しているのは次のような関数です

namespace std {
  constexpr bool is_pointer_between(const void * ptr, const void * first, const void * last); // freestanding
}

std::is_pointer_between()はシグネチャ通り、第一引数に渡したポインタ値が、第二・第三引数のポインタ範囲内に含まれる場合(first <= p and p < lastの場合)にtrueを返す関数です。

この関数は実装に全順序を要求することなく使用でき、定数式でも使用可能なものです。ただし、その実装はおそらくコンパイラマジックが必要になります。

これを用いると例えば、契約アサーションによってポインタの包含をアサートできるようになります。

constexpr bool intersect(std::span<const T> lhs, std::span<const T> rhs) noexcept {
  const auto * a = lhs.data();
  const auto * b = lhs.data() + lhs.size();
  const auto * x = rhs.data();
  const auto * y = rhs.data() + rhs.size();
  return std::is_pointer_between(x, a, b) 
      || std::is_pointer_between(y, a, b)
      || std::is_pointer_between(a, x, y)
      || std::is_pointer_between(b, x, y);
}

void transfer(std::span<const T> source, std::span<T> target) 
  pre(not intersect(source, target))  // 範囲がオーバーラップしていないこと
  pre(source.size() == target.size()) 
{
  std::copy(source.begin(), source.end(), target.begin());
}

また、std::hive.get_iterator(T*)constexpr化のためにも使用できるとのことです。

P3856R1 New reflection metafunctions - is_structural_type (US NB comment 49) and is_destructurable_type

P3856R2 New reflection metafunctions - is_structural_type (US NB comment 49) and is_destructurable_type

P3856R3 New reflection metafunctions - is_structural_type (US NB comment 49) and is_destructurable_type

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

以前の記事を参照

R1での変更は

  • 提案番号を修正
  • proof of concept (section 3.3)からの考察を追加

R2での変更は

  • is_structural_type()を追加

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

  • バージョン番号を修正

などです。

is_structural_type()は構造的な型(NTTPとして使用可能な型)を判定するリフレクションメタ関数です(名前は似ていますが役割はかなり異なります)。

P3858R1 A Lifetime-Management Primitive for Trivially Relocatable Types

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

以前の記事を参照

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

  • 型消去のセクションにApplicationを追加
  • std::restart_lifetimeの冗長な呼び出しがサポートされていることを明確化
  • 関連するNBコメントへの参照を追加

などです。

C++26からトリビアルリロケーション関連が削除されたことを受けて、この提案の議論は一旦停止されています。

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

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

以前の記事を参照

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

  • noexceptを追加
  • 制約の記述を“T and U are similar types”に変更
  • 整数型/浮動小数点数型/ポインタ型の特殊化に変換コンストラクタを追加

などです。

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

P3868R1 Allow #line before module declarations

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

以前の記事を参照

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

  • groupを使用しないように文言を変更
  • 別の編集上の変更との互換性を持たせる

などです。

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

P3869R0 Slides for P3666R1 Bit-precise integers

P3869R1 Slides for P3666R1 Bit-precise integers

P3666R1の紹介スライド。

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

P3666R1ではC23で追加された__BitIntをC++でもサポートしようとしています。このスライドではどのようにそれを輸入してくるか(ほぼCそのまま+C++標準ライブラリでの対応)について簡単に説明されています。

P3876R0 Extending <charconv> support to more character types

std::from_chars/std::to_charschar以外の文字型による文字列をサポートするようにする提案。

std::from_chars/std::to_charsは文字列と数値間の変換を行うもので、特にパフォーマンスに重きを置いています。これらの関数は入力/出力の文字列としてcharの文字範囲のみをサポートしています。char8_tを始めとしてC++にはchar以外の文字型がありますが、これらの文字型による文字列のサポートは行われていません。

この提案は、std::from_chars/std::to_chars両関数でchar以外の文字型による文字列を入出力としてサポートすることを提案するものです。

特に、std::format()における数値から文字列への変換はstd::to_charsを用いて規定されているため、std::formatchar8_t等のユニコード文字型をサポートするために重要なステップとなります。

この提案の対象の文字型はchar以外のすべてのもの

  • char8_t
  • char16_t
  • char32_t
  • wchar_t

です。

wchar_tを除けばこれらの文字型はすべてユニコードのエンコーディングの一つと対応しており、ユニコード(UTF)ではASCII範囲の文字が数値的にASCII互換になっているため、ほとんど特別な処理は不要です。

この提案では、ユニコード文字型のstd::from_chars/std::to_charsオーバーロードは共にcharオーバーロードの出力形式と互換となるようにしています。例えば、std::from_charsにおいてASCII範囲外の数字とみなせる文字(など)を数値として変換できるようにすることは提案していません。

また、ユニコードのエラーハンドリングもほとんど行わないことを提案しており、この提案のオーバーロードはあくまでユニコード文字列内のASCII互換数字の範囲内(基本ラテン文字のブロック内)でのみ動作します。すなわち、std::from_charsでは基本ラテン文字のブロック外の文字コードはすべて解析パターンではないものとして扱われます。

string_view cstr = "123z"; // OK, 正常
int i1;
const auto [p1, e1] = from_chars(cstr.data(), cstr.data() + cstr.size(), i1); // OK

u8string_view u8str = u8"123\xFF"; // 不正なUTF-8
int i2;
const auto [p2, e2] = from_chars(u8str.data(), u8str.data() + u8str.size(), i2); // OK

assert(i1 == i2);                              // i1とi2は共に123になる
assert(p1 - cstr.data() == p2 - u8str.data()); // どちらも3コード単位分をパターンとして認識

これらのことは、std::from_chars/std::to_charsは共にパフォーマンスを最重視する低レベルなユーティリティであることを反映しています。

この提案ではむしろ、既存のcharオーバーロードの互換性を崩すことなく新しいオーバーロードを追加する方が困難だとしています。なぜなら、std::from_chars/std::to_charschar版だけでも11個のオーバーロードがあり、単純に文字型毎にオーバーロードを定義すると55のオーバーロードを追加することになってしまうため、テンプレート化が必須となるからです。

テンプレート化すると特に問題があるのは戻り値型です。std::to_chars_result/std::from_chars_resultはどちらも、入出力文字列の位置に関する情報を保持するために文字へのポインタ(char*/const char*)をメンバとして持つ集成体型でありテンプレート化されていないためです。名前も最も適切なものを占有しています。

既存のこれらをテンプレート化することはできないため、元の型に変更をくわえず新しいテンプレート化された結果型を使用するために、この提案ではエイリアステンプレートを使用するようにしています。

提案より、新しいstd::to_charsセットの概要

template<class T>
  concept character-type = any character type;

// 現在の戻り値型
struct to_chars_result {
  char* ptr;
  errc ec;
  friend bool operator==(const to_chars_result&, const to_chars_result&) = default;
  constexpr explicit operator bool() const noexcept { return ec == errc{}; }
};

// 新しい文字型毎の戻り値型
struct wto_chars_result { /* ... */ };
struct u8to_chars_result { /* ... */ };
struct u16to_chars_result { /* ... */ };
struct u32to_chars_result { /* ... */ };

// 文字型に応じて戻り値型を導出するエイリアステンプレート
template<character-type T>
  using to_chars_result_t = one of the result types above;

// 既存の整数型オーバーロード
constexpr to_chars_result
  to_chars(char* first, char* last, integer-type value, int base = 10);
// 対応する新しい関数テンプレートオーバーロード
template<character-type T, integer-type-concept U>
  constexpr to_chars_result_t<T> to_chars(T* first, T* last, U value, int base = 10);

// 浮動小数点数型でも同じアプローチを採用
to_chars_result to_chars(char* first, char* last,
                         floating-point-type value);
template<character-type T, floating-point-type-concept U>
  to_chars_result_t<T> to_chars(T* first, T* last, U value);

to_chars_result to_chars(char* first, char* last,
                         floating-point-type value, chars_format fmt);
template<character-type T, floating-point-type-concept U>
  to_chars_result_t<T> to_chars(T* first, T* last, U value, chars_format fmt);

to_chars_result to_chars(char* first, char* last,
                         floating-point-type value, chars_format fmt, int precision);
template<character-type T, floating-point-type-concept U>
  to_chars_result_t<T> to_chars(T* first, T* last, U value, chars_format fmt, int precision);

新しいstd::from_charsセットの概要

struct from_chars_result {
  const char* ptr;
  errc ec;
  friend bool operator==(const from_chars_result&, const from_chars_result&) = default;
  constexpr explicit operator bool() const noexcept { return ec == errc{}; }
};
// analogous classes:
struct wfrom_chars_result { /* ... */ };
struct u8from_chars_result { /* ... */ };
struct u16from_chars_result { /* ... */ };
struct u32from_chars_result { /* ... */ };

template<character-type T>
  using from_chars_result_t = one of the result types above;

// pre-existing overload:
constexpr from_chars_result
  from_chars(const char* first, const char* last, integer-type& value, int base = 10);
// new function template:
template<character-type T, integer-type-concept U>
  constexpr from_chars_result_t<T> from_chars(T* first, T* last, U& value, int base = 10);

// same approach for floating-point types:
from_chars_result from_chars(const char* first, const char* last,
                             floating-point-type& value,
                             chars_format fmt = chars_format::general);
template<character-type T, floating-point-type-concept U>
  from_chars_result_t<T> from_chars(T* first, T* last, U& value,
                                    chars_format fmt = chars_format::general);

また、この提案では追加されるwchar_tオーバーロードのstd::to_charsを用いるようにstd::formatの規定を修正することも提案しています。std::formatはすでにwchar_tをサポートしていますが、その規定でもstd::to_charsが使用されています。これを額面通りに実装するとstd::to_charsの出力をwchar_t文字範囲に書き込むことになりますが、このような実装は存在しない事を暗に仮定してもいます。この提案により、std::to_charsがほぼそのまま使用できるようになります。

SG16による初期レビューではこの提案の方向性(std::from_chars/std::to_charsの両方で4つの文字型のサポートを追加)におおむねコンセンサスが取れています(それでも反対意見はあったようです)。

P3878R0 C++26 Contracts are not a good fit for standard library hardening

P3878R1 Standard library hardening should not use the 'observe' semantic

標準ライブラリの堅牢化モードは終了セマンティクスで評価されることを必須とする提案。

C++26から導入される標準ライブラリの堅牢化モードは、Contractsの枠組みの中で標準ライブラリの一部の関数呼び出しにおいて事前条件をチェックするモードです。

Contractsの仕様では、アサーションがチェックされてからどうするか(特に終了するかしないか)については評価セマンティクスというもので主に指定されます。評価セマンティクスには終了セマンティクス(enforce, quick enforce)と非終了セマンティクス(ignore, observe)の大きく2種類があります。

現在の堅牢化モードでは少なくとも契約アサーションの条件がチェックされるセマンティクス(enforce, quick enforce, observe)が選択されることは規定しているものの、どのセマンティクスを選択するかは実装に委ねています。

この提案では、堅牢化モードにおける評価セマンティクスとしてobserveを禁止し、確実に終了セマンティクスで評価を行うようにすることを提案しています。

主な理由は、observeは契約違反を検出しても終了せずに継続するため、標準ライブラリの事前条件チェックにおいては確実に未定義動作に陥るためです。このような振る舞いは堅牢化(hardened)モードという名前にふさわしくないため、堅牢化モードでは禁止すべきという主張です。

この提案はobserveのユースケースを軽視しているのではなく、堅牢化モードという名前を名乗るべきではないとするもので、実装が「チェックされるが堅牢ではない(checked but not hardened)」モードを提供することには反対していません。

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

P3880R0 Make subspan aware of compile-time constants

spanのメンバ関数を、コンパイル時定数を引数として取ることができるようにする提案。

C++26では、std::cwによってコンパイル時引数を比較的簡単に実現できるようになりました。この提案は、これを用いてstd::span.subspan()などのサイズやインデックスの指定をコンパイル時定数によって指定できる様にしようとするものです。

int arr[42] = {};
std::span sp(arr);                                     // span<int, 42>
auto first5 = sp.first  (std::cw<5>);                  // span<int, 5>
auto last5  = sp.last   (std::cw<6>);                  // span<int, 6>
auto sub    = sp.subspan(std::cw<2>, std::cw<9>);      // span<int, 9>

対象は.first(), .last(), .subspan()の3つです。

これらの関数は現在実行時引数を取るため、静的エクステントを持つspanであっても動的エクステントのspanしか取り出せません。また、境界チェックを行おうとしても実行時にしか行えません。

コンパイル時定数を取れるようになれば、可能な限り静的エクステントの情報を保持できるようになります。

また、これによりstd::mdspanとの一貫性も高まります。std::mdspanに対するstd::submdspan()では、そのスライスの指定などに整数定数だけではなくstd::cwのようなコンパイル時定数を使用できるようになっていますが、std::spanではそうなっていないためです。この目的のために、この提案において使用可能なコンパイル時定数とはstd::cwに限りません(std::mdspanと同様にintegral-constant-likeという説明専用コンセプトで指定されています)。

P3881R0 Forward-progress for all infinite loops

全ての副作用の無い無限ループを未定義動作ではなくする提案。

C++では副作用のない(=終了の見込みがない)無限ループは未定義動作とされています。これはマルチスレッドプログラムにおいてプログラムが終了に向かって進行することを保証するための規則(forward progress guarantees)であり、この未定義動作を利用してコンパイラはそのようなループが終了することを仮定した最適化を行うことができます。

この自由度はコンパイラに最適化機会を提供する一方で、プログラマには不便を強いています

  • 不可解な最適化の可能性
    • 副作用の無い無限ループを削除するためにnullチェックなどの重要なコードが削除される可能性がある
  • UBを回避するための冗長コードの必要性
    • volatile変数の操作やyieldの挿入など

また、コンパイラの実装に対してもC++特有のこのようなループセマンティクスを実現するための実装を維持する負担を強いています(例えばLLVM のmustprogress)。C/Rustではこのような規定は存在しないため、無限ループの扱いおよび最適化のためのインフラがC++だけ別に必要になる部分があります。

このようなセマンティクスの違いはRustとC++およびCの間で一貫しない最適化結果をもたらし、相互運用性を妨げます。

肝心のコンパイラの最適化についても、ほとんど利用されていないどころか、利用しない方がプログラムのパフォーマンスが向上する可能性があることが示されています。

これらのことからこの提案では、副作用の無い無限ループを未定義動作とするのをやめて、単にwell-definedにすることを提案しています。

現在次のようなプログラムは未定義動作となっていますが、この提案後はそうではなくなります。

int main(void) {
  bool True = true;
  while(True);
  return 0;
}

次のプログラムを実行しても終了したりUBが発生したりしません。

int main() {
  while(true);
  *nullptr;
  return 0;
}

次のようなマルチスレッドプログラムでは、abort()が呼ばれるとは限らなくなります。

#include <atomic>
#include <thread>

std::atomic<bool> flag = false;

void thread0() {
  flag.store(true);
  abort();
}

void thread1() {
  flag.wait(false);
  bool True = true;
  while(True);
}

int main() {
  std::jthread t0(thread0);
  std::jthread t1(thread1);
  return 0;
}

このサンプルのように、いずれかのスレッドが実行ステップを実行しない場合でも、実装は弱い並行進行保証(weakly parallel forward progress)を提供することができます。これにより、現在未定義動作となっているコード(副作用の無い無限ループ)は未定義動作ではなくなります。また、この提案の後も現在未定義動作ではないコードの動作は変更されません。

P3883R0 A Proposal for a Boolean Flip Operator in C++

bool値を反転させる演算子の提案。

bool値を反転させるためにはflag = !flag;の様な一文を書かないとなりませんが、変数名が2回現れるなど冗長になります。

一方、このようなbool値の反転はboolフラグによって状態を管理するような場所で頻出するため、使用したいシーンは多岐にわたります。

この提案では、これをインクリメント演算子などのようにin-placeかつ簡潔に行うための新しい演算子として、後置~を提案しています。

bool flag = true;
flag~; // flag = !flag; と同等

この演算子は次のような特徴を持ちます

  • 後置演算子
  • オペランド対象はboolの左辺値のみ
    • 右辺値に対しては使用不可
  • 式の型はvoid
    • if (flag~)の様な紛らわしい使用を回避するため

前置~は整数型に対するビット反転演算子としてすでに競合していますが、後置~は現在無効な構文となっているため後方互換性を保ったまま導入することができます。また、後置であることによって後置インクリメント/デクリメント演算子との動作が一貫し、in-placeで値を変化させていることが明確になります。

P3884R0 Slides for P3505R2 Fix the default floating-point representation in std::format

P3505R2の紹介スライド。

P3505R2で提案されているstd::formatにおける正しい浮動小数点文字列化における問題やその背景、パフォーマンスへの影響などが詳しく説明されています。

P3885R0 Add a formatter for std::error_category

std::error_categoryをフォーマット対応させる提案。

現在のところ、std::error_categoryを直接フォーマットや出力する方法がありません。

// どちらもng
std::cout << std::generic_category();
std::print("{}", std::generic_category());

P3395で解決されたものの、std::error_category::name()にはエンコーディングが指定されていないという問題もありました。

この提案は、std::format()/std::printにてstd::error_categoryを直接扱えるようにしようとするものです。

std::print("{}", std::generic_category());
std::print("[{:>10}]\n", std::generic_category());
generic
[   generic]

std::error_categorystd::string_viewのフォーマッタを利用してフォーマットされるため、文字列と同じオプションが使用可能です。

iostreamに対する対応は行われていませんが、std::format()を通して出力することは可能になります。

P3886R0 Wording for AT1-057

契約違反ハンドラの置換がサポートされているかどうかをチェックする方法を提供する提案。

C++26 Contracts仕様では契約違反ハンドラの置換がサポートされているものの、そのサポートは実装定義とされています。とはいえ、実装がサポートしているかは文書を当たるしかなく、コードからそれを調べる方法はありません。サポートされていない場合にコンパイルエラーになる保証はなく、ほぼ未定義動作に近い扱い(ill-formed, no diagnostic required)になります。

#if __cpp_contracts 
  #include <contracts>

  void handle_contract_violation( 
    const std::contracts::contract_violation & 
  ); // ‼ IFNDR if not replaceable  

#endif

この提案では、これを回避するために、違反ハンドラの置換可能性を判定するための機能テストマクロを提供する様にするものです。提案しているのはライブラリマクロの__cpp_lib_replaceable_contract_violation_handlerです。

#if __cpp_contracts 
  #include <contracts> 

  #if __cpp_lib_replaceable_contract_violation_handler 
 
    void handle_contract_violation( 
      const std::contracts::contract_violation & 
    ); // ✅ guaranteed to be well-formed 

  #endif 
#endif

__cpp_lib_replaceable_contract_violation_handlerの値が0ではない場合は違反ハンドラの置換がサポートされていることを表しています。

この提案はNBコメントを受けてそのための文言を提供するためのものです。LEWGでは合意が得られたようですが、CWGでの合意を得ることができなかったようです(リジェクトされてはいないようですが)。

P3887R0 Make when_all a Ronseal Algorithm

P3887R1 Make when_all a Ronseal Algorithm

execution::when_allの停止完了に関する仕様を調整する提案。

execution::when_allは指定されたすべての子操作が完了(値・停止・エラー)してから自身も完了を報告する(子操作の完了を待機)するsenderアダプタです。when_allが完了するのはその名の通りwhen_allに指定されたすべての子操作(sender)が完了してからだと思うのが自然ですが、実際にはwhen_allが子操作を開始する前に停止要求を検出した場合にも停止完了をします。

しかもこの場合の停止が行われる際には子操作は一切開始されません。

この早期の停止要求検出機能は、子senderが全て停止完了を行わないことが静的にわかっている場合でも、子senderが全て停止トークンを認識しないことが分かっている場合でも、無条件で実行され、when_allsenderは停止完了チャネルを持ちます。これによって、下流のreceiverに対して停止完了シグナルをサポートすることを暗黙的に要求しています。

これらのことはexecution::when_allという名前から想像できるその責務・動作とは異なり、その名前以上の事を行っており、単一責任の原則や驚き最小化の原則に従っていないと言えます。

この提案は、execution::when_allのこれらの問題を解消して、その名前の通りの予測可能な動作をするようにしようとするものです。

次の変更を提案しています

  • 早期停止検出機能の削除
    • 子操作の開始前に停止要求を確認するのをやめて、停止要求に関わらず常に子操作を開始するように変更する
  • 停止完了チャネルを持つ条件の変更
    • 現在は無条件で値無しの停止完了チャネルを持つが、少なくとも一つの子操作が停止完了チャネルを持つ場合にのみwhen_allも停止完了チャネルを持つように変更する

これによって、execution::when_allはその名前の通りに子操作を開始しその完了を待機するものになり、それ以上の副作用を持たなくなります。

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

P3889R0 A minimal solution for contracts, or, what is an MVP?

よりシンプルなcontracts機能の提案。

この提案ではP2900のContractsは複雑かつ実装定義が多すぎることで、MVPとはいえないとして、真にミニマルなContracts機能を示すものです。

ここでは次の2つの特徴を持つ契約機能を例として示しています

  • 常にチェックされるアサーションが一種類だけある
  • 契約違反ハンドラを設定できる

この提案は厳密にはこのような機能を提案しているわけではないですが、このようなシンプルなものでも想像するよりも多くの人に効果的にサービスを届けることができ、P2900はこのような点を見落としている、と主張しています。

P3890R0 Add description for parallel memory algorithms

未初期化メモリに対するアルゴリズムの並行アルゴリズム版記述を明確化する提案。

<memory>ヘッダに存在する未初期化メモリに対するアルゴリズムには、C++17以降並行アルゴリズムが追加されています。しかし、規格書においてこのことは一部の文章によって存在が示唆されるのみで、具体的な定義がされていませんでした。

この提案は、未初期化メモリに対する並行アルゴリズムの定義を明確化するものです。

  • std::uninitialized_default_construct
  • std::uninitialized_value_construct
  • std::uninitialized_copy
  • std::uninitialized_move
  • std::uninitialized_fill
  • std::destroy

同時に、並行Rangeアルゴリズムについても追加されています。

P3891R0 Improve readability of the C++ grammar by adding a syntax for groups and repetitions

C++の文法規則を指定する構文において、繰り返しとリストを簡潔に表現するための記法を追加する提案。

C++の文法規則を標準内で指定するために使用されている構文記法はあまり機能が多くなく、次のような機能しかありません

  • 連結
    • pp-numberidentifier-continueなど
  • 共用体
  • オプション展開

特に、頻繁に使用されているにもかかわらず繰り返しとリストのための構文が欠けており、これによって半ばお決まりのパターンによる指定がなされています。

declaration-seq:
  declaration declaration-seq(opt)

template-parameter-list:
  template-parameter
  template-parameter-list , template-parameter

この様な指定は読みやすさを損ね、構文規則を膨張させています。

この提案では、繰り返しとリストのための新しい記法を追加することでこのような冗長な表現を置き換えようとするものです。

ここで提案されているのは次の3種類です

  • Xseq
    • seqは下付き文字
    • Xの一回以上の繰り返し
  • Xseq opt
    • seq optは下付き文字
    • Xの0回以上の繰り返し
  • ⟪X yyy⟫
    • Xyyyをグループ化することでopt, seq, seq optをグループに含まれるもの全てに対して適用できる

提案文書より、例(下付きのoptseq optはmarkdownだと再現できないので、()でくくって表現しています)

現在 この提案
compound-statement:
  { statement-seq(opt) label-seq(opt) }
statement-seq:
  statement statement-seq(opt)
label-seq:
  label label-seq(opt)
compound-statement:
  { statement(seq opt) label(seq opt) }
P3149R11 この提案
initializer-list:
  initializer-clause ...(opt)
  initializer-list , initializer-clause ...(opt)
initializer-list:
  initializer-clause ...(opt) ⟪ , initializer-clause ...(opt) ⟫(seq opt)
P3149R11 この提案
identifier:
  identifier-start
  identifier identifier-continue
identifier:
  identifier-start identifier-continue(seq opt)

この提案は純粋にeditorialなものです。

P3892R0 unless_stop_requested

停止要求が発行されていたら処理の開始を無視するsenderアルゴリズム、unless_stop_requestedの提案。

P3887R0(少し上の方)では、when_allが早期の停止要求検出機能を備えていることを問題視し、それを削除しようとしています。その動機付けの中で、早期の停止要求検出機能は単一のアルゴリズムとして簡単に実現できる、として自身の講演をリンクしています。しかし、使用例や実装例などは示されていません。

この提案は、この早期の停止要求検出とその時点での停止完了発行を行うアルゴリズムとして、unless_stop_requestedを追加するものです。

std::executionの停止要求(およびストップトークンの仕組み)は、C++20でstd::jthreadとともに追加された仕組みをベースとして構築されています。このstd::stop_tokenのフレームワークにおいては、停止シグナルのことを停止要求(stop request)と呼んでいます。ここで要求という言葉が採用されている意味は、停止要求が強いものではなく無視される可能性があるものだからです。

停止要求の処理は明確に協調的(cooperative)なものとして設計されており、それを受けて処理を停止するかどうかは要求を受けた側が決めることです。その根拠の一つとして、停止要求の受信とほぼ同時に非同期処理が完了(成功orエラー)した場合、停止要求は無視して本来の成功を返した方が何が起きたかの情報を伝達できることがあります。

これは単一のコンポーネントや小さな処理レベルでは良い選択となりえますが、より大きなシステムの構成要素となった時に停止要求を無視するコンポーネントは無限ループに陥るリスクがあり、システム全体を停止不可能な状態にしてしまいます。

これは標準ライブラリのような汎用的なコンポーネント(フレームワーク)では回避すべきでありそのための仕組みを最初から入れておくべき、という意見もありますが、停止要求のチェックと応答にはコストがかかるため、むしろ汎用的なフレームワークに入れてしまうと、フレームワーク内部の停止要求応答のためのコストはそこから構成されるコンポーネントの数に比例して増大していきます。

std::stop_tokenのフレームワークが任意になっているのはこうした事情からであり、この観点からもP3887R0の主張は理にかなったものと言えます。しかし一方で、ユーザーが簡単に停止要求への対応を行う方法を用意しておくことには特に問題はないはずです。そのような部品を提供しておけば、ユーザーがどの時点で処理の停止要求を考慮するかを完全に制御することができます。

execution::unless_stop_requestedsenderアルゴリズムとしてまさにそのような働きをするもので、senderによるチェーンに挿入しておくことでその処理ステップで停止要求のチェックを行うことを明確化できます。

using namespace ex = std::execution;

ex::scheduler auto sc = ...;

ex::sender auto async_work =
  ex::starts_on(sc, ex::just(10)) | 
  ex::then([](int n) {
    return ...;  // 何か処理
  }) |
  ex::unless_stop_requested | // 停止要求のチェック、停止要求が発行されていたら停止完了
  ex::then([](int n) {
    ...;  // 次の処理、unless_stop_requestedの時点で停止完了していた場合は実行されない
    return n;
  });

P3893R0 The CppCon 2025 Talk on Contracts and CodeQL in Context

CodeQLにおけるアサーションに対する静的解析の実装経験をP2900の肯定のために使用しない事を推奨する文書。

CodeQLはGithubが開発している静的解析ツールです。CodeQLでは最近C/C++の一部のアサートに対する静的解析機能を実装し、それについてCppConでの講演があったようです(この提案はその講演者の方の一人によって書かれたものです)。そして、そのことがC++26 Contractsをめぐる議論の中で言及されることがあったようで、この文書はCodeQLでの経験は概念実証段階にあるためP2900の静的解析サポートの実現可能性を評価する根拠として使用しないように求めるものです。

文書では、統一的なアサーションがあることによってCodeQLがそれを把握するのに役立つ可能性があるとしつつも、P2900の他の側面によってそれ以外の問題がもたらされるため、静的解析ツールの全体的な動作のために有用であるかはまだ分からないとしています。

P3895R0 Slides for P3724R1 - Integer division

P3724R1の紹介スライド。

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

P3724R1で提案されている様々な丸めモードによる剰余計算の必要性や、標準におけるサポート状況、提案の方向性などについて簡単に紹介されています。

P3896R0 Design goals for a contract support facility

Contracts機能の設計目標について説明した文書。

この文書では、C++26の機能凍結前後で急増したP2900のContracts機能への拒否・修正・再設計などの案への反論として、そもそもP2900が何を解決しようとしていたのかについての設計目標を説明するものです。

この文書によれば、C++における契約機能の導入に関する当初の(そして現在も変わっていない)構想は、関数に対する単一のアノテーションによって2種類の異なる正当性検証ツールを可能にするというものでした

  • assertマクロの様なオプショナルな実行時検査
  • 正当性チェック、静的解析

この実現のためには、これらの機能の既存実装において一般的な使用方法から一部妥協する必要がありました

  • アサーションは関数宣言内に記述する必要がある
    • アサーションマクロでは一般的ではない
  • アサーションはbool型に変換可能なC++の式によって記述する必要がある
    • 静的解析向けアノテーションでは一般的ではない

この構想は、C++の進化におけるもっとも重要なガイドラインの一つである、「新しい機能を追加して言語を複雑化する場合は、それが複数の問題を解決できるようにする必要がある」という原則に基づいたものです。

この意味において、Contracts機能に関する提案は既存の慣行(アサーションと静的解析用アノテーションに関する)を単に標準化するだけではなくそれを次のレベルへと引き上げようとしています。すなわち、オプショナルな実行時検査と静的解析の間に相乗効果をもたらすこと目指しています。これには、実行時検査のみのアプローチでは対処する必要のない新たな課題が伴います。

これによるプログラマのメリットは、アサーションを一度記述するだけでそれに基づいて複数の方法で正当性を検査できるようになることです。

また、P2900までの提案ではその設計において考慮されていなかった3つ目のツールが最近(2024年ごろ)報告されました。それは、ライブラリが明示した事前条件が破られた場合にプログラムを確実に終了させる非無効化保証を提供することです。P2900の設計はこれをサポートするように拡張可能であることは確認されているものの、C++26時点ではサポートされていません。

それ以前の設計目標のモデルは、プログラマが既に確認済みである事項を再確認するものとしてアサーションとその検証を利用していますが、この3つ目のツールはライブラリ作成者がアサーションによって事前条件を指定するものの、違反時の動作まで指定してしまっていることで、アサーションがその関数の動作の一部になってしまう懸念があります。このため、以前の2つの設計目標に適合するかどうか非常にきわどいラインにあります。

この3つ目のツールを重視するために、設計の妥協案として挙げられている「保証された終了を伴うアサーションを提供するだけ」というものは最初の2つの設計目標に向けた一歩ではありません。なぜならそれは単一の目的を達成するためだけのものでしかなく、専用の言語機能を導入しても既存のアサーションマクロ以上のものは得られないからです。

そのためならば、せいぜいチェックと違反時の確実な終了、そしてグローバルな違反ハンドラを提供するためのライブラリ関数があれば十分です。pre post contract_assertは不要です。これはContracts機能ではありません。

3つ目のツールを重視するにしても、最初の2つの設計目標を軽視すべきではありません。

提案されている再設計案にはこれとは別に、P2900 Contractsの持っている機能を細分化して言語機能とし、それを組み合わせてContracts機能を構成しようとするものがあります。この方法ではアサーションの評価についてを細かくユーザーが制御することができますが、一方で契約アサーションを記述する普遍的な表記法が導入されないため、最初の設計目標の1つである静的解析のためのアノテーションの提供ができなくなります。

なお、この提案における静的解析とは、プログラムの入力によってアサーション違反が発生する可能性があるかどうかを判定する解析を指しており、その用途においてアサーションはアノテーションでしかなく、評価セマンティクスは関係ありません。

P3897R0 Slides for P3776R1 - More trailing commas

P3776R1の紹介スライド。

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

P3776R1の提案内容の紹介と、懸念点の説明がされています。

P3898R0 Slides for P3793R0 - Better shifting

P3793R0の紹介スライド。

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

P3793R0で提案されいている整数のビットシフトを行うライブラリ関数のモチベーションや機能性などについて簡単に説明されています。

P3899R0 Clarify the behavior of floating-point overflow

浮動小数点数演算におけるオーバーフロー時の未定義動作を修正する提案。

CWGの議論の中で、浮動小数点数型の演算中の無限大に関する次のようなことがWell-formedであるかが合意が取れていないことが分かりました

  • FLT_MAX * 2のような、無限大(inf)を発生させる演算
  • infinity() + 1のような、無限大を伝播する演算
  • numeric_limits<T>::infinity()のように、無限大を取得すること

まず、浮動小数点数演算のうち未定義動作となるものについては [expr.pre]/4 で規定されています

If during the evaluation of an expression, the result is not mathematically defined or not in the range of representable values for its type, the behavior is undefined.

[Note: Treatment of division by zero, forming a remainder using a zero divisor, and all floating-point exceptions varies among machines, and is sometimes adjustable by a library function. — end note]

式の評価中に結果が数学的に定義されていない場合、あるいはその型で表現可能な値の範囲外である場合、動作は未定義となる。

[注記:ゼロ除算の扱い、ゼロ除数を用いた剰余演算、およびすべての浮動小数点例外処理はマシンによって異なる場合があり、場合によってはライブラリ関数によって調整可能である。――注記終了]

2つの有限数間の演算はゼロ除算を除いて数学的に定義されていますが、その結果が表現可能な値の範囲に含まれない場合はその動作は未定義となりえます。この表現可能な値の範囲は[basic.fundamental]/13にあります(CWG2723によって最近追加されたようです)。

The minimum range of representable values for a floating-point type is the most negative finite floating-point number representable in that type through the most positive finite floating-point number representable in that type. In addition, if negative infinity is representable in a type, the range of that type is extended to all negative real numbers; likewise, if positive infinity is representable in a type, the range of that type is extended to all positive real numbers.

[Note: Since negative and positive infinity are representable in ISO/IEC/IEEE 60559 formats, all real numbers lie within the range of representable values of a floating-point type adhering to ISO/IEC/IEEE 60559. — end note]

浮動小数点型で表現可能な値の最小範囲とは、その型で表現可能な最も小さい負の有限浮動小数点数から、最も大きい正の有限浮動小数点数までの範囲を指す。さらに、負の無限大がその型で表現可能な場合、その型の表現可能な範囲はすべての負の実数に拡張される。同様に、正の無限大が表現可能な場合、その型の表現可能な範囲はすべての正の実数に拡張される。

[注記:ISO/IEC/IEEE 60559規格に準拠する浮動小数点形式では、負の無限大と正の無限大が表現可能であるため、ISO/IEC/IEEE 60559規格に準拠するすべての浮動小数点型において、実数全体がその表現可能な値の範囲内に含まれる。――注記終了]

翻訳はすべてPLamo翻訳を使用。

この定義の解釈について、表現可能な値の範囲に無限大とNaNが含まれるかどうかによって2つの解釈があるようです。これにより次の3つの相反する見解が存在しています

  1. 無限大は表現可能な値の範囲に含まれるため、無限大を生成する浮動小数点数演算のオーバーフローはWell-formed
  2. [expr.pre]/4 で結果が表現可能な値の範囲内にあると述べているのは、丸め処理が行われる前の数学的な結果を指しており、丸め処理によって結果が無限大になる前の状態を指している。実数はすべて表現可能な値の範囲内にあるため、浮動小数点数演算のオーバーフローはWell-formed
  3. [expr.pre]/4 は、式の評価中に発生するあらゆる種類の結果(無限大も含む)について言及している。実数が表現可能な値の範囲内にあるかもしれないが無限大はそうではないため、無限大が存在する場合でも浮動小数点数演算のオーバーフローは未定義動作。
    • [expr.pre]/4 の注記は浮動小数点例外の処理はマシンによって異なると述べており、これは浮動小数点オーバーフローを未定義動作として扱うことの設計根拠を示している

また、CWG2168ではSG6の見解として、無限大を生成する算術演算は定数式では許可されないが、std::numeric_limits<T>::infinity()の使用は問題ない(ただし部分式として使用できない)、というガイダンスを示しています。

CWG2723によって追加された表現可能な値の範囲の定義とこのガイダンス、および[expr.pre]/4の記述は一貫していないようです。この提案は、SG6のガイダンスに準拠しつつCおよび現在constexpr指定済みの<cmath>関数との整合性を保ちながら、合意が得られる最小の変更を行うことによって、この問題を解決しようとするものです。

ここで提案されていることはおおむね、FE_INEXACT例外を除いた浮動小数点例外を発生しない演算のみをWell-definedとして、定数式でも使用できるようにすることで、次のような動作を提案するものです

  • 有限値のオペランドから無限大/NaNを生成することはWell-formedだが、定数式では実行できない
  • 無限大またはquiet NaNを伝播させる演算はWell-formedであり、定数式で実行可能
  • 結果を表すために無限大/NaNが利用できない場合、オーバーフローは未定義動作

GCC15は提案しているこのような振る舞いを実装している唯一の実装であるようです。

冒頭で挙げた例では次のようになります

  • FLT_MAX * 2はWell-formedだが、定数式では実行できない
  • infinity() + 1はWell-formedであり、定数式で実行可能
  • numeric_limits<T>::infinity()はWell-formedであり、定数式で実行可能
    • 部分式としても使用できる

この提案における設計変更点は、浮動小数点数演算でオーバーフローが発生した場合、無限大が表現可能な場合は無限大が返されるようになることが規定される(ただし、定数式で実行可能ではない)点のみです。FLT_MAX * 2のようなコードが定数式であると仮定していたコードはコンパイルエラーになります。

P3902R0 Against implicit conversions for indirect

P3902R1 Against implicit conversions for indirect

P3902R2 Against implicit conversions for indirect

std::indirect<T>へ参照型T&への暗黙変換を追加すべきではないとする提案。

std::indirect<T>はC++26で追加されたライブラリ機能で、ヒープ上に確保されたオブジェクトに値セマンティクスを付与するものです。すなわち、std::indirect<T>はポインタを介して対象のオブジェクトを所有し、std::indirect<T>をコピーするとそのオブジェクトもコピーされ、constアクセスはconst性を対象オブジェクトまで伝播させます。

また、std::indirect<T>をムーブするとムーブ元のstd::indirect<T>は空状態になります。C++26仕様では、std::indirect<T>が空ではない事を事前条件としているのは->*のみであり、このことはstd::unique_ptrstd::optionalなどと整合しています。

アメリカから提出されたNBコメント(US 77-140)では、std::indirect<T>T&に変換できるようにすることで使用例を簡素化でき、reference_wrapperに近くなる、ということが提案されています。

この提案はこれに反対するものです。主な理由は次のものです

  • reference_wrapperに近くすることがなぜ望ましいのかは説明されていない
  • std::indirect<T>reference_wrapperよりもstd::unique_ptrstd::optionalに近いクラス型
    • インターフェースはそれらに寄せたものにすべき
  • 提案されている暗黙変換演算子は空状態でないという事前条件を持つものの、標準化の最終版で未定義動作の発生しうる状況を増やすことは回避したい

ただし、使用経験と実装経験を十分に積んだ後で再検討することはでき、将来望まれた場合の反対意見ではないとしています。

この提案は2025年11月の全体会議で承認されたようです(ただし何か変更を伴うものではありません)。

P3904R0 When paths go WTF: making formatting lossless

std::filesystem::pathのフォーマットにおいて情報の損失が発生しないようにする提案。

C++26 P2845R8でstd::filesystem::pathのフォーマットサポートが追加されています。この提案では、Windowsにおいて実行時コードページではなくリテラルエンコーディングに基づいてフォーマット先のエンコーディングを決定することで、Windowsにおけるエンコーディングの問題に対処しています。

しかし、それも完全ではなく、Windowsにおけるパス文字列としてペアになっていないサロゲート(ペアの片割れ)が現われる場合に正しくエンコードできない問題があります。

auto p1 = std::filesystem::path(L"\xD800"); // a lone surrogate
auto p2 = std::filesystem::path(L"\xD801"); // another lone surrogate
auto s1 = std::format("{}\n", p1); // s1 == "�"
auto s2 = std::format("{}\n", p2); // s2 == "�"

p1p2は異なる文字列ですが同じ文字列(U+FFFD REPLACEMENT CHARACTER)にエンコーディングされてしまっています。この問題はPOSIX環境では起こらないためプラットフォーム間での一貫性がありません。また、これによりpathと文字列の往復変換が不可能になります。

この提案では、このような場合におけるデータ損失を防ぐために、filesystem::pathのフォーマットにおいては不正なUTF-16文字列をWTF-8を使用してフォーマットすることを提案しています。

WTF-8(Wobbly Transformation Format − 8-bit)はUTF-8のスーパーセットであり、16bitコードの任意のシーケンスをUTF-16として不正な場合でも損失無く表すことができます。

auto p1 = std::filesystem::path(L"\xD800"); // a lone surrogate
auto p2 = std::filesystem::path(L"\xD801"); // another lone surrogate

// この提案後の出力
auto s1 = std::format("{}\n", p1); // s1 == "\xED\xA0\x80"
auto s2 = std::format("{}\n", p2); // s2 == "\xED\xA0\x81"

このようにフォーマットされた文字列からは、このことを仮定する文字列変換によって再び同じ表現のpathに戻すことができます(この変換は後程提案されるとのことです)。

ただし、WTF-8はユニコード標準の規定するエンコーディングではないため。std::printlnによって出力しても上記の変換後文字列は正しく出力されません。

std::print("{}\n", std::filesystem::path(L"\xD800"));

P3905R0 C++ Standard Library Ready Issues to be moved in Kona, Nov. 2025

11月に行われたKona会議でWDに適用されたライブラリに対するIssue報告の一覧

P3906R0 C++ Standard Library Immediate Issues to be moved in Kona, Nov. 2025

11月に行われたKona会議でWDに適用されたライブラリに対するIssue報告の一覧

↑(P3905)との違いは、事前にイシューとして公開されていなかったもののその修正の妥当性が(会議の間に)確認されて準備ができたものをまとめた文書の様です。

P3907R0 Waving more ::result_type goodbye

std::copyable_functionから::result_typeを削除する提案。

C++17では呼び出し可能型のインターフェースの一部となっていたresult_typeが非推奨化(P0090R0)され、C++20で削除されました。その際、std::functionのみで後方互換のために残していました。

C++23~26でstd::functionのファミリが増えた際、それらのものにもresult_typeが追加されていました。std::function_refにはなかったようですが、ライブラリIssueとして追加することが促されています。

この提案はそれに対して反対し、result_typeのような情報を取得するための専用の型特性を別に追加することを提案しています。

ただし、C++26の作業フェーズは終わっていることもあり、ここで提案しているのはC++26で追加されたstd::copyable_functionからresult_typeを取り除いておくことのみです。これにより、ひとまずresult_typeの使用を抑制しつつ、そのほかの措置を検討することができます。

std::move_only_functionはC++23で追加されたものであるため、ここでは手を付けていません。

この提案がresult_typeの復活に反対している理由は、P0090の主張と同じく、ラムダ式を始めとするCallable型の多くがこれを備えていないことにより、一貫性が無いためです。

ただし、呼び出し可能オブジェクトの戻り値型を取得したい場合というのは確かにあるため、代わりにより一般的に呼び出し可能型の情報にアクセスするための型特性std::signature_traitsstd::callable_traitsを追加することを検討しています。

std::signature_traitsは関数型を受けて、その関数型のもつ情報にアクセスするためのメンバを備える型特性です。

// signature_traitsの説明用定義例
template<...>
struct signature_traits<R(Args...) cv_qual ref_qual noex> {
  using unqualified = R(Args...);
  using return_t = R;
  
  template<size_t i>
  using param_t = Args...[i];
  
  static constexpr size_t arity = sizeof...(Args);
  static constexpr bool is_noexcept = noex;
  
  template<class U>
  using cv = U cv_qual;
  
  template<class U>
  using ref = U ref_qual;
};

これはあくまで説明のための定義の例であり、細部が正確ではありません。

std::callable_traitsは、呼び出し可能型から関数型を取得してstd::signature_traitsにアクセスする型特性です。

// ラムダのクロージャ型など
template<class T>
  requires is_class_v<T>
struct callable_traits<T> : _callable_traits_impl<^^T::operator()> {};

// 関数型
template<class F>
  requires is_function_v<F>
struct callable_traits<F*> : signature_traits<F> {};

// copyable_function
template<class S>
struct callable_traits<copyable_function<S>> : signature_traits<S> {};

// reference_wrapper
template<class T>
struct callable_traits<reference_wrapper<T>> : signature_traits<T> {};

このような型特性の方がより広く使用することができ、一貫しています。

P3908R0 constexpr from_chars<float> / to_chars<float>

<charconv>の浮動小数点数変換をconstexpr対応する提案。

<charconv>にあるto_chars/from_charsのうち、整数型の変換についてはすでにconstexprになっていますが、浮動小数点数の変換はどちらもconstexprではありません。

この提案では、浮動小数点数型のto_chars/from_charsオーバーロードをどちらもconstexprにする提案です。

浮動小数点数型のto_chars/from_charsオーバーロードがconstexprではなかったのは、定数式でそのような変換を実行する実装経験が無かったためです。しかし近年、定数式で実行可能かつ高速な浮動小数点数と文字列の変換アルゴリズムがいくつか実装されています。

浮動小数点数型のto_chars/from_charsオーバーロードをconstexprで実装できることは実証されているため、この提案はそれを提案しています。

P3909R0 Contracts should go into a White Paper - even at this late point

C++26 Contractsをホワイトペーパーとして出荷すべきとする提案。

P2900のContracts機能は言語にハードコートされた部分が多すぎることで柔軟性や拡張性に欠けており、C++29スコープのラベル機能を考慮しても言語仕様及び委員会による設計への依存が多すぎることで、ユーザーの多様なニーズに応えられておらず、今後も応えられないと主張しています。

例えば、ユーザーのニーズを満たすために何か変更をくわえようとすると、通常のWG21のプロセスを経る必要があり、これはハードルが上がるとともに時間がかかります。

この提案では、AdaのAssertプラグマを参考にした、シンプルでユーザーが任意に拡張可能な実装方法を概説し、それを検討するためにContractsをホワイトペーパーに移すことを提案するものです。

この提案が異議を唱えているのは主にアサーションのセマンティクス(評価セマンティクスと違反ハンドラの動作)がハードコートされている点です。

この提案では、任意のセマンティクスは次の2つのビルディングブロックを提供することで実装可能だとしています

  • ユーザーが設定した評価セマンティクスを問い合わせる機能
  • プログラムから契約違反を発生させる方法

これを用いてquick enforceは次のように実装できます

  • 述語はユーザーが評価セマンティクスをどう設定しているかを問い合わせることができ、ユーザーがquick enforceを設定していた場合は述語の評価結果がfalseだった場合に直ちにabort()/__builtin_trap()/__fastfail()を呼び出す
    • ユーザーが契約違反時に強制終了する事を想定している場合、ここでのP2900との違いは違反ハンドラの呼び出されない点のみ
    • ラベル機能によって同等の事が実現される予定であるため、これを実装できない理由はない

ここでの述語とは、契約条件とは異なるものの様です。この述語をユーザーが関数や関数オブジェクトの形で記述して、それを契約アサーションの評価時に使用できるようにすることでカスタマイゼーションポイントを提供し、それを通じてアサーションのセマンティクスを柔軟に制御しようとするものの様です。

この提案によるこのような設計であれば、すべてのそしてそのほかに必要となるアサーションのセマンティクスはライブラリ、あるいはユーザーサイドで実装し提供可能となります。実際に、現在の4つの評価セマンティクスとそこから派生するいくつかの評価セマンティクスについて、すべて標準でハードコートする必要がないことを示しています。

ラベルでもほぼ同様のことが達成できる見込みですが、次のような点で劣っているとしています

  • 委員会による設計の枠内に収まっている
    • コア言語に組み込まれた機能であり、委員会の想定外の方向に拡張しようとするとコストと時間がかかってしまう
  • ラベルによるカスタマイズはアルゴリズムのフックでその動作をカスタマイズするようなもので、アルゴリズムそのものを変更できない
    • 例えば、契約条件式からの例外送出を契約違反に変換する/しない
  • ラベルを使用するためには複雑で新しい専用のコードを書く必要がある
    • この提案の方法は、関数/関数オブジェクトを書くだけでカスタマイズできる
  • ラベルは低レベルなビルディングブロックではなく、ラベルの下にあるものを変更できない

P2900を急いでISに移行することで、このような代替案の可能性が閉ざされてしまい、少なくとも著者の方はWG21がその準備ができているとは言えないとしています。P2900は紙の上で設計され実証も展開もほぼされておらず、貴重な代替案も試さずに議論だけで却下しており、実際のユーザーにとって不十分なものになっている、としています。

P3910R0 Improving safety of C++26 contracts

Contractsの評価セマンティクスを名前マングリングに含めるようにする提案。

C++26 Contracts(P2900)の仕様に対して色々と意義が提出されており、その中の一つに複数の翻訳単位がリンクされる際にすべての翻訳単位で評価セマンティクスを一貫させる方法がない、というものがあります。例えば、mainの翻訳単位をenforceセマンティクスでコンパイルしていても、ignoreセマンティクスでビルド済みのライブラリをリンクする場合にそのライブラリの関数においては契約チェックが行われず、これを検出したり制御する方法がありません。

この提案はこれに対する解決の方法として、契約アサーションが指定されている関数の名前マングリング処理において、選択された評価セマンティクスを含めるようにするという方法を提案しています。

名前マングリングに評価セマンティクスを含めることによって、ある関数に対して評価セマンティクス毎にコンパイルされた関数のコードが同じオブジェクトファイル内で共存可能になります。これによって、インライン展開されない関数であっても、使用場所(呼び出し側)の評価セマンティクス指定に対応した関数が呼び出すことができるようになります。

オブジェクトファイルにおいて4つの評価セマンティクスに対応した関数の実体が含まれていれば、それをリンクする翻訳単位において指定されている評価セマンティクスによってコンパイルされた関数のシンボル名はその4つのいずれかに対応することになります。これによってビルド済みライブラリであっても、主たる翻訳単位の設定に応じた評価セマンティクスの指定が可能になります。

もしリンク先のオブジェクトファイルにおいて指定された評価セマンティクスによるシンボル名が見つからない場合はコンパイルエラーとなりますが、これは評価セマンティクスの指定が混合されている(必ずしも4つ全ての評価セマンティクスによるシンボルが含まれない)場合のビルドにおいてリンカエラーを引き起こすことになり問題となります。

そこで、リンカもContractsの評価セマンティクスに関する知識を持っておくようにすることでこれを解消します。主体となっているオブジェクトにおいて指定されている評価セマンティクスに該当するシンボルを見つけられない場合、リンカは別の評価セマンティクスを選択してリンクを続行できるようにします(このリンク時の評価セマンティクス選択は現在のContracts仕様の範疇です)。その際、リンカはコマンドライン引数として優先順位付きの評価セマンティクスリストを受け取り、このリストの順番で評価セマンティクスを変更して同じ関数のシンボルを探索するようにします。リストをすべて探索してもシンボルが見つからない場合にリンカエラーを発します。

このリンカに渡す評価セマンティクスリストは必ずしも既知の評価セマンティクスのすべてを網羅している必要はなく、例えばignoreセマンティクスだけを除外することで暗黙的にignoreセマンティクスが選択されることを防止することができます。

ここまでの方法だと、インライン関数のアドレス等価性について問題があります。すなわち、インライン関数のアドレスは一つのプログラム内では翻訳単位を跨いで同一である必要があるものの、異なる評価セマンティクスでコンパイルされた翻訳単位ではそのアドレスが異なる場合があります。

/// ヘッダファイル
inline int f(int n)
  pre(n != 0)
  post(r: 0 < r)
{
  ...

  contract_assert(n < 100);

  ...
}

/// 翻訳単位A、enforceセマンティクスでコンパイル

void call() {
  auto* pf = f; // f_enforceのようなシンボル名のアドレスになる
}

/// 翻訳単位B、observeセマンティクスでコンパイル

void call() {
  auto* pf = f; // f_observeのようなシンボル名のアドレスになる
}

このf_enforcef_observeのシンボル名は契約アサーションの動作が異なるため別の実体となり、翻訳単位AとBの間でpfのアドレスは一致しません。このような状況は現在のC++では未定義動作となります。

この提案ではこの解決のために、コンパイラがこのようなアドレスの取得に対してundefinedシンボル名を発行するようにすることを提案しています。undefinedシンボル名はコンパイラによってこのような場合(契約がなされたインライン関数のアドレス取得時)に発行されるものの対応する実体コードは生成されません。この場合、先ほどのようにリンカは渡された評価セマンティクスリストの一番上にある最も優先度の高いセマンティクスを選択し、そのシンボルのアドレスをundefinedシンボル名のものの代わりに入れるようにします。

/// 翻訳単位A、enforceセマンティクスでコンパイル、リンカの評価セマンティクスリストは"enforce < observe < ignore"とする

void call() {
  auto* pf = f; // f_undefinedのようなシンボル名のアドレスを指定するコードをコンパイラが生成
                // f_undefinedシンボルは未定義かつ契約あり関数のシンボルなので、
                // リンカは評価セマンティクスリストの一番上にあるセマンティクスに対応するシンボルを選択
                // 結果、f_enforceのようなシンボル名のアドレスになる
}

/// 翻訳単位B、observeセマンティクスでコンパイル、リンカの評価セマンティクスリストは"enforce < observe < ignore"とする

void call() {
  auto* pf = f; // f_undefinedのようなシンボル名のアドレスを指定するコードをコンパイラが生成
                // f_undefinedシンボルは未定義かつ契約あり関数のシンボルなので、
                // リンカは評価セマンティクスリストの一番上にあるセマンティクスに対応するシンボルを選択
                // 結果、f_enforceのようなシンボル名のアドレスになる
}

リンカによるこのような処理によって、インライン関数のアドレスは評価セマンティクスの指定に関わらずすべての翻訳単位で同一に保たれます。

ただしこの処理が行われる場合、評価セマンティクスの選択が再び不透明になります。とはいえ、ユーザーが指定している評価セマンティクスリストの一番優先度の高いものが選択されることになるので、結果として選択されたセマンティクスはその翻訳単位ビルド時に指定されていた評価セマンティクスと同等かより厳密なものになるはずです。

このような方法によって、インライン関数がインライン展開されているかに関わらず同じ評価セマンティクスで評価されることや特定の評価セマンティクスをプログラム中で厳密に指定することなどが達成でき、P2900仕様に対して提出されている意義のうち1つが解消されます。

ビルド済みライブラリを配布する場合、特定の一つの評価セマンティクスに対応するシンボルだけを生成しておくのではなく、既知のすべての評価セマンティクスに対応するシンボルをすべて含めるようにして配布しておくと、利用者の指定に応じた評価セマンティクスの選択が可能になります。これは、CUDAのfat binaryとしてすでに実用化されているアプローチと同等です。

このアプローチをとる場合でもまだ問題はあり、extern "C"関数やmain()はこのような評価セマンティクスに応じたシンボル生成を行うと必ず多重定義エラーが発生する事です(似た問題がグローバル変数の初期化においてもあります)。特にmain()の場合は実体が一つしか存在できないことで、そのmain()をコンパイルした評価セマンティクス指定によって実行ファイル全体の評価セマンティクス指定が決まってしまいます。これにより、プログラムで局所的に評価セマンティクスの指定を変更するようなことが妨げられます。

この提案ではこの解決としていくつかの方法を提示しています

  • これらの特殊なエンティティに対して複数のシンボル生成を抑制するオプションを追加する
  • そのソースファイルをコンパイルする評価セマンティクスのリストを指定するオプションをコンパイラに追加する
    • コンパイラはコンパイルの際、そのリストにある評価セマンティクスに応じたシンボルを生成する
    • が、extern "C"関数やmain()などはリストの先頭のセマンティクスに対してのみコードを生成する

ただしどちらの方法でもmain()関数をコンパイルする際の評価セマンティクス指定がプログラム全体に影響してしまう問題を回避できません。この対策として関数呼び出し時に評価セマンティクスを指定する方法を提案していますが、構文等の設計に議論が必要なためそれは将来の標準化における課題としています。ここでは暫定措置として、evaluation_semantic_cast<Semantics>()による明示的な構文を提案しています。

// 別の翻訳単位で1つ以上の評価セマンティクスでコンパイルされたextern関数
void myFunction();

void callMF() {
  // callMF()をコンパイルする評価セマンティクスに対応する関数を呼び出し
  myFunction();

  // enforceセマンティクスでコンパイルされたcallMF()を呼び出し
  evaluation_semantic_cast<enforce>(myFunction)();
  
  // 評価セマンティクスの選択をリンカに委ねる
  evaluation_semantic_cast<undefined>(myFunction)();
}

提案ではさらに、仮想関数の場合のアプローチやモジュールの場合、可能な最適化などについて検討されており、筆者の方のコードベースにおけるP2900の現状の問題の程度の報告などがされています。

この方法を実現するためには、コンパイラだけではなくリンカにも変更が必要となります。C++標準規格としてはリンカの存在を特に取り扱ってはいないものの、リンカに対する影響については慎重なところがあります。この提案では、リンカへの影響を気にするあまり言語機能の進化を妨げた例が過去にあり、この問題についても同様(このままだと同様になる)としています。

P3911R0 RO 2-056 6.11.2 [basic.contract.eval] Make Contracts Reliably Non-Ignorable

契約アサーションの評価セマンティクスから、ignoreの選択を制御できるようにする提案。

契約アサーションの評価セマンティクスが集中管理されていることによって、ビルド構成やプロジェクトポリシーによって容易にignoreセマンティクスに設定されてしまう可能性があるため、Contracts機能を信頼性・モジュール性の低いものにしてしまうとして、この提案では無視されないように制御できるようにすることを提案しています。

ここでは次の4つの案を提示しています

  1. pre!の様な構文によって、必ず終了セマンティクスで評価されるアサーションを指定できるようにする
  2. ignoreセマンティクスを削除する
  3. P3400で提案されている完全なプロパティシステム(ラベル指定)を採用する
  4. P2900 Contracts MVPを別の方法で出荷する
    • 1~3のいずれも合意に至らない場合
    • C++29, white paper, TS

この提案では1のpre!を推奨しています。

この提案はルーマニアのNBによるNBコメントと連動した提案の様です。

P3912R0 Design considerations for always-enforced contract assertions

常に評価され破られたら終了する契約アサーションの設計上の疑問点をまとめた文書。

ルーマニアNBのコメントおよびそれを受けてのP3911R0(上の方に解説があります)では、通常のセマンティクス可変なアサーションに対して、常に評価され条件式がfalseになったらプログラムを終了するような契約アサーションをC++26に向けて導入しようとしています。提案ではpre!(expr)のように通常のアサーションに対して!を後ろに付けた構文を提案しています。

EWGはこのNBコメントと提案を受けて、C++26に向けてこのようなアサーション(always enforced assertion)の追加を検討することにしたようです。

この文書では、NBコメント解決のためにこの段階で新しい機能を急いで導入することはWG21の標準化モデルに反しており、C++26WDにおけるContracts機能はこの様なアサーションが無いことを理由に遅延させるべきではないとしています。

しかし、そのうえでEWGがこのような機能を検討するならば、設計空間を慎重に検討し設計選択におけるトレードオフを十分に理解することが重要であるとして、この文書はalways enforced assertionの設計に関しての提言をするものです。

always enforced assertionはC++29で予定されているContractsの機能拡張であるラベル機能(P3400R2)とかなり重複があることが分かっています。これはC++26に導入される予定はなくC++29に対してもまだ検討中ではありますが、SG21において複数回のレビューを受けており、always enforced assertionの様な機能を包含する契約アサーションの拡張機能として現時点で最も検討が進んでいる設計基盤となっています。

この文書ではラベル機能の設計の経験をベースとして、always enforced assertionおよび類似の機能提案が回答する必要のある設計上の疑問点を提示します。

  1. 構文
    1. 既存キーワードに記号を付加する構文にすべきか?その場合どの記号を使用すべきか?
      • !は慣習的に否定を意味するため、認識上の混乱の可能性がある
      • pre!の様な構文は、rustの宣言マクロのような機能のために確保しておいた方が良い可能性がある
      • 今のところ、他の記号について代替案はない
      • 現在の契約構文はCとの互換性もある程度考慮されている
        • 関数マクロで再現可能な構造になっていることで、C側で無効化できる
    2. デフォルトの構文(pre(), post(), contract_assert())はどちらのアサーションを表すべきか?
      • always enforced assertionにデフォルトの構文を割り当てて、無視可能(セマンティクス可変)なアサーションに別の構文を当てるべきという提案があった
      • また、デフォルトを決定できないのならば現在の構文を廃止して、セマンティクス可変/固定アサーションそれぞれに別の構文を与えるべきという案もあった
      • どちらのアプローチでもC++26 Contracts機能の大幅な再設計となり、P2900R14が強い合意を得てWDにマージされ、現在NBコメントフェーズであることを鑑みると、このような変更はWG21標準化における慣行に反する
    3. always enforced assertionと通常のアサーションの構文は、どれほどの関連性を持つべきか?
      • 構文の長さ、構文構造の類似性など
      • どちらのアサーションがデフォルトになるかに関係なく、always enforced assertionと他のアサーションの構文がどのように関連付けられるかを明確にする必要がある
        • 同じキーワードのわずかな違いで関連性を明示すべきか?
        • 別のキーワードを利用して異なる機能であることを明確化すべきか?
        • 片方の種類のアサーションは他方よりも短いことが望ましいか?
        • など
    4. always enforced assertionの構文はラベル機能との前方互換性を持つべきか?
      • always enforced assertionがC++26で導入され、ラベル機能がC++29で導入されると、契約アサーションにおいて全く同じことをするために2つの方法が存在することになる
      • この状況を回避する方法の一つはラベル機能に対する前方互換性を持たせることだが、その場合はその構文の仕様が<contracts>ヘッダに依存するかなどの問題に対処する必要がある
      • どのような選択を取ったとしてもラベル機能の設計空間を侵食するため、これらの選択の適切な決定のためにはP3400R2をC++26のスコープ内でEWGにおいてレビューする必要があるが、NBコメントフェーズでそれは実現可能性が低い
  2. セマンティクス
    1. always enforced assertionはenforce/quick_enforceのどちらのセマンティクスを採用すべきか?
      • enforce/quick_enforceセマンティクスはどちらも、pre!()の様なアサーションの要件に合致している
      • 単一の構文で両方のセマンティクスを許容する場合、その選択は結局コンパイラオプションによって行われる必要がある
        • このようなオプションは通常のアサーションのオプションとは区別されている必要がある
        • このことはalways enforced assertionの動機と矛盾する可能性がある
    2. 新しい構文はどのアサーションに対して利用可能になるべきか?
      • pre, post, contract_assertのいずれか一部、あるいは全部?
      • pre, postcontract_assertでは考慮すべき設計上の問題が異なるため、この選択が重要になる
    3. 2種類のアサーションの混在は許可されるか?
      • pre!(x)pre(y)を1つの関数宣言で共存させることができる場合、その評価順序は保証すべきか?
      • always enforced assertionの動機の一つである、事前条件違反ごにすぐ終了することによる利点を活かすためには、その評価順序を保証する必要があるが、この選択はalways enforced assertionの設計を大幅に制約することになる
    4. 関数が間接的に(他の言語から)呼ばれる場合でも、pre!/post!は評価されるべきか?
      • always enforced assertionの目的から、関数ポインタを介した関数呼び出し時にもpre!/post!を評価することには異論の余地がない
        • ほかの言語から呼ばれる場合も同様になる
      • このような評価を行うには呼び出し先側でpre!/post!を評価する必要がある
        • このような場合呼び出し元は契約アサーションを認識できないが、契約アサーションは関数型の一部ではなく関数ポインタは契約アサーションを保持できない
        • このため、コンパイラは呼び出し元側でチェックを行うことができなくなる
      • この要件はalways enforced assertionとその類似提案が仕様化可能かつ実装可能であるために順守しなければならない追加の設計制約を課す
    5. pre!()の条件式評価規則はpre()と同じになるべきか異なるべきか?異なるとしたらどう異なるべきか?
      • const化、副作用の省略、重複評価、例外の扱いなど
      • pre!()がC++26WDの最小限の拡張として設計される場合、同じ規則に従うべき
      • 一方、always enforced assertionの動機と要件から、これは通常の契約アサーションとは根本的に異なる機能であり、P2900/P3400と独立して設計された場合異なる規則に従うことになることが予想される
      • また、契約とプログラムの正当性の関係についての見方によっては、always enforced assertionは決定論的なプログラムの動作の一部としてみなされる可能性がある
        • P2900の契約チェックがゴーストコードと位置付けられているのは、P2900の中核原則の一つである「プログラムの正しさを検証するためのチェックを追加しても、その正しさを変更してはならない」に従う物
        • P2900の条件式評価に関する批判されがちなルールはこれに従っている
      • 2種類のアサーションが異なる評価規則に従う場合、両者が同じセマンティクスで評価されたとしてもその条件式の意味は異なることになる
    6. always enforced assertionは実装済みか?そのABIへの影響はどのようなものがあるか?
      • P2900は関数の宣言に契約アサーションを追加してもABIの変更の必要がなく展開可能であることを意図して設計されており、2つの主要なコンパイラで実装されている
        • そうでなければ、ツールチェーンのアップデートが必要となり、展開が大きく遅れることになる
      • 同じアサーションの枠組みに載っている以上、always enforced assertionにも同じ基準を適用するのが妥当
        • 2.5の問に関して、異なる評価規則を採用する場合はなおさら
      • 実装可能性を検証し、ABIへの影響について議論する必要がある
      • always enforced assertionが正確に一回だけの評価を保証する場合、ABIの変更を回避するためには呼び出し先側でアサーションのチェックを行わなくてはならない
        • これは間接呼び出しをサポートするため
        • その場合、通常の契約アサーションとの混在を許可すると、通常の契約アサーションにも同じ制限が適用される
        • 通常の契約アサーションは呼び出し元と呼び出し先の両方での評価を許可しているため、これは大きな設計変更となる
    7. 契約アサーションに仮想関数サポートを追加する場合、always enforced assertionはそこにどのように組み込まれるべきか?
      • 契約アサーションの仮想関数サポートはC++26には間に合わなかったものの、CWGによる文言レビューを通過した経験とGCCによる実装経験があり、C++29の早い段階で導入されると予想される
      • always enforced assertionはそこにどのように組み込まれるかを説明する必要がある
        • ラベル機能も含めたContractsのC++29の拡張は、相互互換性を確保するために同時に設計されている
        • always enforced assertionはそうではないため、これらの機能に対する先方互換性を考慮する必要がある
        • 評価規則が異なる場合、仮想関数サポートについて困難が生じる
          • 基底クラスの契約に対して派生クラスの契約が異なっている場合、正確に一回だけの評価を保証するとこの場合の契約チェックの必要性と矛盾が生じる
  3. ライブラリ
    1. <contracts>のライブラリAPIを拡張して通常の契約違反とalways enforced assertionの契約違反を区別すべきか?どのように区別すべきか?
      • contract_violationオブジェクトから契約違反に関する情報を取得できるが、always enforced assertionにおいてこのAPIの振る舞いをどのように変更するか、あるいはしないかを決定する必要がある
      • ラベル機能においてcontract_violationオブジェクトからラベルを取得する方法はかなり複雑で、実質ラベルが一種類しかない場合にこのAPIを選択するのは疑問がある一方、別の方法を取れば恒久的な設計上の矛盾が生じる
    2. 標準ライブラリにおける堅牢化された事前条件は、pre!()で表現できるべきか?
      • C++26の契約アサーションは標準ライブラリの堅牢化モードにおけるフレームワークとして利用されているにもかかわらず、C++26の機能セットではそれを実装できない点で批判されている
        • 現在は契約アサーションと同等の振る舞いをするベンダー固有のマクロが使用されている
      • always enforced assertionがあれば堅牢化された事前条件を実装できるのか?、そうすべきか?
        • しかし、堅牢化された事前条件はalways enforced assertionとは根本的に異なるもの
        • 堅牢化された事前条件はコンパイラオプションによって評価セマンティクスが変更できる(ignore or enforce)必要がある一方で、always enforced assertionは評価されないセマンティクスが選択されないものであるため

2026/03に行われた全体会議において、Contracts機能にこれ以上の変更を加えずにC++26に導入することが決定されたようです。これによっておそらく、always enforced assertionはラベル機能によって実現されることになると思われます。

P3913R0 Optimize for std::optional in range adaptors

P3913R1 Optimize for std::optional in range adaptors

Rangeアダプタにおいてstd::optionalを認識し動作を最適化する提案。

C++26ではstd::optionalrangeになり、Rangeアダプタで使用できるようになります。

auto f() -> std::optional<int>;

int main() {

  for (const auto& v : f() | std::views::as_const | std::views::reverse) {
    std::println("{}", v);
  }
}

しかし、Rangeアダプタはstd::optionalを認識しないため(ほかのrangeと同様に扱うため)その特性を考慮した動作の最適化などを行いません。

std::optionalは範囲としては要素0か1の範囲であるため、例えば上記例のようにviews::reverseする場合は何もしないで返すことができます。しかし現在はそうなっておらず、reverse_viewを構築してreverse_iteratorを返すことになります。

この提案は、Rangeアダプタでstd::optionalを認識して特別扱いすることで、その特性に合わせたアダプタ動作の最適化を組み込もうとするものです。

ここではstd::optional<T>型のoptの入力に対して、次のような変更を提案しています

  • opt | views::as_const
    • std::optional<T>、または
    • std::optional<const U&>TU&の場合)
  • views::take(opt, n)
    • n == 0: 空のstd::optional
    • それ以外: opt
  • views::drop(opt, n)
    • 0 < n: 空のstd::optional
    • n == 0: opt
  • views::reverse: opt

これ以外のアダプタについては変更を提案していません。

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

P3914R0 Assorted NB comment resolutions for Kona 2025

C++26 CDに対して寄せられたNBコメントの一部の解決のための文言を提供する文書。

ここでは以下のNBコメントの解決のための文言が提供されています

  • US 160-260
    • ranges::uniqueranges::unique_copyについて、入力範囲が空である場合が許可されていない
      • 空の入力に対して動作するように仕様を調整する
  • US 209-332
    • senderの指定で使用されているunspecified-exceptionはその仕様が不十分
      • std::exceptionから派生(曖昧さの無い公開継承)したものであり、dependent_sender_errorとは関係ないことを明確化する
  • US 228-348
    • execution​::​spawn_futureの規定に登場する説明専用エンティティssource-tはデフォルト構築可能である必要があり、デフォルト構築されたオブジェクトは停止状態と関連付けられるべき
  • US 263-396
    • execution::query_parallel_scheduler_backend()の戻り値のshared_ptrはオブジェクトの所有権を持つべき
    • “implements the interface”という表現はC++的ではないため、 “whose type is derived from”に変更する
  • US 265-398
    • parallel_scheduler_backendの3つのメンバ関数の事前条件について、ストレージ(記憶域)についてlifetimeという用語を使用しているが、ストレージには持続期間(duration)がありlifetimeはない
      • ストレージについては、lifetimeではなくdurationを使用する
  • US 266-399
    • parallel_scheduler_backendの3つのメンバ関数の事前条件について、“one of the expressions below”は曖昧であるため、“the call to set_value, set_error, or set_done on r”に変更する
  • US 112-172
    • std::meta::extract()で使用されるextract-value()の要件について、派生型から基底型への変換を禁止すべき
      • Uが配列型である場合にremove_extent_t<U>*のsimilarな型であることを追加する
  • US 130-193
    • 型のプロパティをクエリするリフレクションメタ関数の規定について、対応する型特性メタ関数が存在していない場合に例外を送出するようにする
    • 対応する型特性の使用要件の違反の仕方に応じてどのようなエラーとなるかを規定する

これらの問題は何事もなければC++26でそのまま解消されます。

US 160-260はおそらく並行Rangeアルゴリズムに関連するNBコメントだと思われますが、以前のアルゴリズムの規定(C++20 Rangeアルゴリズムおよびそれ以前のもの)に対しても変更しています。

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

P3915R0 Responses to Trivial Relocation NB Comments

C++26 トリビアルリロケーションに対するNBコメントに対応する文書。

この文書は、C++26 CDに対して寄せられたNBコメントのうちトリビアルリロケーション(P2786R13)に関連するものに対して、P2786の著者の方が応答するものです。

寄せられたNBコメントは次の7つに大別されます

  1. P2786のほぼ全てを削除する
  2. トリビアルリロケーションとmemcpyを同等にする
    • memcpyでもリロケーションできるようにする
  3. std::relocatestd::uninitialized_relocate_*に置き換える
  4. 暗黙的なトリビアルリロケーションの修正
    • 暗黙的にムーブ操作がdeleteされる場合でも、トリビアルリロケーション可能にする
  5. 条件付きトリビアルリロケーション
    • _if_eligibleをキーワードにする
    • _if_eligiblebool値を取るようにする
  6. リロケーション関数においてconst型を許可する
  7. restart_lifetime(P3858)

これらのカテゴリごとに、NBコメントに対して返答(賛成・反対・中立、その根拠など)をしています。

概ね次のような姿勢です

  1. 反対
  2. 反対
  3. 反対
  4. 賛成
  5. 中立、ただしC++26では現在で十分
  6. 中立
  7. C++26に対しては反対

 

P3917R0 A Lifetime-Management Primitive for Trivially Relocatable Types (Presentation)

P3858R1の紹介スライド。

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

このスライドではP3858で提案されているrestart_lifetime()のモチベーションやユースケース等の説明が簡潔になされています。また、restart_lifetime()のARM64eプラットフォームにおける悪用の懸念やC++26に入れる際の利点欠点なども説明されています。

P3919R0 Guaranteed-(quick-)enforced contracts

終了セマンティクスが保証される契約アサーションのニーズについて報告する文書。

C++26のContracts仕様(P2900)では、契約アサーションの評価セマンティクスは固定ではありません。これに対して、「常にチェックされ、契約違反は終了することが保証される」セマンティクス(というよりもそういう動作をするアサーション)のニーズがあり、この文書はそれについて説明するものです。

文書によるとニーズとは次のようなものです

  • メモリ安全性を保証するためのアサーションでは、条件は常にチェックされ、条件がfalseなら後続のコードを実行しないために終了しなければならない
  • そのようなアサーションでは、条件式の評価は正確に一回である必要がある
    • 実行時のオーバーヘッドを最小化し、パフォーマンスを理由に利用を控えることが無いようにする
    • 正確に一回だけの評価によって、副作用も制御可能になる
  • 関数ポインタなど、間接的な呼び出しにおいても必ずチェックされなければならない
  • 条件式の評価に伴う例外送出を契約違反に変換しない
    • 変換にはコストがかかり、メモリ安全性アサーションは可能な限り低コストである必要があるため
  • const化は有害
    • メモリ安全機能においては条件式の理解しやすさが重要であり、目に見えない変換の理解を必要とすべきではない
    • 必ず一度だけ評価されることで、副作用の懸念は無くなる(副作用は必ず一度だけ発生する)

これらはP2900の提供する契約アサーションでは完全に満たすことができないニーズであるため、実質的に現在のContracts仕様に反対を表明する文書になっています。

P3920R0 Wording for NB comment resolution on trivial relocation

トリビアルリロケーションをC++26から削除するための文言を提供する文書。

2025年11月に行われたkona会議において、トリビアルリロケーション(P2786R13)をC++26から削除してC++29をターゲットとして検討しなおすことがEWG/LEWGで合意されたようです。

この文書は、P2786R13の変更をC++26 CDから取り除くための文言の変更をまとめたものです。単純にrevertするのではなく、一部で行われた文言の改善を維持するようにしています。ただし、トリビアルリロケーションに関する機能で残るものはないようです。

P3921R0 Core Language Working Group "ready" Issues for the November, 2025 meeting

2025年11月に行われたKona会議でWDに適用されたコア言語に対するIssue報告の一覧。

量が多いのは、C++26のNBコメント由来のイシュー解決が含まれているためです。

P3922R0 Missing deduction guide from simd::mask to simd::vec

P3922R1 Missing deduction guide from simd::mask to simd::vec

simd::vecの推論補助を復元する提案。

スカラ型の演算コードではブール値を整数型に変換することがたまに行われます。

template<typename T>
void f(Tx, Ty) {
  int b = x < y;
  // ...
}

これはsimd::vecでも行うことができます

template<typename T>
void f(Tx, Ty) {
  vec<int> b = x < y; // ng、型が異なる
  // ...
}

ただし、ここでのbの型は正しくはありません。これはbasic_mask型が得られています。

この場合に単項+を使用することで整数型のsimd::vecに変換することができます。

template<typename T>
void f(Tx, Ty) {
  auto b = +(x < y);  // bはsimd::vec<T>(Tは整数型
  // ...
}

ただし次のように書けた方がより直感的と思われます

template<typename T>
void f(Tx, Ty) {
  basic_vec b = x < y;
  // ...
}

この提案はこのように書くことができるようにするために、simd::vecsimd::basic_maskからのテンプレートパラメータ推論を行う推論補助を追加することを提案するものです。

simd::basic_mask<Bytes, ABI>simd::vec<T, ABI>のように両者は1つ目のテンプレートパラメータが異なり、ABIタグ型(2つ目のテンプレートパラメータ)の選択には実装の自由度があります。それによって、simd::basic_maskオブジェクトに対する単項+の結果型をユーザーが予想すること(特に2つのテンプレートパラメータが何になるか)は困難になっています。

basic_vec b = x < y;のように書いたときにsimd::vecの2つのテンプレートパラメータを推論するには推論補助が必要となりますが(左辺のbasic_maskに対して+を適用した結果型を取得するようにするもの)、これは著者の方の参照実装には実装されていたものの仕様に対する提案はもれていたようです。この提案ではそのCTADを追加することでsimd::vecの比較演算結果を直接simd::vecで受けられるようにしています。

P3923R0 Additional NB comment resolutions for Kona 2025

C++26 CDに対して寄せられたNBコメントの一部の解決のための文言を提供する文書。

ここでは以下のNBコメントの解決のための文言が提供されています

  • AT 7-213
    • std::complexはtuple-likeとなったが、タプルサポートライブラリにおいてそれが明記されていない
  • US 140-233
    • std::hiveの要素列(rangeあるいはイテレータ範囲など)を指定できるコンストラクタ/代入演算子のうち、その操作の後の要素の順序を指定していないものが多い
      • 入力と同じ順序を保つことを一貫して指定する
  • US 141-235
    • std::hive::reserve()について、要素の順序の維持が明記されていない
      • 既存要素については何も起こらないこと明確にするために、変更されないことについての言及を削除(サイズも要素の順序も変化することは無い)
  • US 145-234
    • std::hive::trim_capacity(size_type n)について、呼び出し前にキャパシティがn未満である場合の動作が明記されていない
      • キャパシティがn未満である場合、何も起こらない事を明記する
  • US 147-240
    • std::hive::splice()について、例外送出条件がsplice操作の前にチェックされるのか最中にチェックされるのか不明なことによって、例外送出時のコンテナの状態が不明になっている
      • splice操作の前にチェックされることを明記し、強い例外保証を提供する(つまりコンテナの状態は変更されない)
  • US 164-203
    • 集合操作アルゴリズムの指定で出現するMの定義について、複数の同値要素の処理を考慮しているかが明確ではない
    • 効果の指定では、M > Nの場合にどの要素がコピーされるのかが明確ではない
  • US 126-189
    • type_ordertype_order_vは不完全型を許容すべき
      • 明示的な許可を追加する
  • US 227-346
    • execution​::​spawn_futureにおいて、アロケータはまだその型が不完全な時にspawn-state/spawn-future-stateに再バインドされデータメンバとして使用される
       -  不完全型をサポートする必要はなく、使用箇所でアロケータを再バインドする
      
  • US 229-347
    • spawn_future(sndr, token, env)の説明で使用されているsの型が不明確
  • US 221-339
    • execution​::​bulk等の指定に使用されている説明専用check-typesの位置や定義が間違っている
  • US 225-341
    • std::tupleのコンストラクタは条件付きnoexceptではなく、“potentially-throwing”というチェックが正しく機能しない可能性がある
      • 条件付きnoexceptを追加する

これらの問題は何事もなければC++26でそのまま解消されます。

US 160-260(おそらく並行Rangeアルゴリズムに関連するNBコメント)、US 225-341は以前からあるものに対する変更です。

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

P3924R0 Fix inappropriate font choices for "declaration"

規格文書内での宣言に対するスタイルを一貫させる提案。

C++規格書内では宣言という用語について、類似した2つの使用法が定義されています

  • [basic.pre]で定義されている通常フォントの宣言(declaration)
  • [dcl]で定義されている文法的な宣言(declaration
    • 斜体で表示される

前者は単に宣言という言葉として文章中で使用され、後者は特定の宣言の文法要素を指定するために使用されています。これら2つは規格文書中で適用されているスタイルが異なっています(通常フォントと斜体)。

しかし規格文書中ではこの2つのスタイルが誤って適用されている箇所があるようで、この提案はそれを指摘するNBコメントを受けて再度見直しと調整を行ったものです。

このことからわかるように、この提案はeditorialなもので動作の変更等を伴うものではありません。

P3925R0 RO 3-292 29.10.8.3 [simd.comparison] Make basic_simd a Regular Type (with Boolean operator==)

std::simd==の振る舞いについて修正の提案。

この提案は以前のP2892のフォローアップとなる提案です。基本的なところは以前の記事を参照してください

std::simdstd::simd::vec)のoperator==は要素ごとの比較の結果によるマスク(std::simd::basic_mask)を返し、要素列が一致しているかいないかをbool値で返すもの(std::equalやコンテナの比較)ではありません。

この提案はそれを既存のコンテナ等と同じ振る舞い(std::equalによる比較によるような)になるように修正しようとするものです。

この提案ではSIMDの専門家(現状の動作が自然)と一般的なC++開発者(bool値を返すほうが自然)を区別して、後者のユーザーに向けた設計とすることを求めています(P2892では技術的な議論のみが行われていた)。具体的な数値は記載されていないものの、一定の規模のアンケートを行った結果として現在の動作が非直感的だという調査結果も報告しています。

この提案では次の3つの設計案を提示しています

  1. 既存APIのリネーム
    • operator==を削除し、現在の比較動作は明示的なフリー関数(equal_elements())で行われるようにする
  2. operator==boolを返すようにする
  3. std::simdを次のバージョンまで延期する

提案としては1か2の採用を推奨しています。

この提案はルーマニアのNBとしてのNBコメントに基づく提案でもあります。

P3926R0 Slides: operator T& on indirect<T> (in defense of US 77-140)

std::indirect<T>を暗黙的にT&に変換できるようにすることについての解説を行うスライド。

これは、P3902R2で反対意見が出されている問題について、賛成意見を解説するものです。

P3928R0 static_sized_range

そのサイズがコンパイル時に判明している場合のsized_rangeを表すコンセプトの提案。

sized_rangeコンセプトではそのサイズはranges::size(r)によって取得されますが、これが定数式で実行可能であるかどうかを調べる方法が無かったため、コンパイル時にサイズが確定しているかどうかを調べてコンセプト定義に反映することができませんでした。

P2280R4の採択により、コンセプトの定義時やSFINAEの文脈においてranges::size(r)の呼び出しが定数式であるかどうかを自然に調べることができるようになりました。そして、C++26のstd::simdではこのテクニックを用いてranges::size(r)がコンパイル時に取得可能かどうかを積極的にチェックしています。

範囲のサイズ取得が定数式で行えるかどうかを判定することが有益なのはstd::simdだけではなく、この提案はrangeの性質としてそれを表現し判定できるようにするためのコンセプトranges::static_sized_rangeを提案しています。

static_sized_rangeはC++26のstd::cwstd::constant_wrapper)を用いて次のように定義できます

namespace std::ranges {
  template<class T>
  concept static_sized_range =
    sized_range<T> && requires(T& t) { cw<ranges::size(t)>; };
}

cw<ranges::size(t)>ranges::size(t)の呼び出しが定数式で行えない場合にエラーとなるため(std::cw<V>がNTTPVによってstd::constant_wrapper<V>を返すため)、Tのオブジェクトtに対してranges::size(t)が定数式で呼び出せない場合にこのコンセプトはfalseになります。

また、static_sized_rangeに対してそのサイズをコンパイル時に取得するための変数テンプレート、range_static_size_vも提案しています

namespace std::ranges {
  template<static_sized_range T>
  constexpr auto range_static_size_v =
    decltype([](T& t) { return cw<ranges::size(t)>; }(declval<T&>()))::value;
}

std::cw<V>std::constant_wrapper<V>型オブジェクトを返し、std::constant_wrapper<V>::valueからVを取得できます。この定義ではそれを利用しています。

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

static_assert(ranges::static_sized_range<array<int, 3>>);
static_assert(ranges::range_static_size_v<array<int, 3>> == 3);

static_assert(ranges::static_sized_range<span<int, 5>>);
static_assert(ranges::range_static_size_v<span<int, 5>> == 5);

static_assert(ranges::static_sized_range<ranges::single_view<int>>);
static_assert(ranges::range_static_size_v<ranges::single_view<int>> == 1);

static_assert(ranges::static_sized_range<ranges::empty_view<int>>);
static_assert(ranges::range_static_size_v<ranges::empty_view<int>> == 0);

static_assert(!ranges::static_sized_range<span<int>>);
static_assert(!ranges::static_sized_range<vector<int>>);
static_assert(!ranges::static_sized_range<optional<int>>);
static_assert(ranges::range_static_size_v<string> == 42); // ill-formed: constraints not satisfied

さらに、既存のRangeアダプタ等でもこれらのものを活用することを提案しています

  • integer-class
    • range_static_size_v<R>で取得した値の整数型が正しく定数式で使用可能になるように明確化
  • ref_view
    • static_sized_rangeをラップする際にその性質を継承するようにする
      • ref_view<array<int, 42>>のような場合にstatic_sized_rangeとなるようにする
  • array/span/view_interface.front()/.back()
    • 範囲が空の場合に呼び出しをコンパイルエラーにする
  • join_view
    • 現在常にsized_rangeではないが、外側の範囲がsized_rangeであり、内側の範囲が固定サイズである場合などの特定のケースではsized_rangeになることができる
    • 内側の範囲がstatic_sized_rangeである場合、join_viewsized_rangeにできる
    • 外側もstatic_sized_rangeならば、join_viewstatic_sized_rangeになる
  • lazy_split_view
    • tiny-rangestatic_sized_rangeで置き換えることで、input_rangeを分割する際のパターンとしてstatic_sized_rangeを使用できるようになる
  • span
    • 範囲コンストラクタについて
      • 静的にサイズが決まる範囲から構築する際、サイズチェックをコンパイル時にチェックできる(現在は実行時
      • CTADにおいて入力範囲の静的サイズを取得して固定サイズspanを推論できる
  • std::simd/std::inplace_vector
    • ranges::size(r)が定数式である場合、という条件をstatic_sized_rangeで置き換える
  • ranges::min/max/minmax
    • 入力範囲が空でないという事前条件をコンパイル時にチェックできるようになる

このような広い範囲での応用可能性が挙げられていることが、static_sized_rangeの必要性と有用性を示してもいます。

P3929R0 Fix safety hazard in std::function_ref

std::function_refの二重ラップを防止するために、変換コンストラクタをexplicitにする提案。

C++26で追加されたstd::function_refはcallableなものへの軽量な参照となるstd:functionのファミリの一つです。std::function_refstd::functionなどと同じく指定された関数型と互換性のあるcallableの参照となることができるため、型が完全に一致してコピーコンストラクタが呼ばれる場合を除いて、std::function_refstd::function_refというものを構築することができます。

しかし、std::function_refはcallableへの参照でありそれを所有していないため、これは参照の参照のようなものであり、ダングリング参照を容易に生成しえる危険な構築経路となります。標準ライブラリで参照セマンティクスを持つ既存の型(std::string_viewstd::span)はこのような動作をしません。

// function_refで関数を受け取る関数
void call_me_later(std::function_ref<void()> f);

void f();
int g(); // we don't care about g's result

// 関数ポインタを渡せる
call_me_later(f); // ok
call_me_later(g); // ok

// function_refも渡せてしまう
call_me_later(std::function_ref(f)); // ok, コピーコンストラクタが呼ばれ、fの関数ポインタを直接渡したのと同じ
call_me_later(std::function_ref(g)); // use-after-free, `std::function_ref(g)`の一時オブジェクトへの参照を渡している

call_me_later()が受け取った関数を保存していてこの呼び出しの後で使用する場合、寿命が尽きたstd::function_refを呼び出してしまいます。

実際にはこのように構築することはそうそうないはずですが、callableを別の場所から取得してくる場合などでその型が不明瞭な場合、気づかない間にこのような渡し方をしてしまう可能性があります。

// 関数を返す関数
auto return_g_ptr() { return g; }
auto return_g_ref() { return std::function_ref(g); }

call_me_later(return_g_ptr()); // ok, gの関数ポインタを渡している
call_me_later(return_g_ref()); // use-after-free, `std::function_ref(g)`の一時オブジェクトへの参照を渡している

あるいはメンバ関数をstd::function_refに渡したい場合

struct foo{
  void f();
  int g(); // we don't care about g's result
};
foo obj; // suppose obj lives long enough

call_me_later({std::constant_arg<&foo::f>, obj}); // ok
call_me_later({std::constant_arg<&foo::g>, obj}); // ok

内実を知らなければconstant_argを用いたこの初期化式は何をしているのか全く明確ではないため、std::function_refを明示的にしたくなるかもしれません

call_me_later(std::function_ref(std::constant_arg<&foo::f>, obj)); // ok, コピーコンストラクタが呼ばれる
call_me_later(std::function_ref(std::constant_arg<&foo::g>, obj)); // use-after-free, `std::function_ref(std::constant_arg<&foo::g>, obj)`への一時オブジェクトを渡している

こうすると同じ問題にぶつかります。これは、後者の場合はstd::function_ref{...}としていることによってまずここでstd::function_refの構築が行われ、CTADによってテンプレートパラメータが推論されます。それはfoo::gに基づいて決定されるため、std::function_ref<int()>になります。一方、call_me_later()の引数型はstd::function_ref<void()>になるので変換が走ります。

前者の方は、引数型std::function_ref<void()>の構築を行うものであるため、CTADは起こらず型変換も発生しません。

このような寿命の問題がない場合でも、二重std::function_refは参照の参照であるため、呼び出しに際して2回の間接呼び出しが必要になります。

この提案は、このような二重std::function_refが暗黙的に発生することを防止するために、テンプレートパラメータが一致しない場合の変換コンストラクタをexplicitにしておくものです。

ここで関連するstd::function_refのコンストラクタは2つあります。

namespace std {

  template<typename R, typename... Args>
  class function_ref<R(Args...) cv noex> {
    ...

    // コピーコンストラクタ
    constexpr function_ref(const function_ref&) noexcept = default; 

    // 変換コンストラクタ
    template<class F>
    constexpr function_ref(F&& f) noexcept;

    ...
  };
}

片方はコピーコンストラクタであり、これはテンプレートパラメータがcv修飾やnoexceptまで含めて完全一致している場合にのみ呼ばれるものであり、ダングリング参照を生成しないので問題ありません。問題なのはもう一つの汎用の任意のF&&から構築するコンストラクタで、現状これはテンプレートパラメータが異なるものの呼び出しについて互換性のあるstd::function_refを暗黙的に受け入れています。

この提案ではこのコンストラクタを条件付きでexplicitにすることを提案しています。これにより、先ほどのcall_me_later()で問題となる例は暗黙変換が禁止されることによってコンパイルエラーになるようになります。

提案されているexplicitの条件は簡易に記述することが難しいものの

  • Fstd::function_refの特殊化であり
  • Fの関数型のシグネチャに互換性がない
    • 互換性がある場合: 引数型と戻り値型が同じでnoexceptとCV修飾に互換性がある場合

場合にexplicit(true)になります。

また同時に、同じ判定方法で関数型のシグネチャに互換性がある場合に次の動作変更も提案しています

  • 該当のコンストラクタを用いて構築された場合、渡されたstd::function_refを参照するのではなく、それが参照しているcallableを直接参照するようにする
    • 二重std::function_refを回避する
    • この場合はexplicit(false)(暗黙変換可能)
  • 代入演算子を有効化する
    • 上記の変換コンストラクタを経由して代入されるため、ダングリングや二重std::function_refにならない

これらの変更により、二重std::function_refが暗黙的に構築されなくなり、可能な場合は二重std::function_refを回避する最適化が行われた上で暗黙変換が許可されるようになります。

この提案はリジェクトされているようです。

P3931R0 consteval all the non-allocating operator"" things

標準ライブラリ内のユーザー定義リテラルをすべてconsteval関数にする提案。

実行時の依存関係(実行時にしか取得できない引数など)を持たず、単にオブジェクトを生成するだけの関数は、暗黙的な外部依存関係(動的メモリ確保の必要性など)がない限りconsteval関数であるべきだとして、ちょうどそれらの条件に該当する関数群である標準ライブラリ内のユーザー定義リテラル(operator"")についてconsteval関数に変更しようとする提案です。

ただし、ユーザー定義リテラルのうちでも動的メモリ確保の必要性があるもの、すなわちstd::stringを生成する""sについてはその対象ではありません。

提案の対象は次のものに関連するユーザー定義リテラルです

  • std::string_view
  • std::complex
  • <chrono>

これらのユーザー定義リテラルを使用する場合で何か問題がある場合、それは実行時ではなくコンパイル時にコンパイルエラーとして報告されます。それにより、ユーザーはバグに早期に気づくことができるようになり、実行時のデバッグにおいてもそれらのものを無視することができます。

この変更によって既存のコードが壊れる可能性があるのは、ユーザー定義リテラル演算子オーバーロード関数のアドレスを取っている場合です。consteval関数のアドレスはコンパイル時にしか存在できないため、そのようなコードはエラーになる可能性があります。しかし、標準ライブラリのほとんどの関数は基本的にそのアドレスを取ることが禁止されているため、実質的に既存コードを壊さないとしています。

P3933R0 constexpr std::hive

std::hiveを定数式で使用可能にする提案。

C++26では既存の全てのコンテナとコンテナアダプタが基本的に定数式で使用可能になっています。その一方、同じくC++26で追加されたstd::hiveはほとんどの関数がconstexpr指定されていないことによって定数式で使用可能ではありません。

この提案は、C++26においてstd::hiveを定数式対応させようとするものです。

std::hiveはパフォーマンスを重視しているコンテナであり、特にキャッシュヒット率を向上させることを意識しています。そのため、コンテナの一部の操作などが定数化されてしまうことによる実行ファイルの肥大化と、(本来キャッシュ内で計算すればいいものを)定数のメモリからの読み出しに置き換えてしまうことによるパフォーマンスの低下を懸念してほとんどconstexpr対応は行われていませんでした。

この提案では、それはオプティマイザの仕事の結果でありconstexpr対応やその仕様とは関係ないこと(オプティマイザの定数畳み込みはconstexpr関数であるかどうかとは無関係に行われている)、constexpr変数の初期化などによって定数式をトリガーしないと定数化は行われないこと(ほとんど、自動的に起こることではない)などから、そのような懸念を否定しています。

MSVC STLのフォークによるconstexpr std::hiveの実装を行っており、そこで問題があったのは.get_iterator(const T *)のみだったとのことです。この関数はstd::hiveの要素のポインタを受け取って、そこからイテレータを取得するものです。その計算量は要素数に対して線形と指定されており、その実装はポインタのグローバルな順序付け(実装定義全順序)に依存しているようで、これが定数式では提供されないことが問題となります。

そのため、この提案では.get_iterator()constexpr指定することは提案していません。

この提案の著者の方は、.get_iterator()constexpr指定するために必要なユーティリティをP3852で提案しており、これが採択されてから改めて.get_iterator()constexpr指定することを推奨しています。

また、std::hiveの可能な実装戦略のうち少なくとも一つではあとからconstexpr対応させようとするとABI破損の可能性があるため、C++26でconstexpr対応しておくことを提案しています。

P3935R0 Rebasing <cmath> on C23

<cmath>ヘッダの参照をC23に更新する提案。

<cmath>ヘッダはCの<math.h>を参照しており、ほとんどの機能はC由来になっています。C++26時点では<cmath>ヘッダはC17の<math.h>を参照しています。

P3348R4ではC++26におけるCの参照をC23に更新していますが、10進浮動小数点数型に関するものやその他コア言語の変更が必要になるものがあったため<cmath>ヘッダの参照は更新されていません。

この提案は、コア言語に必要な対応を行ったうえで<cmath>ヘッダの参照をC23に更新するものです。これにより、C23までに追加されたいくつかの数学関数がC++でも利用可能になります。

ただしこの提案では、C23の10進浮動小数点数型(_Decimal128)をC++でも使用可能にすることは含んでいません。

C20以降で追加されたものは大きくISO/IEC 60559に準拠する数学関数と、マクロの2種類です。

そのうちNarrow rounding functionsと呼ばれる丸め関数群については、関数名の一部として型を指定する関数群をそのまま輸入するのではなく、1つの関数テンプレートとして使用可能にすることを提案しています。

コア言語について変更が必要なのは、浮動小数点数の正規化(canonicalization)に関してであり、このための文言も提案しています。

追加を提案しているものは多岐にわたるため、詳細は提案をご覧ください(関数については表にまとめられています)。

P3936R0 Safer atomic_ref::address (FR-030-310)

std::atomic_ref::address()の戻り値型をvoid*にする提案。

std::atomic_ref::address()atomic_refが参照しているオブジェクトのポインタを取得するものです。これは取得したポインタを介してアクセスするためのものではなく、そのポインタ値そのもの(アドレス)を使用したい場合のためのものです(主に並行ハードウェアにおいての同期コスト削減のために使用する)。

ただ、この関数の戻り値型はatomic_ref<T>に対してT*であり、容易に間接参照して元のオブジェクトにアクセスする事ができてしまいます。しかし、それはアトミックアクセスではなくなるため、ほとんどの場合誤用となります。

そのためatomic_ref::address()の戻り値型はそのままだと間接参照することができないvoid*uintptr_tにすべきだという意見もあり、その提案の途中のリビジョンではそうなっていた時期もありました。しかし、どちらの型も定数式で使用できない問題があったことから、最終的には関数名を変更(最初は.data()だった)したうえで戻り値型はatomic_ref<T>に対してT*を返すようにされました。

uintptr_tは必須の型ではないことによって定義されていない環境があり得ることが問題でしたが、これは解決のめどが立っていません。一方、C++26 P2738R1の採択によってvoid*T*のキャストは定数式でサポートされるようになっています。

この提案は、atomic_ref::address()の戻り値型についての懸念を表明するNBコメントを受けて、その解決のためにatomic_ref::address()の戻り値型をvoid*に変更しようとするものです。

void*はそのままだと間接参照できないため元のオブジェクトにアクセスする事が簡単ではない一方、アドレス値を取得して利用する用途では全く問題がありません。これにより、元の目的を達成しながらNBコメントの懸念に対処することができます。

唯一の欠点は、atomic_ref<T>においてTのCV修飾をコピーする必要性から戻り値型が複雑になることだとしています。

namespace std{

  template<typename T>
  class atomic_ref {
    ...

    // TのCV修飾をvoid*にコピーして適用した型を表す
    // atomic_ref<int>ならvoid*、atomic_ref<const int>ならconst void*
    using address_return_t = COPYCV(T, void*);
    
    ...

    // 元の宣言
    constexpr T* address() const noexcept;

    // この提案
    constexpr address_return_t address() const noexcept;

    ...
  };
}

P3937R0 Type Erasure Requirements For Future Trivial Relocation Design

C++29以降のTrivial Relocation機能の設計要件の提案。

P2786R13で提案されC++26 WDにマージされていたトリビアルリロケーション機能は、NBコメントでの反対意見などを受けてC++26から削除されることが決定されました。この提案はそれを受けて、ライブラリでの使用経験や要件から望ましいトリビアルリロケーション機能に対する設計要件を提示するものです。

この提案は、Proxyというライブラリ(標準ライブラリ入りを目指して提案されてもいる)において、型消去を扱う場合に移動(ムーブ/リロケーション)パフォーマンスを最大化する観点からトリビアルリロケーションに求める要件を検討しています。

型消去ラッパでは、型消去対象のオブジェクトを何らかのバイトバッファとして保持しているため、型消去ラッパ自体をリロケーションしようとする場合はそのバイトバッファに格納されているオブジェクトもリロケーションする必要があります。バイトバッファに格納されているオブジェクトの型がトリビアルリロケーション可能であることが分かっている場合、このリロケーションはバイトバッファのmemcpyで行うことができます。

しかし、P2786R13のトリビアルリロケーション可能な型の定義においては、何らかのfixup操作が必要な型が含まれてしまっています。よくあげられるところではそれは多態的な型(仮想関数テーブルを持つ型)であり、ARM64eなどの特定のプラットフォームにおいてPointer Authentication Codeへの対応が必要になります。

このような場合、型消去ラッパの保持するバイトバッファをリロケーションするにはmemcpyではなくそこに格納されている型に応じたリロケーションパスを呼び出する必要があります。このような型を想定しなければ一切ユーザーコード(型消去対象の型で定義されている操作)を実行しない単なるmemcpyで終わっていたのに対して、ユーザーコードを実行してバイトバッファの移動を行う必要があります。これは、単なるmemcpyあるいは多態的型を保持するポインタの単純なムーブと比較してパフォーマンスで劣ることになります。

提案では、Proxyライブラリにおいてこのようなリロケーション後のfixupを考慮する場合としない場合(あるいはする必要のある型消去ラッパなど)との間でリロケーションのパフォーマンスをいくつかのプラットフォームで計測したベンチマーク結果が掲載されており、fixupを考慮する場合に大幅なパフォーマンスの低下が起こることを報告しています。

そのため、この提案ではトリビアルリロケーション可能な型とはmemcpyによるビット単位のコピーでリロケーションが完了する型として、追加のfixupが必要な型を含まないようにすることを提案しています。この提案のこの定義では、多態的な型はトリビアルリロケーション可能ではなくなります。

これを柱として、この提案では次の設計要件を提示しています

  1. トリビアルリロケーション可能とは、ビット単位のコピーのことと定義
    • トリビアルリロケーション可能な型とは、オブジェクト表現をバイト列としてstd::memcpy/std::memmoveを用いてsizeof(T)分コピーし、適切にアラインされたストレージに再配置できる型のこと
      • このコピーの後、宛先オブジェクトは元のオブジェクトとの寸断なく生存期間を開始し、元のオブジェクトの生存期間は終了する。この際にデストラクタの呼び出しやその他のfixup処理は発生しない
    • この性質は完全にコンパイラによって決定可能であり、判定する型特性のユーザー定義特殊化は禁止される
  2. ムーブ可能ではない型でもトリビアルリロケーション可能にする
    • 暗黙的に削除された、または意図的に定義されていないムーブ操作は、トリビアルリロケーションを無効化しない
      • その表現が要件1を見たすクラス型ではその宣言されたムーブ操作の有無にかかわらず、トリビアルリロケーション可能となる
    • これにより、ムーブ後の無効状態の導入を防止しつつトリビアルリロケーション可能になり、型の無効状態を考慮するユーザー定義リロケーション操作の必要性を排除できる
  3. 条件付きのトリビアルリロケーション可能性
    • 型が、あるブール条件下でのみトリビアルリロケーション可能であることを宣言することを許可する
      • このブール条件が満たされ、すべてのサブオブジェクトが要件1を満たす場合、その型はトリビアルリロケーション可能となる
    • P2786R13のtrivially_relocatable_if_eligible(bool)を利用することを推奨
    • これにより、ライブラリ作成者は独自の特性クエリ方法や複雑な特殊化ルールなどを導入することなく、型の性質に合わせてトリビアルリロケーションを活用できる
  4. 生存期間のセマンティクスの明確化
    • トリビアルリロケーション可能なオブジェクトのwell-formedな生コピーを作成した後、新しいランタイムプリミティブを導入せずに、コピー先オブジェクトの生存期間が直ちに開始され、コピー元オブジェクトの生存期間が終了することを明確化する
    • これにより、新しい関数が不要になり既存の最適化に関する知識が活用できる
  5. 低レベルコードでの使用時のガイダンスの提供
    • 生存期間のルールが順守される場合でも、トリビアルリロケーションが一般的な低レベルパターンに対して安全であることを明確に文書化する
      • SBOのバッファ拡張
      • アリーナ・コンパクション
      • mmapによる永続化

C++26からトリビアルリロケーション仕様が削除されたことでその仕様の再設計が必要となり、この提案はその再設計に際してライブラリ実装者の観点からほぼ必須の要件をまとめて提示するものです。

この提案はリジェクトされているようです(P4155R0がレスポンスとして提出されています)。

P3938R0 Values of floating-point types

浮動小数点数型の表現可能な値のモデルについて、もう少し明確にする提案。

提案では、浮動小数点数型の細部についてのいくつか質問に答える形でC++標準の浮動小数点数モデルの行間を明確化していき、規定が必要な部分を浮き彫りにしています。

主に次のような点を明確にしようとしています

  • 浮動小数点数リテラル(0.0など)は常に正(非負)
    • doubleが符号付のゼロを表現できる場合、0.0は正のゼロであり、-0.0は負のゼロ
      • 正と負のゼロを表現できる浮動小数点数型の場合、ゼロに対する単項-は符号が逆のゼロを返す
  • ISO/IEC 60559に準拠していることの意味の定義
    • ある浮動小数点数型は、その値の表現がISO/IEC 60559に記載されいている浮動小数点数形式の一つであり
    • かつ、ISO/IEC 60559に記載されている浮動小数点数値の集合を表現可能な場合、にISO/IEC 60559に準拠している
    • ISO/IEC 60559に準拠していることは、その浮動小数点数型に対する演算がISO/IEC 60559の規定通りに振舞うことを意味しない
  • 浮動小数点数型が表現できる値の指定
    • 少なくとも有理数のサブセット
    • 型の実装定義値表現によっては、次の非有限値を表現できる
      • 無限大
      • quiet NaN
      • signaling NaN
      • その他の実装定義の値
      • これらの値についても、正と負の符号を持つ異なる値を表現できる
  • 負の定義
    • 値が負であるのはその値がゼロより小さい場合
      • 負のゼロとNaNは負の値ではない
    • 符号ビットが立っていることはその値が負の値であることを意味しない
  • 式の結果におけるゼロ
    • 式の結果が正負のゼロを表現可能な浮動小数点数型である場合、式の結果としてどのゼロが選択されるかは実装定義
    • <=>== !=< <= > >=において、正のゼロと負のゼロは同値
  • テンプレートにおける等価性の定義
    • テンプレート引数の文脈(特にNTTP)において、浮動小数点数の等価性とは、ビット単位で同一(bitwise identical)であること
    • ビット単位で同一(bitwise identical)であるとは
      • Tの値xyは、std::bit_cast<U>(x) == std::bit_cast<U>(y)trueの場合にビット単位で同一とみなされる
        • Uは、Tの同じサイズでオブジェクト表現におけるパディングビットの数と位置がTと同じ仮想的な符号なし整数型
      • 正負のゼロ、ペイロードが異なるNaNなどはビット単位で同一ではない

この提案は現在のC++標準の規定する浮動小数点数型とその演算についての振る舞いを変えるものではありません。明確になっていない行間の部分を明確に規定しようとするものです。

P3940R0 Rename concept tags for C++26: sender_t to sender_tag

std::executionのコンセプトの判定に使用されるタグ型の命名を修正する提案。

std::executionのコンセプトはそれによって使用可能になるいくつかのCPOを持っています。例えば、senderに対するsenderファクトリ/アダプタが該当します。これらのCPOは入力の型に対してそのメンバ関数を考慮してカスタマイゼーションポイントを探索しますが、その際に同名の関係ないディスパッチを防止するために、コンセプトにアダプトするためには特定のメンバ型をコンセプトによって指定されるタグ型によって定義しておく必要があります。

このstd::executionにおけるタグ型はCPO名に対して_tプリフィックスを付加する命名になっています。そのコンセプトにアダプトする型を定義する場合は、メンバ型としてCPO名+_conceptの名前でこのタグ型のエイリアスを定義しておく必要があります。

namespace std::execution {

  // scheduler コンセプトのタグ型
  struct scheduler_t {};

  template<class Sch>
  concept scheduler =
    derived_from<typename remove_cvref_t<Sch>::scheduler_concept, scheduler_t> &&
    ...;

  // receiver コンセプトのタグ型
  struct receiver_t {};

  template<class Rcvr>
  concept receiver =
    derived_from<typename remove_cvref_t<Rcvr>::receiver_concept, receiver_t> &&
    ...;
}

例えば、schedulerコンセプトの場合はscheduler型を定義する際に、その型をSとするとS::scheduler_conceptという名前の型としてstd::execution::scheduler_t型を取得できるようにしておく必要があります。

このようなタグ型はすでに標準ライブラリにもいくつか存在しているのですが、それらのタグ型はその役割によって大きく2つに分類されます。

  • 曖昧性解消用のタグ型
    • コンストラクタ呼び出しや関数呼び出しの曖昧性を解消するためのタグ型
  • コンセプトタグ型
    • ある特定のコンセプトのモデルとなる型を識別するためのタグ型

そして、既存のタグ型はこのカテゴリの違いによってどうやら命名規則が異なっています。

  • 曖昧性解消用のタグ型: name_t
  • コンセプトタグ型: name_tag

std::executionのタグ型は後者のコンセプトタグに該当するため、この命名規則に従っていません。この提案はC++26のうちにこれを修正しておこうとするものです。

曖昧性解消用のタグ型は、ライブラリ関数(コンストラクタ)の特定のオーバーロードを明示的に選択するために引数に指定して使用するタグの型です。

// タグ型とタグ値
struct foo_t {}; // maybe with an explicit ctor
inline constexpr foo_t foo = foo_t();

// タグを使用するクラス型
struct useful_class {
  explicit useful_class(foo_t, int, int);
  explicit useful_class(bar_t, int, int);
};

// タグを指定することでコンストラクタを呼び分ける
auto myVar = useful_class(foo, 1, 2);

標準ライブラリの中では次のものがこれに該当するタグ型として提供されています

  • std::defer_lock_t
  • std::from_range_t
  • std::in_place_t
  • std::piecewise_construct_t
  • std::unexpect_t

このタイプのタグ型は_tプリフィックスを持ちます。

コンセプトタグ型は、関数の呼び出し時に指定する形で使用されるのではなく、ユーザー定義型において特定の名前で定義されるものです。ジェネリックプログラミングの文脈でタグによって入力されたユーザー定義型の性質を特定し、その満たす性質(コンセプト)に応じたディスパッチを行うために使用されます。

// タグ型
struct foo_tag {};

// コンセプト
template<class T>
concept foo = ~~~~;

template<class T>
void internal_algorithm(T, foo_tag);
template<class T>
void internal_algorithm(T, bar_tag);

// 満たすコンセプトによってディスパッチを行う関数
template<class T>
void useful_template(T t) {
  internal_algorithm(t, typename T::your_category());
}

// コンセプトへアダプトする型
struct MyFoolikeClass {
  using your_category = foo_tag;
};

また、このようなタグ型の継承構造によって、対応する概念の階層構造を表現することができます。

標準ライブラリでこのタイプのタグに該当するのはイテレータのタグ型です。

struct forward_iterator_tag : input_iterator_tag {};

template<class I>
concept forward_iterator =
  input_iterator<I> &&
  derived_from<ITER_CONCEPT(I), forward_iterator_tag> &&
  ...;

コンセプトタグの場合、タグ名はコンセプト名+_tagになり、メンバ型のエイリアス名は_category/_conceptのプリフィックスを持ちます。

std::executionで追加されているタグ型は、その命名規則を除いてコンセプトタグに該当するタイプのタグ型です。そのため、C++26の完成前にその命名を変更する必要があります。

  • sender_t:
    • メンバ型名: sender_concept
    • コンセプト: sender
  • receiver_t:
    • メンバ型名: receiver_concept
    • コンセプト: receiver
  • operation_state_t:
    • メンバ型名: operation_state_concept
    • コンセプト: operation_state
  • scheduler_t:
    • メンバ型名: scheduler_concept
    • コンセプト: scheduler

これらのタグ型は現在のところ互いに派生関係にはなく、それぞれ派生するタグ型を持ちません。しかし、std::executionの上に構築される予定のネットワークライブラリではstd::executionから派生したコンセプトが定義されることが予想されており、その場合にはこれらのタグ型から派生したタグ型が定義される可能性があります。その場合はさらにコンセプトタグへ該当するようになります。

P3941R0 Scheduler Affinity

execution::affine_onアルゴリズムの改善提案。

execution::affine_onexecution::task(P3552R3)とともに導入されたsenderアルゴリズムで、senderschedulerを受け取って、そのsenderが与えられたschedulerで完了するようなsenderを返すものです。この時、受け取ったsenderが受け取ったschedulerで完了するように既になっている場合、返されるsenderでは追加のスケジューリングを省略することができます。

コルーチン内で他の非同期処理(コルーチン or sender)を待機(co_await)する場合、非同期処理のネストはco_awaitによって通常のシングルスレッド実行コードと同じような見た目で書くことができるようになります。この時、ある非同期処理のco_await前後で、その一連の処理を実行する場所(実行コンテキスト、すなわちscheduler)は変化しないものと仮定されるのが自然です。

例えば次のような極端な例において

task<> fun() {
  co_await ex::just();
  auto v = co_await ex::just(0);
  auto [i, b, c] = co_await ex::just(0, true, 'c');
  try { 
    co_await ex::just_error(0);
  } catch (int) {}
  co_await ex::just_stopped();
  int result = co_await []->ex::task<int> { co_return 42; }();
}

この例でco_awaitしているのはほぼすべてsenderですが(最後だけコルーチン)、このようなコードにおいてco_awaitの前後で実行場所が変化することは非直観的となることが分かると思います。例えば、fun()自体があるスレッドプール上で実行されているときに、その内部のいずれかのco_awaitの後でスレッドプールとは関係ない別のスレッドで実行されるようになっていることはどちらかと言えばバグと判断されると思われます。

このため、execution::taskではco_awaitする前後で実行場所(scheduler)が変化しないようにしています。すなわち、別の非同期処理をco_awaitする場合、その処理がどこのschedulerで実行され、また現在のschedulerを書きかえるような事を行ったとしても、非同期処理が完了してco_awaitによる待機が解除される際に元のschedulerに復帰して後続の処理(大本のコルーチンそのもの)を再開します。

この仕組みの事をScheduler Affinityと呼びます。Scheduler Affinityは他にも、コルーチン固有の落とし穴を回避するためにも役立ちます。

execution::affine_onexecution::taskにおけるScheduler Affinity動作に使用されるsenderアルゴリズムで、execution::taskのコルーチン内のco_awaitにおいて、待機対象の処理が完了した後に元のschedulerに復帰するために使用されます。

schedulerの復帰には再スケジュールと呼ばれる処理が必要になり(実行コンテキストへの再投入、スレッドプールの場合はスレッドプールのキューへの投入など)、これはコストがかかります。そのため、この再スケジュールをする必要がないことが分かる場合(co_awaitする操作がschedulerを変更しないことが分かる場合)に再スケジュールを回避してそのまま元のschedulerで再開することが望ましく、execution::affine_onはこのような最適化が許可されているものです。

execution::affine_onexecution::continues_onと同じシグネチャで同じ動作をするのですが、この点がexecution::continues_onと異なります。

しかしP3552R3のexecution::affine_onは非常に最小限の定義しかされておらず、仕様が不透明で設計も不完全でした。それに対してはP3796などで問題が指摘されており、この提案はその流れやNBコメントを受けてexecution::affine_onの設計を改善しようとするものです。

この提案では次の変更を提案しています

  1. execution::affine_onの引数の修正
    • affine_on(sndr, sch) -> affine_on(sndr)
      • senderのみを受け取るようにする
    • scheduleraffine_onの上から(co_await式内で用意して)渡すのではなく、affine_onの返すsenderに接続(connect)されたreceiverの環境から取得する
      • これは、co_await sender-exprに対して、as_awaitable(affine_on(sender-expr), *this)(ここでの*thisexecution::taskオブジェクト)で行われる接続操作を介して
      • execution::taskのプロミス型の環境からschedulerを取得する
        • これはco_awaitの前の実行コンテキストのschedulerと一致するため、scheduler引数無しで正しいschedulerを取得できる
  2. 失敗しないschedulerの要求
    • affine_on(sndr)の目的は、渡された処理sndrを実行し元の実行コンテキスト(scheduler)上で完了すること
    • sndrの処理が完了した後のスケジューリング操作(as_awaitableで接続後のoperation_stateの処理)が成功しなかった場合(set_error/set_stoppedで完了した場合)、元のschedulerに復帰することができなくなるためaffine_onの目的を果たすことができなくなる
    • このため、affine_onで使用されるschedulerreceiverは必ず成功するものであることを要求する必要がある
      • receiverに関連付けられた停止トークンはunstoppable_tokenであり
      • schedulerの完了シグネチャにset_error_t/set_stopped_tを持たないものであること
    • すべてのschedulerがこれを保証できるわけではない
      • 保証できないschedulerでは、affine_onの使用(ひいてはtaskコルーチンにおけるco_awaitの使用)がコンパイルエラーとなる
      • 標準のschedulerではparallel_scheduler以外のschedulerでこの保証が実現可能
  3. senderアルゴリズムにおけるexecution::affine_onカスタマイズ
    • schedulerを変更することがないsenderアルゴリズムにおいては、そのことをaffine_onに伝達するために返すsenderに対してaffine_onをカスタマイズできる必要がある
      • affine_onsender型のメンバ関数としてtransform_sender(sndr)を追加する
        • デフォルトでは恒等関数だが、子senderを返すようにして実行コンテキストを変更しない事を通知する
      • 例えば、just, just_error, just_stopped, read_env, write_env, then, upon_error,upon_stoppedなどが(一部は条件付きで)該当する
  4. change_coroutine_schedulerの削除
    • change_coroutine_schedulerは、taskコルーチンの実行コンテキスト(scheduler)を切り替えるためのもの
      - `co_await change_coroutine_scheduler(sch)`のように使うもの
      
    • この切り替えにはいくつか問題があり、ユースケースは非常に稀であるとして削除する
      • schedulerの切り替え前に作成されたローカル変数の構築と破棄が異なるscheduler上で起こる
        • 元のschedulerで構築され、切り替え後のschedulerで破棄される
      • task内にネストしたtaskとの間で対称転送をを正しく行うためには、各taskが適切なschedulerで完了する必要があり、このために元のschedulerに復帰するための情報を保存しておく必要がある
        • ネストtaskchange_coroutine_schedulerされたとしても、そのtaskが完了した時に元のschedulerに復帰する必要がある
        • このために、元のschedulerと異なるschedulerに切り替えるための状態を保存しておく必要がある
          • コルーチン内でchange_coroutine_schedulerが使用されているかを静的に知ることができないため、このために常に追加のストレージが必要になる
    • あるtask自体が使用しているschedulerを切り替えるためには、そのtask自体にon(sch, task)starts_on(sch, task)を適用して明示的に変更できる
  5. execution::affine_onのデフォルト実装の明確化
    • ここまでの変更も踏まえて、以前のexecution::continues_onから踏襲されていた設計とは全く異なるaffine_onの定義が得られる
    • 1~4の変更を組み込んだ動作をデフォルトの定義とする
  6. execution::affine_onの名称変更
    • ここまでの事を踏まえると、affine_onという名前は適切ではない可能性がある
    • 名称の代替案は特に挙げていない

この提案はまだ具体的な文言を伴っておらず、これらの変更点も確定したものではないようです。

P3945R0 Comments on D3933R0 (constexpr hive)

constexpr std::hive提案(P3933R0)に対する、std::hive提案の著者からのコメント。

P3933R0(上の方)では、std::hiveのC++26でのconstexpr対応が提案されており、それが行われていない理由や検討の歴史、先行実装と問題点について報告しています。

この文書は、std::hive提案の著者の方によるP3933R0へのコメントで、技術的な側面、提案のヒストリーの問題点、現在の実装においての問題点、標準化手続きの問題点などについてコメントされています。

著者の方としてはstd::hiveconstexpr対応には反対ではないものの、「完全な実装とテストケースが提供され、私(std::hiveの著者)以外の第三者によるテストが行われ、かつアーキテクチャ固有のパフォーマンス上の問題やその他の問題が確認されない場合に限り、constexpr std::hive の採用を検討する価値があると考える」としています。

ただし、C++26に急いで間に合わせる必要はなく、まず標準ライブラリの実装が出てくるのを待ち、そのうえで実行時パフォーマンスや実装負荷を考慮しながらconstexpr実装を行って、それから標準化を進めることを推奨しています。

転載はしませんが、技術的なコメントや実装におけるコメントにはstd::hiveの提案では語られていないような点も語られているので、興味のある人は読んでみるとよいかもしれません。

P3946R0 Designing enforced assertions

必ずチェックされ違反が起きたら終了するアサーションの設計検討を行う提案。

2025年11月のKona会議において、ルーマニアNBからのNBコメントとそれに付随する提案(P3911R0、上の方に解説があります)を受けて「無視できない契約アサーション(non-ignorable contract assertions)」と呼ばれる新しい言語機能をC++26に向けて追加することを決定したようです。この提案は、それを受けてその設計空間の探索やトレードオフの検討を行うものです。

この提案では、この無視できないアサーションをP2900の契約アサーションの枠組みの中で設計しようとしており、特に事前条件アサーションの動作モデルについて次のように定義しています

事前条件アサーションは、関数呼び出し時にfalseである場合にプログラムにバグが存在することを示す条件を表すものである。これは静的解析ツールの様なツールによって報告されることが推奨される事象である。 その後の動作は、強制アサーションを使用しているか設定可能アサーションを使用しているかなど、多くの条件に依存する

このような定義にしているのは、ベースとなる事前条件のモデルが「条件がtrueでない限り、プログラムの動作は未定義」というものであるためです。これは広く用いられているモデルであり、事前条件が破られた場合の動作を規定するとそれはもう事前条件ではなくなります(つまり、その動作は関数の動作の一部になります)。

上記の定義の無視できないアサーション(強制アサーション)のモデルは、一定の動作保証を与えつつも(モデルの中ではそれについて言及せず)、依然として事前条件違反はプログラムのバグであるとして扱うものです。

そして、次の3つの設計基準を定義しています

  1. 関数宣言は人間が容易に解析でき、簡潔である必要がある
  2. アサーションの主要な目的(どのような場合にバグがあるとみなすか、を伝達する)が、二次的な目的(バグのある呼び出し時に何が起こるか)よりも明確に示されている必要がある
  3. 強制アサーションを使用している人が、誤って設定可能アサーションを記述してしまうことが起こらないようにする

この上で、構文とセマンティクスの設計を検討しています。

まず構文については、P3911R0で提示されていたものを含む次の4つを候補としています

// 候補 A
void fun(int number, int index)
  pre (number > 0)
  pre! (index >= 0)
  pre! (index < size());

// 候補 B
void fun(int number, int index)
  pre? (number > 0)
  pre! (index >= 0)
  pre! (index < size());

// 候補 C
void fun(int number, int index)
  pre_maybe (number > 0)
  pre_always (index >= 0)
  pre_always (index < size());

// 候補 D
void fun(int number, int index)
  pre (number > 0)
  pre<enforce> (index >= 0)
  pre<enforce> (index < size());

AとBには次のような認識上の曖昧さがあります

void fun(vector& vec1, vector& vec2)
  pre!(vec1.empty())   // typo?
  pre(!vec2.empty());  // or intended? 

void container::fun()
  pre not(empty());  // container not empty?

また、AとBにはCにおける契約アサーション(検討段階)との互換性の観点からのリスクがあり、Cではpre(expr)のようにアサーションを関数呼び出し形式にしたい要望があります。これは、Cとの相互運用においてpre, post, contract_assertを何もしない関数マクロとして定義しておくことができるためです。とはいえ属性を考慮すると現在すでにこれは必ずしも満たされないため、この要件の重要性は不明だとしています。

Cでは、_maybe_alwaysのサフィックスがアノテーションとしての重要な部分である述語に対してノイズになっています。

最後のDはP3400R2で提案中のラベル指定との互換性があるものです。しかし、設定可能アサーションの方が強制アサーションよりも短くなります。また、P3400はまだ設計中であり、構文についてはもこのまま導入されるかはわかりません。例えば次のようになる可能性があります

void fun(int * p)
  pre<std::enforce> (p != nullptr);

// または
void fun(int * p)
  pre [[=enforce]] (p != nullptr);

このようになる(あるいはほかの形)場合、構文の一貫性が無くなり混乱を招くことになります。

Bには2つの利点があります

  • 特定の動作をデフォルトにしないため、常に選択する必要があり追加の入力も軽微
  • assertマクロのために使用できなかったassertという名前に関する問題を解決できる

ただし、これによりpreのようなサフィックスなしのものが永遠に存在しなくなります。

NSAの言う安全性の観点から、安全なアサーションは最低限次の2つの点が保証されている必要があります

  1. アサーション内の述語が少なくとも一回評価されること
  2. アサーションによって保護された命令の実行に制御が進まないこと

この要件を満たしただけだとまだ実装依存の動作がいくつか許容されます。最も顕著なのはenforceとquick_enforceのどちらのセマンティクスを選択するかです。enforceセマンティクスでは必ずしもプログラムの終了が保証されないため、強制アサーションの目的を明確化する必要があります。

  • アサーションによって保護されたコードを実行しないこと?
  • どのコードも実行しないこと(無条件終了)?

強制アサーションにおいては、設定可能アサーションで議論の的になっているいくつかの側面について別の選択をすることもできます。

  • アサーションの複数回の評価
  • const
  • 条件式からの例外送出の契約違反への変換
  • 条件式で副作用を許容する

これらの決定には議論の余地があり、強制アサーションと設定可能アサーションの間で異なる設計上の選択をすることはそれはそれで大きな議論を呼ぶことになります。また、P3400のラベル機能が採択された場合、ほぼ同じ事を行うために複数の方法が存在することになります。

ただ、このような強制アサーションにおいて使用される式がある程度単純なもの(標準ライブラリの堅牢化事前条件のように)に留まる場合、別の選択をすることも可能かもしれません。例えば、条件式が例外を送出しうる場合をコンパイルエラーにするなどです。

強制セマンティクスにおいては条件式の評価回数を正確に一回にとどめることができるようになります。しかし、これには欠点もあります

  • 呼び出し先でアサーションを評価する必要があるため、呼び出し元でエラーを報告する機能が失われる
    • 事前条件違反が発生した場合、呼び出し元を指摘する必要がある
  • プログラマが任意のコード(正当性とは全く関係のない、場合によっては副作用のあるコード)を関数呼び出しの前後に使用することを可能にし、促進する
    • const化が有効でも、副作用を及ぼすことは可能

この提案は設計空間の検討を行うのみで、特定の選択を提案してはいません。むしろ、このようなアサーションをここで入れることには反対の雰囲気があります。

P3947R0 identifier_of Should Return std::string

文字列を返すリフレクションのメタ関数の戻り値型をstd::stringにする提案。

std::meta::identifier_ofなどの文字列を返すメタ関数の戻り値型はstd::string_view(もしくはstd::u8string_view)であり、これらの関数では返される文字列はnull終端が保証されることが明記されています。

struct C { };

constexpr string_view sv = std::meta::identifier_of(^^C);
static_assert(sv == "C");
static_assert(sv.data()[0] == 'C');
static_assert(sv.data()[1] == '\0');  // ok

ただしこのことは、既存のstd::string_viewの使用習慣と矛盾しています。通常std::string_viewはnull終端されていないものとして扱うことが推奨され、教えられており、標準ライブラリにおけるこれらの関数はその習慣とは真逆の事をしています。

また、コンパイラを始めとしたツールがstd::string_viewを介したアクセスの際にそのサイズを超えた場所を参照するようなコードを検出しようとする場合、標準規格が保証しているこれらのコードがFPとして検出されてしまうため、ユーザーかツールのどちらかが対応しなければならなくなります。

さらに、str[str.size()]のようなnull文字へのアクセスはstd::string_viewの事前条件違反であり、定数式では使用できません。ここにアクセスしたい場合はstr.data() + str.size()と書く必要があります。

リフレクションのAPIの他の部分を見てみると、std::vector<std::meta::info>を返す関数(members_of()など)ではstd::span<std::meta::info>ではなくstd::vectorを返しています。このことはstd::string_viewを返す関数と一貫していません。

この提案は、この問題の解決として、これらの関数でstd::string_viewを返す代わりにstd::stringを返すようにすることを提案しています。

対象となるのは次の関数です

  • identifier_of()
  • symbol_of()
  • display_string_of()

また、代案としては次の方法も挙げています

  • null終端保証を削除する
  • C++29のstd::cstring_viewを待つ

これらの関数がstd::string_viewを返すようになっているのは、これらの関数を次のように記述するためだったようです

constexpr auto name = identifier_of(r);

これは、コンパイル時の動的メモリ確保の制約(非一時的なアロケーションが許可されていない)により、std::stringを返す場合にコンパイルエラーとなります。しかし、このためのソリューションとしてdefine_static_string()が用意されているため、将来std::stringの非一時的なアロケーションが許可されるまではdefine_static_string()を使用することを推奨しています。

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

P3948R0 constant_wrapper is the only tool needed for passing constant expressions

コンパイル時定数を関数の引数として渡す方法について、std::constant_wrapperを一貫して使用するようにする提案。

C++26で追加されたstd::constant_wrapper<V>はコンパイル時定数をNTTPとして保持して扱うための型で、特にconstexpr引数を実現するためのライブラリ機能です。

std::constant_wrapper<V>はほとんどすべての演算子をオーバーロードしていることによって、保持する値Vに対する透過的な操作を提供しつつ、それらの操作の結果が再びstd::constant_wrapper<V>を返すことで値が型の文脈に留まり続けるようにします。

一方で、C++26のstd::function_refではメンバ関数ポインタのサポートのためにstd::nontypeというNTTP値タグ型を導入しています。これはstd::nontype<+f>のようにして関数ポインタをNTTP値としてstd::function_refのコンストラクタに渡すためのもので、NTTP値として渡すことによって関数ポインタのストレージを省略し別の用途(メンバ関数呼び出しに必要な*thisオブジェクトの参照)に使用するためのものです。

std::nontypestd::constant_wrapperと同じ目的であるためそれを統一すべきという議論があったものの、std::constant_wrapperが関数呼び出し演算子もオーバーロードしていることによってstd::constant_wrapper<+f>のように渡した時にこの渡したものそのものが呼び出し可能になってしまうという問題があったことからstd::nontypestd::function_refのコンストラクタ専用のタグ型として残されることになりました(このとき同時にNTTPという用語が変化したことによる名称変更があり、最終的にstd::constant_argになった)。

結局現在のところ、constexpr引数を実現するための方法が2種類存在しています。基本的にはstd::constant_wrapperを使うべきですが、なぜ2種類あるのかやその使い分けなどは学習に当たってのノイズとなります。

この提案は、この現状に対してstd::constant_wrapperへの一本化を提案するものです。

std::function_refstd::nontypeの代わりにstd::constant_wrapperを使う際の問題点はP3792R0で説明されていますが、それは簡単に言えばstd::cw<f>fに同じ引数を与えて呼んだときに最終的な呼び出しの結果(戻り値)が異なる場合に、それを関数ラッパに入れるとstd::function_refとそれ以外の関数ラッパで呼び出し結果が異なってしまうことです。

この提案ではこれに対して、そのような曖昧さが生じる場合を検出してコンパイルエラーにすることを提案しています。

std::function_refstd::constant_wrapperを受け取るコンストラクタ(現在はstd::constant_argを受け取っている)において、渡されたstd::constant_wrapperf0に対してArgsが0より大きい場合、次のどちらかを満たすことを適格要件(Mandates)として要求します

  • f0(declval<ArgTypes>()...)がWell-formedではない
  • f0(declval<ArgTypes>()...)の型がstd::constant_wrapperの特殊化ではない

ここでのf0std::constant_wrapper<f>として渡されてきたものであり、まずArgsstd::function_ref<R(Args...)>)を直接渡して呼べないのであれば問題ありません(std::constant_wrapperの呼び出しは引数もstd::constant_wrapper互換である必要がある)。呼べてしまう場合でも、その戻り値型がstd::constant_wrapperでなければ問題ありません(std::constant_wrapperの呼び出し結果はstd::constant_wrapper)。

Mandates違反はSFINAEしないので、この曖昧さを検出したら即座にコンパイルエラーになります。

これにより、std::function_refに対してstd::cw<f>を渡した際にfの呼び出しをラップしたいのか、std::cw<f>の呼び出しをラップしたいのか曖昧になる、という問題が解消されます。そしてこれをもって、この提案ではstd::constant_wrapperを一貫して使用し、std::constant_argを削除することを提案しています。

おわり

この記事のMarkdownソース

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

文書の一覧

全部で74本あります。

もくじ

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

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

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

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

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

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

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

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

N5024 2026-03 London meeting information

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

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

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

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

N5028 C++26 CD summary of voting and comments

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

P1708R10 Basic Statistics

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

以前の記事を参照

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

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

などです。

P2034R5 Partially Mutable Lambda Captures

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

以前の記事を参照

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

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

P2728R8 Unicode in the Library, Part 1: UTF Transcoding

以前の記事を参照

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

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

などです。

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

P3086R5 Proxy: A Pointer-Semantics-Based Polymorphism Library

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

以前の記事を参照

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

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

などです。

Better Lookups for map, unordered_map, and flat_map

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

以前の記事を参照

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

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

などです。

P3099R0 User-defined error messages for contract assertions

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

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

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

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

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

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

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

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

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

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

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

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

namespace std::contracts {
  class contract_violation {

    ...

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

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

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

P3255R2 Expose whether atomic notifying operations are lock-free

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

以前の記事を参照

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

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

などです。

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

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

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

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

P3351R3 views::scan

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

以前の記事を参照

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

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

などです。

P3385R6 Attributes reflection

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

以前の記事を参照

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

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

などです。

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

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

以前の記事を参照

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

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

などです。

P3411R4 any_view

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

以前の記事を参照

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

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

などです。

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

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

以前の記事を参照

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

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

などです。

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

P3567R2 flat_meow Fixes

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

以前の記事を参照

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

  • 文言の修正

などです。

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

P3578R1 What is "Safety"?

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

以前の記事を参照

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

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

などです。

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

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

以前の記事を参照

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

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

などです。

P3603R1 Consteval-only Values and Consteval Variables

consteval変数の提案。

以前の記事を参照

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

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

などです。

P3606R1 On Overload Resolution, Exact Matches and Clever Implementations

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

以前の記事を参照

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

  • 提案する文言の追加

などです。

P3643R2 std::to_signed and std::to_unsigned

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

以前の記事を参照

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

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

などです。

P3655R3 cstring_view

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

以前の記事を参照

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

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

などです。

P3663R3 Future-proof submdspan-mapping

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

以前の記事を参照

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

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

などです。

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

P3666R1 Bit-precise integers

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

以前の記事を参照

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

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

などです。

P3669R3 Non-Blocking Support for std::execution

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

以前の記事を参照

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

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

などです。

P3684R0 Fix erroneous behaviour termination semantics for C++26

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

P3688R4 ASCII character utilities

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

以前の記事を参照

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

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

などです。

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

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

以前の記事を参照

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

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

などです。

P3702R3 Stricter requirements for document submissions (SD-7)

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

以前の記事を参照

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

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

などです。

P3724R1 Integer division

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

以前の記事を参照

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

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

などです。

P3732R1 Numeric Range Algorithms

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

以前の記事を参照

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

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

などです。

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

namespace std::ranges {

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

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

P3733R1 More named universal character escapes

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

以前の記事を参照

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

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

などです。

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

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

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

以前の記事を参照

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

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

などです。

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

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

以前の記事を参照

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

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

などです。

P3779R1 reserve() and capacity() for flat containers

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

以前の記事を参照

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

  • 提案する文言を追加

などです。

P3786R1 Tuple protocol for fixed-size spans

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

以前の記事を参照

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

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

などです。

P3800R0 2025-07 Library Evolution Poll Outcomes

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

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

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

P3804R0 Iterating on parallel_scheduler

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

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

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

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

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

P3810R1 hardened memory safety guarantees

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

以前の記事を参照

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

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

などです。

P3824R1 Static storage for braced initializers NBC examples

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

以前の記事を参照

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

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

などです。

P3826R0 Defer Sender Algorithm Customization to C++29

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

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

namespace ex = std::execution;

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

提案文書より、サンプル

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

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

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

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

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

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

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

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

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

  • default_domain
  • transform_sender
  • transform_env
  • apply_sender
  • get_domain

などは削除されます。

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

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

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

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

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

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

P3828R0 Rename the "to_input" view to "as_input"

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

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

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

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

P3834R1 Defaulting the Compound Assignment Operators

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

以前の記事を参照

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

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

などです。

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

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

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

以前の記事を参照

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

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

などです。

P3841R0 Proposal for std::constructor Function Object

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

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

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

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

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

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

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

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

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

この提案

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

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

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

namespace std {

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

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

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

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

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

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

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

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

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

経緯等はP3774R0を参照

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

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

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

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

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

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

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

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

P3844R0 Restore simd::vec broadcast from int

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

上記の例のこの提案後

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

変更前(現在)

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

変更後

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

P3846R0 C++26 Contracts, reasserted

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

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

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

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

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

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

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

P3849R0 SIS/TK611 considerations on Contract Assertions

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

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

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

結果として

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

を要請しています。

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

P3851R0 Position on contracts assertion for C++26

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

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

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

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

P3853R0 A thesis+antithesis=synthesis rumination on Contracts

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

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

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

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

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

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

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

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

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

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

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

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

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

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

P3855R0 New Reflection metafunction - is_destructurable_type

P3856R0 New Reflection metafunction - is_destructurable_type

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

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

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

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

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

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

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

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

struct C1 : S {
};

struct C2 : S {
  int d;
};

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

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

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

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

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

        class_with_members = base;
      }
    } 
  } 

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

  return true;
}

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

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

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

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

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

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

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

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

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

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

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

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

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

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

P3858R0 A Lifetime-Management Primitive for Trivially Relocatable Types

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

提案されている宣言

namespace std {

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

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

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

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

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

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

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

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

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

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

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

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

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

// ... continue using *x

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

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

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

P3859R0 Assertions are not necessarily for changing program behavior

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

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

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

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

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

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

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

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

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

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

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

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

int main() {
  int n = 0;

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

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

P3861R0 Pragmatic approach to standard structural types

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

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

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

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

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

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

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

対象は次のものです

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

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

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

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

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

P3862R0 Postpone basic_string::subview and wait for cstring_view

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

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

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

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

  ...

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

  ...
};

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

P3864R0 Correctly rounded floating-point maths functions

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

提案文書より、単純な例

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

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

template void f<C>();

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

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

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

template void f<C>();

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

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

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

デフォルト引数のケース

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

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

template void f<C>();

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

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

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

P3866R0 V2: An Evolution Path for the Standard Library

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

P3867R0 define_static_string as a STATICALLY_WIDEN replacement

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

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

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

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

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

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

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

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

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

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

使用例

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

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

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

P3868R0 Allow #line before module declarations

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

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

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

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

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

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

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

P3870R0 Renaming std::nontype to std::tag

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

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

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

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

P3872R0 2025-10 Library Evolution Polls

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

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

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

P3874R0 Safety Strategy Requirements for C++

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

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

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

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

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

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

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

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

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

P3875R0 Defining -ffast-math is hard!

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

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

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

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

例えば次のような関数

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

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

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

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

... 

use w again;

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

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

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

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

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

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

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

  return 0; 
}

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

Wrong: 4.94066e-324

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

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

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

おわり

この記事のMarkdownソース

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

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

Eigen::Transform

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

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

// ないらしい
using Transform3d = Eigen::Transform<double, 3, Eigen::TransformTraits::Affine>;

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

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

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

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

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

#include <numbers>

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

// *による合成
Transform3d transform1 = T4 * T3 * T2 * T1;

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

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

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

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

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

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

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

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

デフォルト構築

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

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

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

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

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

参考文献

この記事のMarkdownソース