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


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

文書の一覧

全部で143本あります。

もくじ

N5012 WG21 2025-06 Sofia Admin telecon minutes

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

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

N5016 WG21 June 2025 Sofia Hybrid meeting Minutes of Meeting

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

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

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

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

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

以前の記事を参照

R2での変更は

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

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

などです。

P0260R18 C++ Concurrent Queues

P0260R19 C++ Concurrent Queues

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

以前の記事を参照

R18での変更は

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

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

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

などです。

P0870R6 A proposal for a type trait to detect narrowing conversions

P0870R7 A proposal for a type trait to detect narrowing conversions

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

以前の記事を参照

R6での変更は

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

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

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

などです。

P0876R21 fiber_context - fibers without scheduler

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

以前の記事を参照

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

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

などです。

P1040R8 std::embed and #depend

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

以前の記事を参照

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

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

などです。

P1306R5 Expansion statements

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

以前の記事を参照

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

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

P2079R9 Parallel Scheduler

P2079R10 Parallel Scheduler

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

R9での変更は

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

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

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

P2319R5 Prevent path presentation problems

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

以前の記事を参照

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

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

などです。

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

P2414R8 Pointer lifetime-end zap proposed solutions

P2414R9 Pointer lifetime-end zap proposed solutions

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

以前の記事を参照

R8での変更は

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

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

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

などです。

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

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

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

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

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

P2664R11 Extend std::simd with permutation API

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

以前の記事を参照

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

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

P2781R9 std::constexpr_wrapper

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

以前の記事を参照

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

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

などです。

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

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

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

以前の記事を参照

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

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

などです。

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

P2929R1 simd_invoke

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

以前の記事を参照

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

P2996R13 Reflection for C++26

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

以前の記事を参照

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

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

などです。

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

P3008R6 Atomic floating-point min/max

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

以前の記事を参照

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

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

などです。

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

P3044R2 sub-string_view from string

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

以前の記事を参照

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

  • string_view::subviewfreestanding-deleted指定

などです。

P3045R6 Quantities and units library

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

以前の記事を参照

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

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

などです。

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

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

以前の記事を参照

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

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

などです。

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

P3091R4 Better lookups for map , unordered_map, and flat_map

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

以前の記事を参照

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

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

などです。

P3096R10 Function Parameter Reflection in Reflection for C++26

P3096R11 Function Parameter Reflection in Reflection for C++26

P3096R12 Function Parameter Reflection in Reflection for C++26

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

以前の記事を参照

R10での変更は

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

R11での変更は

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

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

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

などです。

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

P3100R3 Implicit contract assertions

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

以前の記事を参照

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

などです。

P3104R4 Bit permutations

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

以前の記事を参照

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

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

などです。

P3111R7 Atomic Reduction Operations

P3111R8 Atomic Reduction Operations

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

以前の記事を参照

R7での変更は

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

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

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

などです。

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

P3149R11 async_scope -- Creating scopes for non-sequential concurrency

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

以前の記事を参照

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

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

などです。

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

P3179R9 C++ parallel range algorithms

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

以前の記事を参照

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

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

P3211R1 views::flat_map

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

以前の記事を参照

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

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

などです。

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

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

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

P3216R1 views::slice

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

以前の記事を参照

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

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

などです。

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

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

などの理由によります。

P3220R1 views::take_before

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

以前の記事を参照

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

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

などです。

P3223R2 Making std::istream::ignore less surprising

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

以前の記事を参照

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

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

などです。

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

P3248R4 Require [u]intptr_t

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

以前の記事を参照

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

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

などです。

P3290R3 Integrating Existing Assertions With Contracts

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

以前の記事を参照

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

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

などです。

P3293R3 Splicing a base class subobject

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

以前の記事を参照

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

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

などです。

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

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

P3347R3 Invalid/Prospective Pointer Operations

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

以前の記事を参照

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

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

などです。

P3348R4 C++26 should refer to C23 not C17

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

以前の記事を参照

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

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

などです。

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

P3383R3 mdspan.at()

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

以前の記事を参照

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

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

などです。

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

P3394R4 Annotations for Reflection

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

以前の記事を参照

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

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

などです。

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

P3411R3 any_view

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

以前の記事を参照

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

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

などです。

P3433R1 Allocator Support for Operation States

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

以前の記事を参照

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

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

などです。

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

P3439R3 Chained comparisons: Safe, correct, efficient

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

以前の記事を参照

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

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

などです。

P3440R1 Add n_elements named constructor to std::simd

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

以前の記事を参照

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

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

などです。

P3480R6 std::simd is a range

std::simdrangeにする提案。

以前の記事を参照

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

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

などです。

P3481R4 std::execution::bulk() issues

P3481R5 std::execution::bulk() issues

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

以前の記事を参照

R4での変更は

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

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

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

などです。

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

P3491R3 define_static_{string,object,array}

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

以前の記事を参照

このリビジョンは

  • P3617R0の内容をマージ

などです。

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

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

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

以前の記事を参照

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

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

P3540R2 #embed Parameter offset

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

以前の記事を参照

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

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

などです。

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

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

のような動作をします。

P3552R3 Add a Coroutine Task Type

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

以前の記事を参照

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

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

などです。

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

P3557R3 High-Quality Sender Diagnostics with Constexpr Exceptions

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

以前の記事を参照

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

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

などです。

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

P3560R2 Error Handling in Reflection

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

以前の記事を参照

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

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

などです。

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

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

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

以前の記事を参照

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

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

などです。

P3570R2 optional variants in sender/receiver

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

以前の記事を参照

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

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

などです。

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

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

P3601R0 Slides for P3407R1

P3407R1の紹介スライド。

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

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

P3642R1 Carry-less product: std::clmul

P3642R2 Carry-less product: std::clmul

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

以前の記事を参照

R1での変更は

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

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

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

などです。

P3647R0 Slides for P3642R1

P3642R1の紹介スライド。

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

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

P3655R2 zstring_view

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

以前の記事を参照

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

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

などです。

P3663R2 Future-proof submdspan-mapping

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

以前の記事を参照

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

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

などです。

P3668R2 Defaulting Postfix Increment and Decrement Operations

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

以前の記事を参照

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

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

などです。

P3669R2 Non-Blocking Support for std::execution

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

以前の記事を参照

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

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

などです。

P3687R1 Final Adjustments to C++26 Reflection

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

以前の記事を参照

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

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

などです。

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

P3688R1 ASCII character utilities

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

以前の記事を参照

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

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

などです。

P3690R1 Consistency fix: Make simd reductions SIMD-generic

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

以前の記事を参照

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

  • 文言の修正
  • 例の修正

などです。

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

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

以前の記事を参照

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

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

などです。

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

P3692R1 How to Avoid OOTA Without Really Trying

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

以前の記事を参照

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

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

などです。

P3697R1 Minor additions to C++26 standard library hardening

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

以前の記事を参照

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

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

P3699R1 Rename conqueue_errc

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

以前の記事を参照

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

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

などです。

P3705R1 A Sentinel for Null-Terminated Strings

P3705R2 A Sentinel for Null-Terminated Strings

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

以前の記事を参照

R1での変更は

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

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

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

などです。

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

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

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

以前の記事を参照

R1での変更は

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

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

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

などです。

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

P3711R1 Safer StringViewLike Functions for Replacing char* strings

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

以前の記事を参照

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

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

などです。

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

P3713R0 2025-05 Library Evolution Poll Outcomes

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

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

P3718R0 Fixing Lazy Sender Algorithm Customization, Again

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

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

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

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

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

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

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

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

P3719R0 std::is_vector_bool_reference

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

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

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

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

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

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

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

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

このような関数テンプレートにおいては、Allocは推論できないコンテキストにあるため推論に失敗します。

この提案では、std::vector<bool>::referenceをアロケータ型によらずに識別するために、std::is_vector_bool_reference<T>型特性を追加することを提案しています。このような型特性は標準においても説明専用のis-vector-bool-referenceとして存在しているため、これをユーザーが使用できるようにするだけです。

std::is_vector_bool_reference<T>は、Tが任意のアロケータ型Allocに対するstd::vector<bool, Alloc>::reference型を示しており、かつstd::vector<bool, Alloc>がプログラム定義の特殊化(ユーザー定義特殊化)ではない場合にtrueを返すものです。

これを用いると、先ほどのオーバーロードの問題はシンプルに書くことができるようになります

template <typename BitReference>
    requires std::is_vector_bool_reference_v<BitReference>
void read(BitReference b);

P3721R0 Slides for P3639R0

P3639R0の紹介スライド。

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

P3639R0の内容が簡単に解説されています。

P3722R0 Slides for P3568R1

P3568R1の紹介スライド。

主に構文候補の選択過程(WG14 N3355を採用)と、その構文の問題点の解消(重複ラベルの許可)についてを説明しています。

P3724R0 Integer division

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

C++の現在の/は切り捨て除算を行います。しかし、丸めモードは切り捨てだけではなく他のものも有用と思われます。また、標準の丸め方法では割られる数が負の場合などに剰余が負の値になる場合もあり、この動作も望ましくありません。

このような問題を回避するのは非常に単純に思えますが、実際には/に存在する未定義動作を回避しながら丸め方法を変更するのは驚くほど難しく、少し調べて出てくるような方法はほとんどが間違っているか、問題を抱えています(提案文書にはその例がいくつか掲載されています)。さらに、剰余に関しても整数オーバーフローを回避する必要性によってゼロ方向以外の丸めモードで正しく計算するのは困難です。

この提案は丸め方法を指定して商と剰余を求めるライブラリ関数を追加することで、現在の/および%に代わって所望の丸め方法で商と剰余を求める方法を提供しようとするものです。

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

const int bucket_size = 1000;
int elements = 100;

int buckets_required = elements / bucket_size; // WRONG, zero
int buckets_required = std::div_to_inf(elements, bucket_size); // OK, one bucket

提案している関数は、その名前が商を計算するものはdiv_、剰余を計算するものはdiv_rem_で始まり、それに続いて丸め方法が指定される形の命名になっています。

namespace std {
  template<class T>
    constexpr T div_to_zero(T x, T y);
  template<class T>
    constexpr T div_away_zero(T x, T y);
  template<class T>
    constexpr T div_to_inf(T x, T y);
  template<class T>
    constexpr T div_to_neg_inf(T x, T y);
  template<class T>
    constexpr T div_to_odd(T x, T y);
  template<class T>
    constexpr T div_to_even(T x, T y);
  template<class T>
    constexpr T div_ties_to_zero(T x, T y);
  template<class T>
    constexpr T div_ties_away_zero(T x, T y);
  template<class T>
    constexpr T div_ties_to_inf(T x, T y);
  template<class T>
    constexpr T div_ties_to_neg_inf(T x, T y);
  template<class T>
    constexpr T div_ties_to_odd(T x, T y);
  template<class T>
    constexpr T div_ties_to_even(T x, T y);

  template<class T>
    constexpr div_result<T> div_rem_to_zero(T x, T y);
  template<class T>
    constexpr div_result<T> div_rem_away_zero(T x, T y);
  template<class T>
    constexpr div_result<T> div_rem_to_inf(T x, T y);
  template<class T>
    constexpr div_result<T> div_rem_to_neg_inf(T x, T y);
  template<class T>
    constexpr div_result<T> div_rem_to_odd(T x, T y);
  template<class T>
    constexpr div_result<T> div_rem_to_even(T x, T y);
  template<class T>
    constexpr div_result<T> div_rem_ties_to_zero(T x, T y);
  template<class T>
    constexpr div_result<T> div_rem_ties_away_zero(T x, T y);
  template<class T>
    constexpr div_result<T> div_rem_ties_to_inf(T x, T y);
  template<class T>
    constexpr div_result<T> div_rem_ties_to_neg_inf(T x, T y);
  template<class T>
    constexpr div_result<T> div_rem_ties_to_odd(T x, T y);
  template<class T>
    constexpr div_result<T> div_rem_ties_to_even(T x, T y);
}

div_rem_関数の戻り値型のdiv_result<T>は商と剰余の値を同時に保持している集成体型です。

namespace std {
  template<class T>
  struct div_result {
    T quotient;
    T remainder;

    friend auto operator<=>(const div_result&, const div_result&) = default;
  };
}

非常にたくさんの関数がありますが、div_div_rem_で変種は共通しており、それはすべて丸めモードの違いです。

  • to_zero
    • ゼロ方向丸め
    • /と同じ動作
  • away_zero
    • ゼロと逆方向への丸め
  • to_inf
    • 正の無限大方向への丸め(切り上げ
  • to_neg_inf
    • 負の無限大方向への丸め(切り捨て
  • to_odd
    • 奇数丸め
  • to_even
    • 偶数丸め
  • ties_to_zero
    • 最近接丸め(最も近い整数への丸め
    • 最も近い整数が2つある場合はゼロ方向(絶対値の小さいほう)へ丸める
  • ties_away_zero
    • 最近接丸め
    • 最も近い整数が2つある場合は絶対値の大きいほうへ丸める
  • ties_to_inf
    • 最近接丸め
    • 最も近い整数が2つある場合は正の無限大方向(大きいほう)へ丸める
  • ties_to_neg_inf
    • 最近接丸め
    • 最も近い整数が2つある場合は負の無限大方向(小さいほう)へ丸める
  • ties_to_odd
    • 最近接丸め
    • 最も近い整数が2つある場合は奇数のほうへ丸める
  • ties_to_even
    • 最近接丸め
    • 最も近い整数が2つある場合は偶数のほうへ丸める

これに加えて、除算の剰余を常に負の無限大方向へ丸めるstd::mod()も用意しています。

namespace std{
  template<class T>
    constexpr T mod(T x, T y);
}

これは、剰余の符号が割る数と同じになり、モジュラー演算を意図するところで有効に使用できるため専用の関数として用意されています。

P3725R0 Filter View Extensions for Input Ranges

P3725R1 Filter View Extensions for Input Ranges

views::filterの問題点を軽減しようとする提案。

P3329R0で指摘されているように、views::filterには落とし穴がいくつかあります。

この提案でもいくつか新しいサンプルが提供されていますが、この提案はそれらの問題を緩和しようとするものです。

ここでの変更は

  • filter_viewbegin()/end()constオーバーロードを追加
    • 使用可能なのは、入力範囲(const化したもの)がinput_rangeでしかない場合
  • views::filterの入力範囲がinput_rangeでしかない場合は、フィルタ結果要素の変更を許可する
    • 現在は未定義動作
  • views::input_filterアダプタの追加
    • 入力範囲にviews::to_inputを適用して使用するviews::filter
    • 他の変更と合わせて、input_rangeにしかならないことで要素の変更(ムーブ)を許可するなど、既知の問題を緩和する

などです。

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

auto large = [](const auto& s) { return s.size() > 5; }; 
auto sub = coll1 | std::views::filter(large) 
                 | std::views::reverse 
                 | std::views::as_rvalue 
                 | std::ranges::to<std::vector>(); 

このコードは、as_rvalueによってフィルタ後要素をムーブしていることによって未定義動作となっています。実装が同じフィルタ後イテレータを2回以上間接参照したりすると問題が起こります。

auto large = [](const auto& s) { return s.size() > 5; }; 
auto sub = coll1 | std::views::input_filter(large) 
                 | std::views::reverse 
                 | std::views::as_rvalue 
                 | std::ranges::to<std::vector>();  // コンパイルエラー

views::filterを使用したままではこの提案でもこの問題は緩和されませんが、views::input_filterを使用する場合コンパイルエラーとして問題が報告されるようになります。

void constIterate(const auto& coll);  // 引数をconstで受ける

std::vector<std::string> coll3{"Amsterdam", "Berlin", "Cologne", "LA"};

auto large = [](const auto& s) { return s.size() > 5; };
constIterate(coll3 | std::views::filter(large));  // コンパイルエラー

現在のviews::filterはconst-iterableではないためエラーになりますが、views::input_filterを使用することで使用できるようになります。

SG9のレビューにおいては大きな反対意見はなかったようで、LEWGに転送されることが投票で可決されています。

P3726R0 Adjustments to Union Lifetime Rules

トリビアルな共用体を定数式で使用可能にするために制限を緩和する提案。

P3074R7では、共用体を使用した次のような実装によるinplace_vectorを定数式で使用可能にするための共用体のルールの調整が行われました。

template <typename T, size_t N>
struct FixedVector {
  union { T storage[N]; };
  size_t size = 0;

  constexpr FixedVector() = default;

  constexpr ~FixedVector() {
    std::destroy(storage, storage + size);
  }

  constexpr auto push_back(T const& v) -> void {
    ::new (storage + size) T(v);
    ++size;
  }
};

constexpr auto silly_test() -> size_t {
  FixedVector<std::string, 3> v;
  v.push_back("some sufficiently longer string");
  return v.size;
}

static_assert(silly_test() == 1);

ここでのunion { T storage[N]; };の役割は定数式で使用可能な遅延初期化用ストレージです。以前はこれが定数式で使用可能ではありませんでしたが、P3074R7にて定数式で使用可能になるようにいくつかの調整が加えられ、C++26で利用可能になっています。

P3074R7の変更はおおむね次のものです

  • 共用体にトリビアルなデフォルトコンストラクタとトリビアルなデストラクタを暗黙的に定義させる
  • 共用体の最初のメンバがimplicit-lifetime typeである場合、そのメンバの生存期間を暗黙的に開始する

しかし、その後寄せられたフィードバックによってこれだけでは目的を達成できないことが分かりました。

問題は、次のようなコードがP3074R7によって振る舞いが変わることです

union U { int a, b; };

template<U u>
class X {};

constexpr U make() { U u; return u; }

void f(X<make()>) {}

P3074R7以前は、X<make()>のNTTPはアクティブメンバを持たない共用体オブジェクトだったのでNTTPとして有効でした。しかし、P3074R7の変更によってこのuは最初のメンバの生存期間暗黙的に開始されることによってアクティブメンバを持つため、NTTPとして使用できずコンパイルエラーとなります。

また、仮にコンパイルが通ったとすると、アクティブメンバを持つことからマングル名を変更する必要があるため、ABI破損を引き起こす可能性があります。

アクティブメンバを持つようになるとNTTPとして適格でなくなるのは少し複雑ですが、まず[expr.const]にある定数式の制限のうち、prvalue式の結果オブジェクトの構成要素の値(constituent value)についての制限があり、そこではスカラ型の構成要素の値は不定値もしくはerroneous valueを持っていてはならない、という規定があります。

そして、この構成要素の値の定義は次のようになっています

The constituent values of an object o are

  • if o has scalar type, the value of o;
  • otherwise, the constituent values of any direct subobjects of o other than inactive union members.

問題は2つ目の条件で、共用体型を含むクラス型の場合、構成要素の値とはそのサブオブジェクトの構成要素の値の集合の事を言っており、そこには共用体の非アクティブメンバは含まれません。

共用体先頭のimplicit-lifetime typeメンバの生存期間が開始されるとそのメンバがアクティブメンバになるものの、その値は初期化されていないため不定値を持つことになります。

これによって、スカラ型メンバのみで構成されている共用体がNTTP(というか定数式内prvalue)として使用される際の振る舞いが変化しています。

このため、P3074R7の目的はまた達成されていません

constexpr std::inplace_vector<int, 4> v = {1, 2}; // ng

このコードは先ほどのFixedVectorの実装と同じ実装になる場合、union { int storage[4]; }が使用されるものの、最初の2つの要素だけが初期化されて残りの要素は初期化されないことで不定値を持ち、コンパイルエラーになります。

ここでの問題は次の2点です

  1. 共用体が定数式で意図しないタイミングでアクティブメンバを持ってしまうこと
  2. 構成要素の値の制限

この提案はこれら2つの問題をそれぞれ解決しようとするものです。

1つ目の問題についてはP3074R7の変更をrevertし、トリビアルな共用体のimplicit-lifetime typeメンバの生存期間が自動開始されるポイントをplacement newまで遅延します。

template <typename T, size_t N>
struct FixedVector {
  union { T storage[N]; };  // P3074R7はここで暗黙的にstorageの生存期間が開始されていた
  size_t size = 0;

  constexpr FixedVector() = default;

  constexpr ~FixedVector() {
    std::destroy(storage, storage + size);
  }

  constexpr auto push_back(T const& v) -> void {
    ::new (storage + size) T(v);  // この提案では、このアクセス時に暗黙的にstorageの生存期間を開始する
    ++size;
  }
};

constexpr auto silly_test() -> size_t {
 FixedVector<std::string, 3> v;
 v.push_back("some sufficiently longer string");
 return v.size;
}

static_assert(silly_test() == 1);

元々の問題の一部は、placement newの際にstorageメンバにアクセスしたときにその生存期間が開始されていない事だったので、placement newアクセス時にそれが暗黙的に行われるようになることで元の問題を解決できます。そして、union U { int a, b; };の様な共用体は再びアクティブメンバを持たなくなります。

2つ目の問題については、構成要素の値の定義を変更して、共用体内配列の各要素の初期化状態について歯抜けを許容するようにします。これにより、共用体内配列のどこかの要素が初期化されていなくても、それは非アクティブメンバと同様に構成要素の値としてカウントされ無くなるため、配列そのものの生存期間が開始されている場合にも共用体の構成要素の値内に不定値が含まれなくなります。

これらの修正(特に2つ目)によって、先ほどのinplace_vectorの例は定数式で動作するようになります。

constexpr std::inplace_vector<int, 4> v = {1, 2}; // ok、この提案後

この提案はこの記事執筆時点(C++26の機能凍結後)でもまだ採択前ですが、トリビアルな共用体について多数のNBコメントが寄せられており、その解決のために遅れてC++26に採択される予定です。

P3727R0 Update Annex E based on Unicode 15.1 UAX #31

C++標準文書のAnnex E(UAX31への参照)の記述をUnicode 15.1に合わせて更新する提案。

背景等はP3717R0を参照

この提案では、P3717とは異なり、Unicode 16ではなくUnicode 15.1でのUAX31の形式に合わせて更新しようとしています。主な変更はP3717同様に「UAX31-R1a Restricted Format Characters」に関する記述を削除することで、こちらの提案はCWG Issue 2843の解決のみを(Unicode 15.1で)行うことを意図したものの様です。

SG16などでのレビューの結果、結局CWG Issue 2843の変更を採用することになったようです。

P3729R0 Aligning span and string_view

std::spanstd::string_viewのインターフェース共通化を図る提案。

std::spanstd::string_viewはそれぞれ、配列/文字列のビューとなるクラスです。とはいえ、std::string_viewが文字列に特化しているだけで、二つのクラスの実装はほぼ同じになり、その扱いや性質もかなり共通しています。

しかし、std::spanstd::string_viewは標準コンテナのインターフェースに準拠したインターフェースについては共通しているものの、それ以外の部分はそれほど共通のインターフェースにはなっていません。

この提案は、std::spanstd::string_viewからそれぞれにだけしかないインターフェースについて、特にサブセット化APIの共通化を行おうとするものです。

提案するインターフェースの追加は次のようになります

template<typename charT, typename traits = char_traits<charT>> 
struct basic_string_view { 
  ...

  // runtime subsetting: 
  constexpr basic_string_view substr(size_type pos = 0, size_type n = npos) const; 
  constexpr basic_string_view subview(size_type pos = 0, size_type n = npos) const; 
  constexpr basic_string_view first(size_type count) const; // この提案
  constexpr basic_string_view last(size_type count) const;  // この提案
  
  // in place shrinking: 
  constexpr void remove_prefix(size_type n); 
  constexpr void remove_suffix(size_type n); 

  ...
};

template<typename T, size_type E = dynamic_extent> 
struct span { 
  ...

  // compile-time subsetting: 
  template<size_t Offset, size_t Count = dynamic_extent> 
  constexpr span<T, /*see below*/> subspan() const; 
  template<size_t Count> 
  constexpr span<T, Count> first() const; 
  template<size_t Count> 
  constexpr span<T, Count> last() const; 

  // runtime subsetting: 
  constexpr span subspan(size_type pos = 0, size_type n = dynamic_extent) const; 
  constexpr span first(size_type count) const; 
  constexpr span last(size_type count) const; 

  // in place shrinking: 
  constexpr void remove_prefix(size_type n) requires(E == dynamic_extent); // この提案
  constexpr void remove_suffix(size_type n) requires(E == dynamic_extent); // この提案

  ...
};

すなわち、std::span -> std::string_viewには

  • .first(n) : 先頭n文字のstring_viewを返す
  • .last(n) : 後ろn文字のstring_viewを返す

が移植され、std::string_view -> std::spanには

  • .remove_prefix(n) : 先頭n要素のspanを返す
  • .remove_suffix(n) : 後ろn要素のspanを返す

が移植されます。

さらに、std::string_viewに関する変更はstd::stringに対しても適用されます

template<typename charT, typename traits = char_traits<charT>, typename Allocator = allocator<charT>> 
struct basic_string { 
  ...

  //runtime subsetting: 
  constexpr basic_string substr(size_type pos = 0, size_type n = npos) const; 
  constexpr basic_string_view<charT, traits> subview(size_type pos = 0, size_type n = npos) const; 
  constexpr basic_string_view<charT, traits> first(size_type count) const; // この提案
  constexpr basic_string_view<charT, traits> last(size_type count) const;  // この提案
  
  ...
};

std::string_view vstd::span<T> pstd::string sがあるとして

現在 この提案
// removing leading elements 
v.remove_prefix(5); 
p = p.subspan(5); 
s.erase(s.begin(), s.begin() + 5); 

// removing trailing elements 
v.remove_suffix(3); 
p = p.subspan(0, p.size() - 3); 
s.erase(s.end() - 3, s.end());
// removing leading elements 
v.remove_prefix(5); 
p.remove_prefix(5); 
//s.remove_prefix(5); 提案しない

// removing trailing elements 
v.remove_suffix(3); 
p.remove_suffix(3); 
//s.remove_suffix(3); 提案しない
現在 この提案
// getting leading elements 
auto l4v = v.subview(0, 4);
auto l4p = p.first(4);
auto l4s = s.subview(0, 4);

// getting trailing elements 
auto t2v = v.subview(v.size() - 2);
auto t2p = p.last(2);
auto t2s = s.subview(s.size() - 2);
// getting leading elements 
auto l4v = v.first(4);
auto l4p = p.first(4);
auto l4s = s.first(4);

// getting trailing elements 
auto t2v = v.last(2);
auto t2p = p.last(2);
auto t2s = s.last(2);

P3730R0 Slides for P3104R3

P3104R3の紹介スライド。

P3104R3で提案されている4つのビット操作関数の簡単な説明が行われています。

P3731R0 #embed Preprocessor Parameter Order

#embedディレクティブの引数指定について順番を指定するようにする提案。

#embedディレクティブは読み込むバイナリリソースについて、読み込みの仕方などについてを指定する4つのオプションがあります。そのうちlimitオプションは読み込むサイズを制限するものです。

constexpr const unsigned char sound_signature[] = {
// a hypothetical resource
#embed <sdk/jump.wav> limit(2+2)
};

また、P3540で提案中のオプションとしてoffsetがあり、これは読み取るバイナリリソースの開始位置を指定するものです。

constexpr const unsigned char truncated_sound_signature[] = {
// a hypothetical resource
#embed <sdk/jump.wav> offset(2) limit(2)
};

このオプションは排他的ではなく、同時に指定して効果を重複させることができます。その際、オプションの指定順には意味がなく、どのような順序で指定しても同じ動作をします。

しかし、(おそらくP3540の議論中に)このオプションの指定順序、特にlimitoffsetの順序が混乱をもたらす可能性があるとの指摘があったようです。その際寄せられた意見の中には、limitの後にoffsetを指定された場合にコンパイルエラーを出すべき、というものもあったようです。

この提案はそれを受けて、オプションの順番を規定するための文言を提供しようとするものです。ここでは、次の二種類の文言が用意されています

  1. オプションの順番を規定し、違反をコンパイルエラーとする文言
  2. オプションの順番を規定し、違反を警告とすることを推奨する文言

提案ではこの選択を委員会の決定に委ねています。

いずれの文言でも、ここで提案している順序付けは次のようなものです

  • offsetlimitの前
  • limitif_empty, prefix, suffixの前
  • limitがない場合でも、offsetif_empty, prefix, suffixの前

#embedはC23で先んじて採択されているため、clang/gccの新しいものであれば実装済みです。さらにそこでは、実装定義のオプションとしてoffsetオプション(gnu::offset/clang::offset)が実装されていますが、現在の#embedが意図するようにオプションの指定順によらず正しく動作しています。

P3732R0 Numeric Range Algorithms

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

C++20でアルゴリズムのRange対応がなされ、C++26では並行アルゴリズムのRange対応がなされています。しかし、そのどちらにおいても意図的に数値アルゴリズムは除外されています。これは数値アルゴリズムは通常のアルゴリズムとは少し異なる要件を持っていることから、まとめて扱えない(あるいは扱うべきではない)と判断されているためだと思われます。

C++26で通常のアルゴリズムが並行アルゴリズムまで含めて(ほぼすべて)Range対応したことで、その設計を踏襲する形で数値アルゴリズムのRange対応(並行版も含めて)を行おうとするのがこの提案のようです。

ただし、全ての数値アルゴリズムに対してRange版を追加することは提案しておらず、ここでは次のものだけを提案しています

  • std::reduce
  • std::transform_reduce
    • 単項/二項両方
  • std::inclusive_scan
  • std::exclusive_scan
  • std::transform_inclusive_scan
  • std::transform_exclusive_scan

これ以外のものはRangeアダプタ等既存のもので代替できるか、設計について特別な議論が必要となるためここでは除外しています。

そして、提案されているこれらのRange版アルゴリズムは、<algorithm>にあるものとは異なり射影をサポートしません。これは、reduce, inclusive_scan, exclusive_scanで射影を指定することは、それらに対応する(射影なし)transform版を使用することと同等になるためです。

struct foo {};

std::vector<std::tuple<int, foo, std::string>> v1 = 
{
  {5, {}, "five"}, {7, {}, "seven"}, {11, {}, "eleven"}
};

constexpr int init = 3;

// reduce with projection get_element<0>
auto result_proj =
  std::ranges::reduce(v1, init, std::plus{}, get_element<0>{});
assert(result_proj == 26);

// transform_reduce with unary transform get_element<0>
auto result_xform =
  std::ranges::transform_reduce(v1, init, std::plus{}, get_element<0>{});
assert(result_xform == 26);

// reduce with transform_view (no projection)
auto result_xv = std::ranges::reduce(
  std::views::transform(v1, get_element<0>{}), init, std::plus{});
assert(result_xv == 26);

※ここでのget_elementはP2769R3で提案されているもので、まだ正式機能ではありません。tuple-likeあるいは構造体から指定したインデックス番目の要素を抽出するものです。

Rangeアルゴリズムにおける射影の提供する一番のメリットは、要素の引き当てと変換という2つの操作を分離して指定することで可読性を向上させ、なおかつ最適化機会を提供することにあります。数値アルゴリズムの場合、そのインターフェースのために射影を用意せずとも同等の事を達成できます。

また、既存のfoldアルゴリズムranges::reduceに最も近い動作をする)はすべて射影を取らないこともこの根拠としています。

transform_reduceに射影を追加しないのは、transform_reduceのための演算の指定と射影の指定の順序が曖昧になるためです。

struct bar {
  std::string s;
  int i;
};

std::vector<std::tuple<int, std::string, bar>> v = 
{
  { 5,   "five", {"x", 13}},
  { 7,  "seven", {"y", 17}},
  {11, "eleven", {"z", 19}}
};

constexpr int init = 3;

// 射影されてから(get_element<2>で`bar`を取り出してから)、単項演算(get_element<1>)が適用される
// first get bar, then get bar::i
auto result_proj = std::ranges::transform_reduce(
  v, init, std::plus{}, get_element<1>{}, get_element<2>{});  // 適用の順序が逆に見える
assert(result_proj == 52);

// first get bar, then get bar::i
auto getter = [] (auto t) {
  return get_element<1>{}(get_element<2>{}(t)); // imagine that get_element works for structs
};
auto result_no_proj = std::ranges::transform_reduce(
  v, init, std::plus{}, getter);
assert(result_no_proj == 52);

二項のtransform_reduceviews::zip_transformを用いてranges::reduceによって同等の事を達成することができますが、可読性を損なうことやzip_transform_viewという別のレイヤが介在することによる最適化機会の損失などの理由により二項のtransform_reduceもRange対応させることを提案しています。

この時、二項のranges::transform_reduceだけは射影があったほうがパフォーマンス上のメリットがある場合があります(要素の一部の抽出をしながらtransform_reduceする際に、views::transformが必要になる)が、インターフェースの一貫性と可読性を優先させ、これに対しても射影サポートは追加していません。

これら一連の議論では(transform_)reduceで代表して説明しましたが、inclusive_scan, exclusive_scanでも同様の事が言えます。

さらに、利便性のためのranges::reduceのラッパアルゴリズムを提供することも提案しています。

  • ranges::sum(rng): ranges::reduce(rng, range_value_t<R>(), plus{})
    • 要素型をデフォルト構築したものを初期値として、rngの総和を求める
    • 演算の順序が不定std::accumulateのデフォルト動作に対応
  • ranges::product(rng): ranges::reduce(rng, range_value_t<R>(1), multiplies{})
    • 要素型をデフォルト構築したものを初期値として、rngの総乗を求める
  • ranges::dot(x, y): ranges::transform_reduce(x, y, T(), plus{}, multiplies{})
    • T = decltype(declval<range_value_t<X>>() * declval<range_value_t<Y>>())
    • 2つの範囲のドット積を求める

要素型の決定などの細部はもう少し複雑になります。これらはranges::reduceにデフォルト引数を一切持たせないようにすることを選択したことによって生まれるラッパです。

ラッパも含めたすべてのアルゴリズムは対応する並行アルゴリズムもRange版が用意されます。

これらのアルゴリズムの入力と出力の型は次のように決定されています

特に、この提案は非並行アルゴリズムで範囲出力にイテレータではなくoutput_rangeを取る初めてのものとなります(inclusive_scan, exclusive_scan)。

この提案の設計は多くの部分で既存の類似物の設計を踏襲しています。例えば

  • ranges::fold_left,ranges::fold_rightと同じ制約によって二項演算を制約し、戻り値型を導出する
  • C++17とlinalgを模倣して、GENERALIZED_NONCOMMUTATIVE_SUMGENERALIZED_SUMを用いてreduce*_scanの動作を指定する
  • それ以外のところでは、P3179R8(並行Rangeアルゴリズム提案)のアプローチに従う
  • P2248R8(Rangeアルゴリズムにおいて値の指定に{}初期化を使用できるようにする)の内容を適用

などです。

P3733R0 More named universal character escapes

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

C++23では文字/文字列リテラルにおけるエスケープ文字の一種として\N{NO-BREAK SPACE}のようにユニコード文字の名前を指定する形のユニバーサル文字名が使用可能になっています。この名前はユニコード標準で規定されているもので、一部の文字に対してはエイリアスとして複数の名前が対応する場合があります。

static_assert(u8'\N{LINE FEED}' == u8'\N{NEW LINE}'); // ✅

このエイリアスユニコード標準で規定されているもので、エイリアスにはいくつかの種類があります

  • correction Aliases : 誤った文字名が割り当てられてしまった場合の代替表記
  • control Aliases : 制御文字のエイリアス
  • alternate Aliases : 広く使用されている代替名のエイリアス
  • figment Aliases : 文書化されたものの標準では採用されなかったエイリアス
    • 現在次の3つのみ
    • U+0080 PADDING CHARACTER
    • U+0081 HIGH OCTET PRESET
    • U+0099 SINGLE GRAPHIC CHARACTER INTRODUCER
  • abbreviation Aliases : 一般的な略語によるエイリアス

ユニコード標準の意図として、一度公開した名前はたとえそれが間違っているものだったとしても変更せず、代わりのエイリアスを提供することによって修正するという方針があります(すなわち、安定性を強く保証している)。correctionエイリアスはそのためにあるものです。

これらのうち、C++標準ではfigmentとabbreviationのエイリアスが除外されており、使用できません。この提案は、これらのエイリアスを使用可能にすることを提案しています。

なぜこのようになっているかというと、P2071R2(ユニコード文字名エスケープの提案)の議論と採択の当時のC++が参照していたユニコード標準が古かったため(正確には参照先が異なったため)、これらのエイリアスが含まれていなかったからの様です。

しかしその後P2736R2の採択によって参照先が更新されたためこのような制限は無くなっており、C++標準の文書の変更のみでこれらのエイリアスに対応することができます。

この提案で使用可能になるエイリアスの例

using namespace std::literals;

// U+007F
static_assert(u8'\N{DELETE}' == u8'\N{DEL}'); // `DEL`はabbreviationエイリアス

// U+0081
static_assert(u8"\u{0081}"sv == u8"\N{HIGH OCTET PRESET}"sv); // `HIGH OCTET PRESET`はfigmentエイリアス

P3734R0 Not all predicates must be regular

述語を使用するアルゴリズムに対する、その述語が要素を変更しないという要件を緩和する提案。

std::find_ifなど(そのrange版も含めて)述語を受け取るアルゴリズムにおいて述語はregular_invocableによって制約されており(indirect_unary_predicate -> predicateを通して)、それによって述語の呼び出しを通じた範囲要素の変更やその他副作用が禁止されています。

しかし、範囲のイテレーションを一回で済ませることを意図してこれらのアルゴリズムの述語において要素を変更したい場合があり、特に要素を一回しか参照しないアルゴリズムではそれを行ったとしても動作には影響がないはずです。

ranges::remove_ifによる例

bool allowed_in_id(char c) {
  return c >= 'a' && c <= 'z';
}

void sanitize_id(std::string& id) {
  auto removed_range = std::ranges::remove_if(id, [](char& c) {
    if (c == ' ') {
      c = '-';  // 👈 ここで要素を変更している
      return false;
    }
    return not allowed_in_id(c);
  });

  id.erase(removed_range.begin(), removed_range.end());
}

このコードは"abc //def"の様な文字列入力を"abc-def"のように変換するものです。これは[a-z]とスペース以外の文字をremove_ifした後でスペースを-に置き換えるような2回のアルゴリズムを伴うコードよりも効率的なものです。しかし、前述のようにremove_ifranges版でなくても)はその述語が要素を変更することを許可しておらず、違反すると未定義動作となるためこのコードは合法ではありません。

しかし一方で、remove_ifはその動作に当たって要素を一回しか参照しないため、その一回の参照時にその要素を変更したとしてもその動作がおかしくなることはありません。上記の例はそのことを仮定しており、標準ライブラリの複雑な要件を良く知らない一般ユーザーにとっては自然な推論となるはずです。

この提案は、述語を受け取るアルゴリズムのうち要素を一度しか参照しないなどの事から要素を変更しても問題ないものについては、述語に対するregular_invocableの要件を緩和することを提案するものです。

ここでの対象は次のものです

  • all_of
  • any_of
  • none_of
  • find
  • find_last
  • count
  • mismatch
  • equal
  • starts_with
  • copy_if
  • replace
  • remove
  • is_partitioned/partition

regular_invocableの制約は意味論的なものであり、この提案が採択されても実装を何か変更する必要はないはずです(制約を除いて)。

このような変更に対する懸念の一つはこれら範囲を変更しないアルゴリズムの冪等性が失われるというものだと思われますが、実際には現状でもその保証は無く、未定義動作の上に成り立っている淡い期待のみであり、実装によって何か静的・動的にチェックされているものではありません。範囲を変更する述語を渡さない限り現在の推論が失われるものでもないため、現状より悪くなるものではないとしています。

P3735R0 partial_sort_n, nth_element_n

中間イテレータを指定する必要があるアルゴリズムについて、その必要のないバージョンを追加する提案。

partial_sortアルゴリズムは、範囲のN要素分の部分ソートを行うアルゴリズムであり、実行後の範囲は先頭N個がソートされた状態になります。この時、このNは数値で指定するのではなく、イテレータで指定する必要があります。

#include <ranges>
#include <algorithm>
#include <vector>
#include <print>

int main() {
  constexpr int N = 5;
  std::vector vec = {5, 3, 10, 4, 1, 11, 0, 9};

  std::println("before: {}", vec);

  std::ranges::partial_sort(vec, vec.begin() + N);

  std::println("after: {}", vec);
}
before: [5, 3, 10, 4, 1, 11, 0, 9]
after: [0, 1, 3, 4, 5, 11, 10, 9]

partial_sortで部分ソートしたい要素数Nイテレータで指定する際は、begin() + Nのようなイテレータの進め方がなされることが多々あります。これは当然、Nが範囲の長さを超えていれば未定義動作となります。筆者の方の経験によれば、このように未定義動作に陥っているコードを何度も見てきたとのことです。

安全に書く方法としてはstd::ranges::next()を使用する方法があります

std::ranges::partial_sort(vec, std::ranges::next(vec.begin(), N, vec.end()));

しかしこれはあまりにも冗長で、バグの本質的原因を解決するものでもありません。このバグは、アルゴリズムがユーザーにイテレータの加算を強いていることが原因です。そのため、Nを直接アルゴリズムに指定できれば解決されるはずです。

std::ranges::partial_sort(vec, N);

この提案は、partial_sortの様に中間イテレータをとるアルゴリズムについてこのように処理数Nを直接指定できるようにする~_nバージョンを追加しようとするものです。ここで提案しているのは次の2種類のアルゴリズムです

  • partial_sort_n : partial_sort
  • nth_element_n : nth_element

他のアルゴリズムはランダムアクセスイテレータを使用していないなどの問題からここでは対象にしていません。また、変更はrange版だけでなく旧来の非rangeアルゴリズムに対しても行うことを提案しています。

SG9のレビューではこの提案の方向性が好まれたようで、おおむね受け入れられてLEWGに転送されています。ただし、命名については_nではなく_at_mostが好まれたようです。

P3736R0 Slides against P2971R3 - No implication for C++

P2971に反対することを説明するスライド。

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

P2971で提案されている=>演算子に反対するいくつかの理由が説明されています。

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

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

std::arrayの実装については標準はあまり多くを語っていないものの生配列をラップした集成体型によるものと理解されており、現在の標準ライブラリ実装はそのようになっています。その実装方法についてはほぼ他の選択肢が無いのですが、より細かい実装については指定していない部分も多く、例えば次のような実装が準拠実装となります

struct alignas(1024) malice_and_evil {
  constexpr malice_and_evil() { }
  malice_and_evil(const malice_and_evil&) { }
};

template <typename T, size_t N>
struct array {
  T __array[N];
  malice_and_evil evil;
};

このように実装されたstd::arrayはコピーが定数式にならず、要素型に関わらずトリビアルコピー可能ではなく、サイズとアライメントはT[N]よりも大きくなります。

この提案は、std::arrayの実装自由度を制限することでこのようなおかしな準拠実装を排除し、std::arrayが生配列の単純なラッパとなるようにしようとするものです。また、std::array<T, 0>の実装についてを細かく規定するようにしています。要素数がゼロのstd::arrayについては実装もそれぞれ微妙に異なっているようで、この提案ではstd::array<T, 0>が満たすべき実装の制約についてを追加しています。

  • Tトリビアルコピー可能・標準レイアウト・構造的な型、のいずれか(もしくはすべて)であるなら、std::arrayもそうなる
  • std::arrayは基底クラスを持たず、単一の公開非staticメンバとしてN要素の生配列を持つ
  • std::array<T, 0>
    • 配列メンバ変数を持たない
      • 代わりに、未規定のトリビアルコピー可能で標準レイアウトな空の集成体型Uの非staticメンバ変数を持つ
      • Uのサイズとアライメントは1 ~ sizeof(T)の間で実装定義
    • 値表現は空
    • イテレータ取得関数はすべて値初期化された結果を返す
    • fill()/swap()は関数本体が空で例外を送出しない
    • [], front(), back()の末尾に実行が到達するとstd::terminate

などの制限を加えることを提案しています。

P3738R0 Make std::make_from_tuple SFINAE friendly

std::make_from_tupleをSFINAE friendlyにする提案。

LWG Issue 3528の採択によって、std::make_from_tuple()の説明専用関数であるmake-from-tuple-impl()に対してrequires節による制約が追加されました。

template<class T, class Tuple, size_t... I>
  requires is_constructible_v<T, decltype(get<I>(declval<Tuple>()))...> // 👈 これが追加
constexpr T make-from-tuple-impl(Tuple&& t, index_sequence<I...>) {
  return T(get<I>(std::forward<Tuple>(t))...);
}

しかし、これによってstd::make_from_tuple()が呼び出し可能かどうかをSFINAEによって調べるコードがハードエラーを起こすようになる場合があります。

template <typename T, typename Tuple, typename = void>
inline constexpr bool has_make_from_tuple = false;

template <typename T, typename Tuple>
inline constexpr bool has_make_from_tuple<
  T, Tuple,
  std::void_t<decltype(std::make_from_tuple<T>(std::declval<Tuple>()))>> = true;

struct A {
  int a;
};

// make_from_tuple()呼び出しにおいて集成体初期化は考慮されない
static_assert(!has_make_from_tuple<int*, std::tuple<A*>>);  // ng、`static_assert`ではない理由によってエラーになる

これは、std::make_from_tuple()の実装が内部でmake-from-tuple-impl()と同等の関数を呼び出すようにしている場合、追加された制約を満たさない場合にstd::make_from_tuple()関数定義内でエラーになることでハードエラーになってしまっています。

template<class T, class Tuple, size_t... I>
  requires is_constructible_v<T, decltype(get<I>(declval<Tuple>()))...> // A
constexpr T make-from-tuple-impl(Tuple&& t, index_sequence<I...>) {
  return T(get<I>(std::forward<Tuple>(t))...);
}

template<class T, class Tuple, size_t... I>
constexpr T make_from_tuple(Tuple&& t, index_sequence<I...>) {
  return make-from-tuple-impl(std::forward<Tuple>(t));  // Aの制約が満たされない場合、ここでエラーになってしまう
}

この提案は、このmake-from-tuple-impl()に追加された制約をstd::make_from_tuple()の宣言に移すことで、制約が満たされない場合にstd::make_from_tuple()の宣言で置換可能なエラーになるようにしようとするものです。

P3739R0 Standard Library Hardening - using std::optional

P3739R1 Standard Library Hardening - using std::optional

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

この提案では、std::inplace_vector特有のメンバ関数で現在ポインタを返しているものについてstd::optional<T&>を代わりに返すようにすることで、std::optional<T&>モナド/rangeインターフェースを利用できるようになるとともに、一部の安全ではないアクセスも堅牢化モードによるnullチェックされることにより安全性を高めようとするものです。

提案しているのは、コンテナの末尾要素を追加を試行するtry_~系の3つの関数です

namespace std {
  template<class T, size_t N>
  class inplace_vector {
  
    ...

    // 現在の宣言
    template<class... Args>
    constexpr pointer try_emplace_back(Args&&... args);

    constexpr pointer try_push_back(const T& x);
    
    constexpr pointer try_push_back(T&& x);

    // この提案
    template<class... Args>
    constexpr optional<T&> try_emplace_back(Args&&... args);

    constexpr optional<T&> try_push_back(const T& x);
    
    constexpr optional<T&> try_push_back(T&& x);

  };
}

これらの関数はinplace_vectorの末尾に要素を追加を試みて、そのキャパシティ余裕が無い場合にnullptrを返すものです。この提案ではポインタの代わりにoptional<T&>を使用することでこのインターフェースをより安全にしようとしています。

int main() {
  std::inplace_vector<int, 5> vec = {1, 2, 3, 4, 5};

  // 現在
  if (auto ptr = vec.try_push_back(6); ptr != nullptr) {
    // 成功した時の処理
  }

  // この提案
  vec.try_push_back(7).and_then([](int& n) {
    // 成功した時の処理
  });
}

P3740R0 Last chance to fix std::nontype

P3740R1 Last chance to fix std::nontype

std::nontypeの使用を再考する提案。

std::nontypestd::function_refメンバ関数ポインタ対応を行うために追加されたユーティリティで、メンバ関数ポインタをNTTPとして持っておくことで本来関数ポインタを保存するためのストレージを対応するthisを保存するために使用できるようにするものです。

void f(std::fuction_ref<int(int)> func);

struct S {
  int m(int) {
    ...
  }
};

int main() {
  S s{};

  f({std::nontype<&S::m>, s});  // ok、function_refでメンバ関数ポインタと対応するthisを束縛する
}

std::nontypeがない場合、別途ファクトリ関数と専用コンストラクタを用意して対応する必要があるなど、std::nontypeと比較すると複雑なアプローチをとる必要がありました。

しかし、C++26の他の機能の導入などの結果として、このstd::nontypeにはいくつかの問題が生じています。

  • std::constant_wrapperと機能が重複する(P2781
  • non-type template parameterという言葉はconstant template parameterに変更された(P2841

どちらも最終的にはC++26に導入されています(この提案の提出時点ではP2781はまだ入っていませんでした)。そのため、std::nontypeの名前を変更するのか、std::constant_wrapperで置き換えるのか、あるいはこのままいくのか、を決めなければなりません。

ここではstd::nontypeの必要性などから初めて様々な選択肢を検討した結果

  • std::nontypestd::constant_wrapperで置き換える
  • std::nontypeをリネームする

のどちらかが良いとして、両方に対応する文言変更を提案しています。

P3741R0 views::set_operations

集合演算を行うRangeアダプタの提案。

ソート済み範囲を集合と見立てて差集合や共通部分などの集合演算を行う操作は、アルゴリズムとしては存在しています。しかし、アルゴリズムの場合は出力の範囲を別に用意する必要があります。

一方、Rangeアダプタとしてこれを実行できれば、遅延評価されることによって出力用の範囲は必要なくなり、オンザフライで集合演算を実行できます。

// アルゴリズムによる集合演算
std::vector<int> diff;
ranges::set_difference(v1, v2, std::back_inserter(diff));

// Rangeアダプタによる集合演算
auto diff = v1 | views::set_difference(v2);

この提案は、既存の集合演算アルゴリズムに対応する集合演算を行うRangeアダプタを提案するものです。ここでは、std::includeを除いた4つの集合演算

  • std::set_union
  • std::set_intersection
  • std::set_difference
  • std::set_symmetric_difference

に対応するRangeアダプタ

  • views::set_union
  • views::set_intersection
  • views::set_difference
  • views::set_symmetric_difference

を提案しています。

これらのRangeアダプタはそれぞれ専用のview型を持ちますが、それぞれの集合演算の性質を踏まえて制約等は大きく2つのタイプに分かれます

// set_intersectionとset_differenceのview型の制約
template<view V1, view V2>
  requires input_range<V1> && input_range<V2> &&
           indirect_strict_weak_order<ranges::less, iterator_t<V1>, iterator_t<V2>>
class set_[intersection|difference]_view;
// set_unionとset_symmetric_differenceのview型の制約
template<view V1, view V2>
  requires input_range<V1> && input_range<V2> &&
           indirect_strict_weak_order<ranges::less, iterator_t<V1>, iterator_t<V2>> &&
           concatable<V1, V2>
class set_[union|symmetric_difference]_view;

前者は片方の範囲(V1)の要素型を結果とすればよいのに対して、後者は両方の範囲の要素型を結果とする必要があるため、concatable<V1, V2>の違いがあります。

どのアダプタも範囲v1, v2に対してv1 | views::set_xxx(v2)の様にパイプライン記法で記述できるようにしています。

4つのアダプタ(view)いずれの場合でもそのイテレータによって演算が行われます。まずイテレータが構築されるとsatisfy()関数(その集合演算に対応した要素判定関数)によって最初の出力要素が特定され、その後は++イテレータが進むごとに次の集合要素を特定していきます。動作としてはviews::filterに近いです。

このsatisfy()関数では2つの入力範囲の両方を順番に進めていく必要があるため、イテレータは2つの入力範囲のイテレータと番兵を共に保持している必要があります。

// set_intersectionのviewのイテレータの実装イメージ
class set_intersection_view::iterator {
  iterator_t<V1> current1_;
  sentinel_t<V1> end1_;
  iterator_t<V2> current2_;
  sentinel_t<V2> end2_;

  constexpr void satisfy() {
    while (current1_ != end1_ && current2_ != end2_) {
      // V1の中から次の有効な要素を探す処理
      ...
    }
  }

  constexpr iterator(iterator_t<V1> current1, sentinel_t<V1> end1,
                     iterator_t<V2> current2, sentinel_t<V2> end2)
    : current1_(std::move(current1)), end1_(end1),
      current2_(std::move(current2)), end2_(end2) {
    satisfy();
  } 

public:
  constexpr decltype(auto) operator*() const { return *current1_; }

  constexpr iterator&
  operator++() {
    ++current1_;
    ++current2_;
    satisfy();
    return *this;
  }

  friend constexpr bool operator==(const iterator& x, default_sentinel_t) {
    return x.current1_ == x.end1_ || x.current2_ == x.end2_;
  }
};

基本的にはこのようなイテレータ実装の細部を少しづつ調整することで4つのviewを実装することができます。set_union/set_symmetric_differenceの場合は両方のイテレータから値を読み取る必要があるため、ある時点でどちらのイテレータを使用するべきかを表すフラグが一つ必要になります。

その他の性質は次のようになっています

  • rangeカテゴリ: もっとも強くてもforward_range
  • common_range: ×
  • borrowed_range: V1V2がともにborrowed_rangeなら
  • const-iterable
    • set_union: 〇
    • それ以外: ×
      • begin()のキャッシュの必要がある

サンプルコード

int main() {
  const auto v1 = std::views::iota(1, 6);
  const std::vector v2 = {1, 3, 5, 7};

  auto uni = v1 | views::set_union(v2);
  // [1, 2, 3, 4, 5, 7]
  
  auto intersection = v1 | views::set_intersection(v2);
  // [1, 5]
  
  auto difference = v1 | views::set_difference(v2);
  // [2, 4]
  
  auto symmetric_difference = v1 | views::set_symmetric_difference(v2);
  // [2, 4, 7]
}

P3742R0 C++ Standard Library Ready Issues to be moved in Sofia, Jun. 2025

6月に行われたSofia会議でWDに適用されたライブラリに対するIssue報告の一覧

P3745R0 Rebuttal to P1144R13

P1144R13に対して反論を行うスライド。

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

P1144R13はP2786R13に対して反対を表明するものですが、これはさらにP1144R13の主張に反論するものです。

スライドでは、関連する提案の提出タイミング等についての説明を行った後、P1144R13の主張(の一部)に対して一つ一つ反論を行っています。

P3746R0 LEWG Slides for P3637R0 Inherit std::meta::exception from std::exception

P3637R0の紹介スライド。

std::meta::exceptionstd::exceptionを継承しないことの原因が現在問題ではないことを説明し、そうすることの利点などを簡単に説明しています。

P3747R0 Call side return type deduction

式の戻り値の型を取得する構文の提案。

std::numeric_limitsstd::numbersなどの利用時には、数値型を指定して値を取得する必要があります。

double i = std::numeric_limits<double>::max();

float pi = std::numbers::pi_v<float>;

このような場合、初期化先の型とテンプレートパラメータの型が一致しており、冗長に指定しなければなりません。この時に、この初期化先の型を初期化している式で取得できると冗長な型の指定を省略することができます。

double i = std::numeric_limits<deduce>::max();  // deduceはdoubleが取得される

float pi = std::numbers::pi_v<deduce>;  // deduceはfloatが取得される

この提案は、このように初期化式の初期化先の型を取得することのできる言語機能を提案するものです。この提案ではこれを仮にdeduceとしています。

上記のような変数初期化であれば、変数の宣言の方をautoにしておけば型を2回指定する必要は無くなりますが、autoを使用できない所では2回指定しなければなりません。

// メンバ変数定義での利用の例
struct S {
  // これはできない
  auto i = std::numeric_limits<int>::max();

  // この提案
  int i = std::numeric_limits<deduce>::max();
};
// これは関数テンプレートになってしまう
void func(auto i = std::numeric_limits<int>::max());

// この提案
void func(int i = std::numeric_limits<deduce>::max());
// Double/Float を double/float の強エイリアスとする
Double func(Double);

Float f = 1;

func(f); // ng、変換できない
func(static_cast<deduce>(f)); // 自動キャスト

このような初期化先の型の取得は、テンプレート型変換演算子において現在でも限定的に利用することができます。しかし、std::numbersなどの既存のものを今からテンプレート型変換演算子を備えた型に変更することは後方互換性を保てない(ABI破壊になる)ために不可能です。このソリューションであれば、他の部分にほとんど影響を与えることなく導入できます。

P3748R0 Inspecting exception_ptr works should be constexpr.

std::exception_ptr_cast()constexpr指定する提案。

C++26で定数式での例外送出対応を受けて、<exception>ヘッダのexception_ptr関連のユーティリティはconstexpr指定されて定数式で利用可能なようになっています。

しかし、同じくC++26で追加されたstd::exception_ptr_cast()はそうなっていません。

この関数の実装にあたって定数式で実行できないような障害はないはずだとして、この提案ではこの関数にconstexprを追加することを提案しています。

P3749R0 Slides in response to P3655R2 - Concerns regarding std::zstring_view

P3655R2(zstring_view提案)への懸念を説明したスライド。

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

主に、パフォーマンスの検討が足りない事、std::string_viewを挟むと変換できなくなること、命名が分かりづらいこと、についての懸念を表明しています。

P3752R0 Core Language Working Group "ready" Issues for the June, 2025 meeting

2025年06月に行われたSofia会議でWDに適用されたコア言語に対するIssue報告の一覧。

P3753R0 Slides for P3740R0 - Last chance to fix std::nontype

P3753R1 Slides for P3740R1 - Last chance to fix std::nontype

P3740R1の紹介スライド。

P3740R1は少し上の方にあります。

P3740R1をはじめとするstd::nontypeの修正関連の提案(今月いくつかある)の背景となる、現時点で結果的にstd::nontypeに起きてしまっていることについて簡単に説明されています。

P3754R0 Slides for P3100R2 presentation to EWG

P3100R2の紹介スライド。

P3100R2に関しては以前の記事を参照

P3100R2の成り立ちや現状、コア言語UBホワイトペーパーにおける立ち位置を説明したうえで、P3100R2の内容を解説しています。

P3757R0 Remove value-type invocability requirement from indirect unary callable concepts

indirectly_unary_invocableコンセプトから、イテレータの値型によって呼び出し可能であることを要求する制約を削除する提案。

indirectly_unary_invocable<F, I>コンセプトは、I型のイテレータ経由で値を取得してその値によってFのオブジェクトが呼び出し可能であることを表現するコンセプトです。このコンセプト定義中では、イテレータの参照型(間接参照結果)だけでなく値型に対しても呼び出し可能であることを要求しています。

template<class F, class I>
  concept indirectly_unary_invocable =
    indirectly_readable<I> &&
    copy_constructible<F> &&
    invocable<F&, indirect-value-t<I>> && // 👈
    invocable<F&, iter_reference_t<I>> &&
    common_reference_with<
      invoke_result_t<F&, indirect-value-t<I>>, // 👈
      invoke_result_t<F&, iter_reference_t<I>>>;

indirect-value-t<I>は射影の結果を反映して値型を取得するコンセプトですが、射影を考慮しない場合はstd::iter_value_t<T>&が使用されます。イテレータの値型は多くの場合素のprvalueな型であるため、この制約によって実際には左辺値参照で呼び出さないような場合にコンパイルエラーになることがあります。

vector<string> v;
ranges::for_each(v | views::as_rvalue, [](string&& s) { /* */ }); // ng、右辺値で呼び出しできれば十分だけど・・・

また、この値型の呼び出し要求に伴うcommon_reference要件によって、イテレータの参照結果をムーブ不可能な型に射影するユースケースがサポートされなくなっています(common_reference_withコンセプト内で変換可能性がチェックされるため)。

歴史を紐解くと、indirectly_unary_invocableコンセプトのこのような要件は、その二項版コンセプトであるindirect_binary_predicateindirect_equivalence_relationにおける同様の制約から来ているようです。これらの二項版コンセプトでは確かに、それを使用するアルゴリズムにおいてイテレータの値型へのコピーとそれを用いた関数呼び出しが発生するためこれは必要な制約です(ranges::unique_copyなど)。

単項版のコンセプトは二項版からそれを継承してこのような制約をもっているようです。しかし、単項版コンセプトの使用場所を見てみるとそれはどうやら不要な制約であるようで、この提案ではこの制約を削除しようとしています。

indirectly_unary_invocableコンセプトを直接的/間接的に使用しているもの

  • ranges::for_each
  • ranges::all_of, any_of, none_of
  • ranges::find
  • ranges::count_if
  • ranges::copy_if, replace_if, replace_copy_if, remove_if, remove_copy_if
  • ranges::is_partitioned
  • ranges::partition, stable_partition, partition_copy, partition_point
  • ranges::filter_view, take_while_view, drop_while_view
  • std::projected<I, Proj>
  • std::projected_value_t<I, Proj>

このうち、最後のstd::projected_value_tだけは実際に値型のこのような制約が重要な意味を持っています。そのため、std::projected_value_tにはこの提案の変更の影響を受けないように修正しています。

この提案後のindirectly_unary_invocableコンセプトの定義

template<class F, class I>
  concept indirectly_unary_invocable =
    indirectly_readable<I> &&
    copy_constructible<F> &&
    invocable<F&, iter_reference_t<I>>;

また、これと同じ変更を同種のindirectly_regular_unary_invocableindirect_unary_predicateにも適用しています。

P3760R0 Presentation: constexpr 'Parallel' Algorithms

P2902の紹介スライド

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

P2902では並行アルゴリズムを定数式で使用可能にすることを提案していますが、このスライドではそのモチベーションについて詳細に説明しています。

P3763R0 Remove redundant reserve_hint members from view classes

標準のview型がsize()を提供している場合にreserve_hintを削除する提案。

P2846R6の採択によってreserve_hintCPOおよび、各標準view型(Rangeアダプタ実装クラス)にreserve_hintメンバ関数が定義されるようになっています。これは、size()が利用できない場合に範囲サイズの見積もりを伝達するもので、ranges::toや一部の標準コンテナのfrom_rangeコンストラクタなどで活用されています。

これについて定義をよく見ると、少しおかしなところがあります。

まず、sized_rangeコンセプトはapproximately_sized_rangeコンセプト(reserve_hintCPOが使用可能かどうかを表す)によってreserve_hintが利用可能かどうかをチェックしています。

template<class T>
concept sized_range =
  approximately_sized_range<T> && requires(T& t) { ranges::size(t); };

次に、各view型ではsized_rangeコンセプトとapproximately_sized_rangeコンセプトを使用して.size()/.reserve_hint()を定義しています。

template<range R>
struct some_view : public view_interface<some_view<R>> {
  […]
  constexpr auto size()               requires sized_range<R>
  constexpr auto size() const         requires sized_range<const R>
  constexpr auto reserve_hint()       requires approximately_sized_range<R>
  constexpr auto reserve_hint() const requires approximately_sized_range<const R>
  […]
};

そして、ranges::reserve_hintCPOは.size()が利用可能であれば.reserve_hint()よりもそちらを優先して使用します。

すなわち、各view型は.size()を持つ場合は必ず.reserve_hint()も持ちますが、範囲の正確なサイズ(.size())が利用可能な場合にサイズの見積もり(.reserve_hint())を提供しても実用的ではありません。標準ライブラリ内ではranges::reserve_hintCPOを介してサイズ見積もりを取得するため、これをサポートするユースケースはありません。

この提案では、reserve_hintは正確なサイズが得られない場合にのみ提供されるべきであり、.size()が利用可能な場合は不要であるため削除することを提案しています。

これを残しておくと、不要なインスタンス化のオーバーヘッド(コンパイル時間増大)にもつながります。

提案では、view型の.reserve_hint()メンバ関数の制約を修正して、approximate_sized_rangeが満たされてsized_rangeが満たされていない場合にのみ定義されるようにしています。

P3764R0 A utility function for propagating the most significant bit

整数の最上位ビットだけを残したビットマスク値を生成する関数std::msb_to_maskの提案。

ビット操作においては、符号付整数値の最上位ビット(すなわち符号ビット)をその他すべてのビットに伝播させたビットマスクを作成して使用することが多々あり、これを用いると整数が正か負かで分岐する必要のある処理において分岐無しの実装が可能になる場合があります。

// ナイーブなmod実装
int mod_naive(int x, int y) {
  int rem = x % y;
  if (rem < 0) rem += y;
  return rem;
}

// 分岐無しの実装
int mod_branchless(int x, int y) {
  int rem = x % y;
  rem += y & (rem >> (INT_WIDTH - 1)); // 👈
  return rem;
}

このような最適化はコンパイラが優秀であれば自動で適用してくれる場合もあるようですが、コードが複雑化したりするとその信頼性は低下します。また、このような形のビットマスクの生成は少し面倒であり、符号付/符号なし整数型の両方をサポートするような汎用実装では正しい記述が少し難しくなります。そして、このような関数の使用例はgithubにおいて多数見つけることができます。

この提案は、このような符号ビットからのビットマスク生成を行うビット演算関数std::msb_to_mask()<bit>に追加する提案です。

std::msb_to_mask()は次のような関数です

namespace std {
  template<class T>
  constexpr T msb_to_mask(T x) noexcept;
}

Tとしては符号付整数型と符号なし整数型の両方をサポートします。また、std::simdオーバーロードも追加することを提案しています。

呼び出すと、引数xの符号ビットをそれ以下の全てのビットに伝播させたビットマスクに対応する整数値を返します。

サンプルコード

int main() {
  std::println("{:032b}", std::bit_cast<std::uint32_t>(10));
  std::println("{:032b}", std::bit_cast<std::uint32_t>(msb_to_mask(10)));
  std::println("{:032b}", std::bit_cast<std::uint32_t>(-10));
  std::println("{:032b}", std::bit_cast<std::uint32_t>(msb_to_mask(-10)));
  std::println("{:032b}", 10u);
  std::println("{:032b}", msb_to_mask(10u));
}

出力例

00000000000000000000000000001010
00000000000000000000000000000000
11111111111111111111111111110110
11111111111111111111111111111111
00000000000000000000000000001010
00000000000000000000000000000000

処理の特性上、符号なし整数型に対しては常に0を返します(はずです)。

提案文書より、実装例。

namespace std {
  template<signed-or-unsigned-integer T>
    constexpr T msb_to_mask(T x) noexcept {
      using S = make_signed_t<T>;
      return static_cast<T>(static_cast<S>(x) >> numeric_limits<S>::digits);
    }
}

CPUによってはこれを行う命令を備えている場合があるようです。

P3765R0 Deprecate implicit conversion from bool to character types

boolから文字型への暗黙変換を非推奨にする提案。

例えば次のようなコードにおいて、boolからchar型への暗黙変換が発生していますが、これは警告などが発っせられることもなくコンパイルされ、実行できてしまいます。

std::string_view str = /* ... */;
if (str.ends_with('\n' || str.ends_with('\r'))) {
//                   ^^
  ...
}

このコードでは'\n'の後にかっこを入れ忘れたうえで別の場所にかっこを挿入してしまっている例です。このコードにおける'\n' || str.ends_with('\r')は全体として常にtrueになります。

まず、組み込み||オペランドboolでなければならないため、'\n'boolに変換されます。これはchar(0)ではないためbool値としては常にtrueになるため、str.ends_with('\r')の結果がなんであれtrue || str.ends_with('\r')trueになります。そして、その結果を用いたstr.ends_with(true)の呼び出しはbool型からcharへの暗黙変換によってstr.ends_with(char)オーバーロードを呼び出します。

しかし当然、このコードはほとんどの場合に意図通りに動作しないでしょう。

このような変換は意味のあるものとは思えず、上記例のようにバグの元にしかならないため、この提案ではこの変換を非推奨にしようとしています。

提案しているのは、bool型から文字型、すなわちchar, wchar_t, char8_t, char16_t, char32_t型への暗黙変換を非推奨とすることです。削除することは提案しておらず、bool型からsigned char/unsigned char型への暗黙変換は対象ではありません。

int x = /* ... */, z;
unsigned char y = x % 2 != 0; // ok、提案後も非推奨ではない
z += y;

また、明示的変換も非推奨ではありません。

bool b = /* ... */;

char d1 = b;                    // deprecated
char d2(b);                     // deprecated
char d3{b};                     // deprecated
char d4 = {b};                  // deprecated
char d5 = char{b};              // deprecated

char k1 = char(b);              // OK
char k2 = (char)b;              // OK
char k3 = static_cast<char>(b); // OK

'a' + b;                        // OK

char d6 = +b;                   // OK

この提案はEWGの議論にてより時間をかけることに合意を得られず、リジェクトされています。

P3769R0 Clarification of placement new deallocation

配置new式におけるdelete演算子の選択に関する仕様を明確化する提案。

P3492ではユーザー定義配置new演算子を呼び出すnew式において、オブジェクト初期化失敗時に呼び出されるdelete演算子に対して確保したサイズに関する情報を渡せるようにしようとするものです。この仕様の策定・検討時に、配置new式においてどのdelete演算子が選択されるかについての仕様が不十分であったことが発覚し、P3492R2ではその修正も含める形の文言が提案されていました。

しかし、それはP3492の主たる提案とは少し外れたところにあり、変更点を明確にするために別の提案に分離されることになり、これがその提案です。

ここでは、次の提案から関連する文言を分離してきています

  • P3492R2
  • P2719R5

また、この提案の内容はDRとすることを提案しています。

この提案において、グローバルスコープでnew式に対応するdelete演算子が探索される場合、次のように選択されます

  1. 候補が関数テンプレートである場合、テンプレート引数推論を用いて候補関数テンプレートの特殊化を生成する。推論に使用される引数は、最初の引数とそれに続く引数。
  2. 仮引数宣言が省略記号(...)で終了する関数は、(選択されている)new演算子の仮引数宣言が同様に省略記号で終了していない場合、以降の検討候補から外す。
  3. 仮引数宣言が省略記号(...)で終了しないものの、(選択されている)new演算子の仮引数宣言が省略記号で終了している場合、以降の検討候補から外す。
  4. 引数の数が(選択されている)new演算子の引数の数と等しくない場合、以降の検討候補から外す。
  5. 引数の変換の後の関数引数の型が、それぞれの最初の引数を除いて、(選択されている)new演算子の引数の型と一致しない場合、以降の検討候補から外す。
  6. (ここまでの選別の後で)関数が一つだけ残っている場合、それを選択して選択プロセスを終了する。
  7. それ以外の場合、関数を選択せずに選択プロセスは終了する。

この選択の過程(特に4~5)において、delete関数末尾にデフォルト引数を指定する形で追加されている引数でもそれによって省略されたりせずに、厳密にマッチングされます。

struct A {};
struct T {};

void* operator new(std::size_t s, A& al); // #1

template<int = 0>
void operator delete(void* p, A& al); // #2

A al;
new (al) T(); // ok、#1と#2のペアを使用する
template<int I>
struct A {};
struct T {};

void* operator new(std::size_t s, A& al); // #1

template<int I>
void operator delete(void* p, A<I>& al); // #2
void operator delete(void* p, A<0>& al); // #3

A<0> al;
new (al) T(); // ok、#1を使用(deleteは選択されない
struct A {};
struct T {};

void* operator new(std::size_t s, A& al);
void operator delete(void* p, A& al) = delete;

A al;
new (al) T(); // ng、削除された関数が選択された

P3771R0 constexpr mutex, locks, and condition variable

標準ライブラリのミューテックスや条件変数関連のものを定数式で使用可能にする提案。

この提案は以前にstd::atomic関連のものを定数式対応させた提案(P3309R3)の延長であり、それに続いてミューテックスとロック、そして条件変数を定数式で使用可能にしようとするものです。ただし、その動機は実行時とコンパイル時で同じコードを使用可能にすることにあり、定数式で並行処理を行おうとするものではありません。

if constevalによってコンパイル時処理のための分岐は可能になっていますが、これはコードに余分な分岐と重複をもたらし、記述しやすさと可読性を低下させます。標準ライブラリのエンティティについてはconstexpr対応させておくことで、利用側のコードは何もせずに定数式と実行時の両方で動作するようになります。

この提案でconstexpr指定を追加しようとしているのは

  • std::mutex
  • std::recursive_mutex
  • std::timed_mutex
  • std::recursive_timed_mutex
  • std::shared_mutex
  • std::shared_timed_mutex
  • std::lock_guard
  • std::scoped_lock
  • std::unique_lock
  • std::shared_lock
  • std::call_once
  • std::condition_variable
  • std::condition_variable_any
  • std::notify_all_at_thread_exit

一部のミューテックス型が備えている、.try_lock_until()などの時間指定でロック取得を試みる関数は、定数式においては即処理を返すようになります。この時、対象のミューテックスがすでにロックされていたら即時に失敗するようにしています。これは、定数式においては時間計測の方法がないことと、実行がシングルスレッドであることによる動作の選択です。

また、いずれにおいても.native_handle()constexpr指定されません。

定数式においては未定義動作は禁止(コンパイルエラー)であり、デッドロックの発生は未定義動作となるためコンパイルエラーとなります(シングルスレッド動作時のそれは同じミューテックスに対して2度ロックを取得することであるため、検出は可能・・・?)。

P3772R0 std::simd overloads for bit permutations

<bit>のビット置換系操作APIstd::simdオーバーロードを追加する提案。

P3104R3では、C++29向けにビット置換系のビット演算に対応する関数を追加しようとしています。一方、C++26で追加されるstd::simdに対してはP2933R4で<bit>にすでにあるビット操作関数についてstd::simd対応がなされています。

この提案は、P3104R3で追加されるビット操作関数についてもstd::simd対応しておこうとするものです。

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

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

P3740R1(少し上)のLEWGでのレビューにおいては、std::nontypestd::constant_argにリネームするオプションが最も支持を集めましたが、LWGの時間不足もありC++26に採択しないという決定がなされたようです。

この提案は、そのオプションの方向性において、std::nontypestd::fnにリネームすることを提案しています。

std::constant_argではなくstd::fnとするのは、同時にC++29以降の拡張として提案しているメンバの追加によって、std::fnが関数ポインタを型システムに持ち上げる役割を担うようなユーティリティになることを見越しての事です。

この提案では、C++29以降の拡張としてstd::fnに関数呼び出し演算子と関数ポインタへの変換演算子を追加することを提案しています。これによって、std::fnは関数ポインタに対してラムダ式std::function_refなどと同等の関数ラッパとして働くようになります。

std::fn<f>std::nontype<f>)は関数fの関数ポインタをNTTP値として保持するため、関数ポインタ保持のためにストレージ領域を使用しません。さらに、std::fn<f>は異なるfに対して異なるインスタンス化をもたらします。これによって、標準アルゴリズムにおいて関数ポインタを使用した時でもインライン展開しやすくなります。

void f(int);
std::ranges::for_each(r, f);                           // インライン展開を妨げるため推奨されない
std::ranges::for_each(r, [](int x) { return f(x); });  // 正しいが面倒
std::ranges::for_each(r, std::fn<f>);                  // 正しく、簡潔

また、関数ポインタへの変換演算子はある程度の自由度を持たせておくことで、引数型の変換を考慮した関数ポインタの変換機能を提供できます。

bool is_prime(long long x);

bool(*p)(int) = std::fn<&is_prime>;

このように、std::fn<f>fのアドレス値を型名に取り込むことによって関数ポインタを型システムに持ち上げる役割を担うようになるため、より短く適切な命名としてstd::constant_argよりもstd::fnを選択しています。

提案文書より、std::fnの実装例

template<auto f>
struct fn_t
{
  using type = decltype(f);

  static constexpr bool is_function_ptr = std::is_function_v<std::remove_pointer_t<type>>;

  template<bool Noex, typename Ret, typename... Args>
  using func_type = Ret(*)(Args...) noexcept(Noex);

  constexpr operator type() const noexcept
    requires is_function_ptr
  { 
    return f;
  }

  template<bool Noex, typename Ret, typename... Args>
    requires (Noex ? std::is_nothrow_invocable_v<Ret, type const&, Args...> 
                   : std::is_invocable_r_v<Ret, type const&, Args...>)
  constexpr operator func_type<Noex, Ret, Args...>() const
  {
    if constexpr (is_function_ptr && std::is_convertible_v<type, func_type<Noex, Ret, Args...>>)
      return f;
    else
      return [](Args... args) noexcept(Noex) -> Ret {
        return std::invoke(f, std::forward<Args>(args)...);
      };
  }

  template<typename... Args>
    requires (!is_function_ptr)
  static constexpr std::invoke_result_t<type const&, Args...> operator()(Args&&... args)
    noexcept(std::is_nothrow_invocable_v<type const&, Args...>)
  { 
    return std::invoke(f, std::forward<Args>(args)...);
  }
};

template<auto f>
constexpr fn_t<f> fn;

この提案のC++26部分(リネームのみ)に関してLEWGの議論の結果、std::constant_argという名前にリネームすることに合意が取れたようです。

P3778R0 Fixing type_order template definition

std::type_orderによる比較結果の表現方法を修正する提案。

P2830R10では2つの型を実装定義の全順序の上で比較するためのメタ関数std::type_orderが提案され、これはC++26に採択されています。std::type_orderstd::type_order_v<T, U>のように使用して、2つの型T, Uの実装定義の<比較の結果をstd::strong_orderingの値で得ることができます。

このstd::type_orderの実装は次のようになっています

template <class T, class U>
struct type_order : integral_constant<strong_ordering, see below> {};

template <class T, class U>
constexpr strong_ordering type_order_v = type_order<T, U>::value;

このようにintegral_constant(特にstd::bool_constant)を使用してメタ関数の実装をある程度簡略化するのは他のメタ関数でもよくやられていることです。しかし、std::strong_orderingがNTTPとして使用できないため、この実装は不可能でした。

NTTPとして使用可能な型は構造的な型(structural type)という型のカテゴリで定義されており、クラス型の場合は集成体型のようにすべてのメンバ変数がpublicである必要があります。しかし、std::strong_orderingは通常プライベートメンバを持つ形で実装されるため、NTTPとして使用できません。

この提案は、std::type_orderstd::integral_constantを使わないように修正することでこの問題を解決しようとするものです。

std::type_orderの実装が先ほどの例のようにstd::integral_constantを使用する必要があるのは、Cpp17BinaryTypeTraitという要件に従うように指定されているからです。この要件の意図するところは次のようなものの様です

  1. std::integral_constant<T, v>への暗黙変換を行うことで、メタ関数の引数型を隠蔽することができる。
    • 意図的に使用すると、テンプレートのインスタンス化数を減少させられる
  2. ::typeメンバは1を明示的に実行するのに使用できる
  3. メタ関数の結果として::valueメンバを提供する
  4. value_typeメンバによって::valueの型を取得できる
  5. 利便性向上のための、constexpr暗黙変換演算子::valueへ変換)
  6. constexpr operator()は関数呼び出しを期待するコンテキストから値を取得するのに役立つ

これらの性質を実現するのにstd::integral_constantを使用するのは最も簡単なため、現在の定義はそうしています。この提案では、これらの性質を維持し他のメタ関数とほぼ同じ動作をさせながらもstd::integral_constantを使用しないようにするために、std::type_orderの構造体を独自かつ具体的に定義するようにしています。

変更後の定義は次のようになります

template<class T, class U>
struct type_order {
  static constexpr strong_ordering value = TYPE-ORDER(T, U);

  using value_type = strong_ordering;

  constexpr operator value_type() const noexcept { return value; }
  constexpr value_type operator()() const noexcept { return value; }
};

そして、std::type_orderCpp17BinaryTypeTraitへの適合の規定を削除しています。これによって、上記性質の1と2以外のものは維持されたままで、std::strong_orderingを比較結果に使用しつつ実装可能になります。

この提案は、2025年11月の全体会議を通過し、C++26に採択されています。

P3780R0 Detecting bitwise trivially relocatable types

トリビアルリロケーション可能な型が、memcpyによってトリビアルリロケーション可能であるかを調べる型特性を追加する提案。

P2786では、オブジェクトの再配置(リロケーション)という操作をライブラリ実装で使用可能にするための言語の調整と最低限のライブラリ機能が提供されます。リロケーション操作は通常ムーブ+デストラクタ呼び出しの複合操作となりますが、一部の条件を満たした方はもっと単純にmemcpyによるビットコピーによってリロケーションを行うことができます。

このmemcpyによるリロケーションが可能な型の事をトリビアルリロケーション可能(trivially relocatable)な型と呼び、P2786は専らこれを正式にC++コード上で利用可能にすることを目的としています。

トリビアルリロケーション可能な型とは、スカラ型やポインタ型などのプリミティブ型は当然含まれますが、普通のクラス型でもトリビアルリロケーション可能となる場合があります。例えば、通常の実装のstd::vectorトリビアルリロケーション可能になります。

P2786によるトリビアルリロケーション可能の定義では、多態的な型(polymorphic type)もトリビアルリロケーション可能であるとされます。これは、多態的な型は隠れた非静的メンバ変数としてポインタ(仮想関数テーブルへのポインタ)を持っているとみなすことができ、ポインタ型はトリビアルリロケーション可能であるため、多態的な型はその継承構造に関わらずトリビアルリロケーション可能となります。

しかし、一部のアーキテクチャやABIにおいてはポインタの署名が行われる場合があり、署名による認証に失敗するとポインタアクセスに失敗するようになります。例えばARM64eではPointer Authentication Code(PAC)と呼ばれる拡張命令を備えています。そして、仮想関数テーブルへのポインタがこの署名の対象になる場合があります。

このポインタの署名にはポインタの参照先アドレスとポインタ自体のアドレスが少なくとも使用されます。これにより、ポインタ署名をサポートする(+仮想関数テーブルへのポインタの認証を行う)ような環境においては、多態的な型を単純なmemcpyによってリロケーションしてしまうとうまく動作しなくなります(仮想関数テーブルへのポインタのアドレスが変化することでポインタ認証に失敗してしまう)。

P2786ではこれが考慮されており、std::trivially_relocate()関数を用いてトリビアルリロケーションを行うことで、この関数内でポインタ署名の再署名を行えるようにしています。

結局、P2786のトリビアルリロケーション可能な型の定義においては、多態的な型もトリビアルリロケーション可能となっています。しかし、それは必ずしもビットコピーによってトリビアルリロケーション可能であることを意味していません。このことは、多態的な型をバイト列として内部的に保持しうるようなラッパ型の実装時に問題となります。

例えば次のようなラッパ型を考えます

class Wrapper {
  // オブジェクトか、ヒープへのポインタを格納するバイト配列
  alignas(A) std::byte storage[N];
  /* ... other data ... */

public:
  Wrapper();

  // デストラクタとムーブコンストラクタがユーザー定義される
  ~Wrapper();
  Wrapper(Wrapper &&);
  /* other special member functions, rest of the API, etc. */
};

これをP2786のトリビアルリロケーションの枠組みにアダプトさせると次のようになります

class Wrapper trivially_relocatable_if_eligible {
  // オブジェクトか、ヒープへのポインタを格納するバイト配列
  alignas(A) std::byte storage[N];
  /* ... other data ... */

public:
  Wrapper();
  
  // デストラクタとムーブコンストラクタがユーザー定義される
  ~Wrapper();
  Wrapper(Wrapper &&);
  /* other special member functions, rest of the API, etc. */
};

このような型の具体例としてはstd::anyがあり、optionalinplace_vectorの実装の一つとして一般的なものです。

storage以外のメンバは通常整数やポインタ等のプリミティブ型しか必要ないため、この型はトリビアルリロケーション可能となります。ただしそのためには、storageに保存する型もまたトリビアルリロケーション可能でなければならず、そうではない場合は構築を禁止するかヒープに配置してそのポインタを(storageに)保存するようにする必要があります。

そのように実装されているとして、このWrapperクラスオブジェクトをリロケーション(std::trivially_relocate())する際に単純なmemcpyによって実行しても大丈夫でしょうか?特に多態的な型(前述のようにこれはトリビアルリロケーション可能)を保持している(storageに格納されている)場合にmemcpyによるリロケーションは安全でしょうか?

前述のように、ポインタ認証を行う環境においては、多態的な型を保持するWrapperクラスオブジェクトをmemcpyによってリロケーションするとポインタ認証に失敗するポインタが生成されてしまうため安全ではありません。std::trivially_relocate()によってリロケーションを行うときでも、std::trivially_relocate()Wrapperクラスの実装についての知識を持たないため、単純なmemcpyでリロケーションしていいのかポインタ再認証が必要なのか(そしてそのポインタはどこにあるのか)は分かりません。

そのため、このトリビアルリロケーション可能なWrapper型に直接格納できる型の要件はトリビアルリロケーション可能というだけではなく、memcpyによってリロケーション可能であるという要件も必要になります。これはP2786でも提供されていません。この提案は、これを検出する型特性を提案するものです。

ポインタ認証をサポートするかはプラットフォームによって異なり、また型が多態的であるかどうかということはこの問題の本質ではないため、std::is_polymorphicではこの問題の解決には適さないため、専用の型特性が必要となります。

この提案では、この型特性をstd::is_bitwise_trivially_relocatableとして提案しています。

Wrapperクラスでは、コンストラクタや代入演算子などでこの型特性を使って格納しようとしている型を内部バッファ(storage)に配置すべきかヒープに配置してポインタを保持すべきかを決定するようにすることで、プラットフォームによらず安全なトリビアルリロケーション対応を達成することができます。

また、このmemcpyによってリロケーション可能であるという要件は、動作するUBの上ですでにリロケーションを活用している既存のライブラリ実装が想定している定義でもあります。既存のライブラリの要件と合致する型特性を追加しておくことでC++26リロケーションへの移行を促しやすくなる効果もあります。

P3781R0 is_*_type should imply is_type

リフレクション(std::meta::info)が何らかのカテゴリの型を反映しているかを取得する関数群について、型ではないリフレクションの入力がエラーにならないようにする提案。

リフレクションのユースケースとしてかなり一般的になると予想されることの一つは何らかのバインディングを作成することだと予想されます。その際、対象が関数なのかクラスなのかによって処理が変わることがあると予想され、その場合はそれを判定して分岐することになります。例えば次のような単純な例が考えられます

// 名前空間fooのリフレクションを取得
constexpr auto namespace_info = ^^foo;

// foo名前空間以下のもののリフレクションを取得しイテレーションする
for (int i = 0; auto element : std::meta::members_of(namespace_info, std::meta::access_context::current()))
{
  // elementがクラスかを判定
  if (std::meta::is_class_type(element))
  {
    // クラスに対する処理をここで行う
    classes[i++] = std::meta::identifier_of(element);
  }
}

どのようなバインディングを作成するかは置いておいて、何かしらの対象(ここでは名前空間)のエンティティの処理は例えばこのようになるでしょう。しかしこのコードは実際には期待通りに動作しません。なぜなら、std::meta::is_class_type(element)elementが型のリフレクションではない場合にfalseを返すのではなくコンパイルエラーになるためです。この例では名前空間のメンバについてイテレーションしているので、関数や名前空間が出てきたところでエラーになります。

正しくは、std::meta::is_typeelementが型のリフレクションであるかどうかを先に判定する必要があります。

  // elementがクラスかを判定
  if (std::meta::is_type(element) && std::meta::is_class_type(element))

とはいえこれは直感的ではなく冗長です。

std::meta::is_*_typeという関数は多数ありいずれも同様の問題があります。is_*_typeという命名からは、それが型かどうかをbool値で返すものであるという妥当な推測が立ち、型ではないものについてクエリしたときにfalseではなくエラーを返すのは直感的ではありません。

このため、この提案ではstd::meta::is_*_type系の関数においてstd::meta::is_typeによる判定をまず行うようにすることを提案しています。これにより、上記の例ではstd::meta::is_class_type(element)だけで意図通りに動作するようになります。

ただし、std::meta::is_*_type系の関数のうち、2項で型以外の入力が意味を持たないものについては除外しています。除外する関数は次のものです

// associated with [meta.unary.prop], type properties
consteval bool is_assignable_type(info type_dst, info type_src);
consteval bool is_trivially_assignable_type(info type_dst, info type_src);
consteval bool is_nothrow_assignable_type(info type_dst, info type_src);

consteval bool is_swappable_with_type(info type_dst, info type_src);
consteval bool is_nothrow_swappable_with_type(info type_dst, info type_src);

// associated with [meta.rel], type relations
consteval bool is_same_type(info type1, info type2);
consteval bool is_base_of_type(info type_base, info type_derived);
consteval bool is_virtual_base_of_type(info type_base, info type_derived);
consteval bool is_convertible_type(info type_src, info type_dst);
consteval bool is_nothrow_convertible_type(info type_src, info type_dst);
consteval bool is_layout_compatible_type(info type1, info type2);
consteval bool is_pointer_interconvertible_base_of_type(info type_base, info type_derived);

template <reflection_range R = initializer_list<info>>
consteval bool is_invocable_r_type(info type_result, info type, R&& type_args);

template <reflection_range R = initializer_list<info>>
consteval bool is_nothrow_invocable_r_type(info type_result, info type, R&& type_args);

これらの関数ではどちらかの引数に型以外のものを入れた時にfalseを返すと誤解を招く可能性があります。例えば、is_same_type(r1, r2)は一方もしくは両方の引数が型を反映したものでない場合にfalseを返すと型として一致していなかったのか引数が型ではなかったのかわからなくなります。

P3784R0 range-if

範囲if文の提案。

範囲が空であるかによって処理を分岐したい場合ということは良くあります。単純にはranges::emptyを使用して分岐すればよいのですが、真にジェネリックに記述しようとすると、emptyが使用できない場合があり得るためイテレータを使用しなければなりません。

// パイプラインを使用するなどして生成されている何か範囲
auto && r = a-view-pipeline; 

if(!r.empty()) { // ❌ すべてのビュー型がempty()を提供するわけではない
  for(auto & x : r) { 
    … 
  } 
} else { 
  // 範囲が空の場合の処理
}
auto && r = a-view-pipeline;

// 😬 明示的にイテレータを使用する
if(auto it{r.begin()}; it != r.end()) { 
  for(; it != r.end(); ++it) { // 冗長なチェック
    auto & x{*it}; 
    … 
  } 
} else { 
  // 範囲が空の場合の処理
}

この用途に範囲forを使用できないのは、else節に相当するものが無いためです。

範囲for + フラグという方法もありますが、フラグの扱いそのものやそのスコープについてなどの問題がありベストな解決策とは言えません。

// 😬 空かどうかのフラグ
auto empty{true}; 
for(auto & x : a-view-pipeline) { 
  empty = false; 
  … 
} 

if(empty) { 
  // 範囲が空の場合の処理
}

この提案は、範囲forに対応する範囲if文によってこのような場合の記述の問題を言語機能で解決しようとするものです。

提案している構文は通常のif文に対して、条件を記述するところに範囲forのように記述する構文です

if (auto & x : a-view-pipeline) { 
  // 範囲のイテレーション処理
  // ここはループする
} else { 
  // 範囲が空の場合の処理
  // ここは一回で抜ける
}

これは範囲forにかなり近いもので、それと同じように展開されます

if constexpr(opt)(init-statementopt for-range-declaration : for-range-initializer) 
statement1 else statement2

の様な文は

{
  init-statement(opt); 
  auto && range = for-range-initializer; 
  auto begin = begin-expr; 
  auto end = end-expr; 
  if constexpr(begin != end) 
    do { 
      for-range-declaration = *begin; 
      statement1 
    } while(((void)++begin), begin != end); 
  else 
    do { statement2 } while(false); 
}

のように展開されます。

この範囲ifではループ部とelse節の両方でbreakcontinueを使用可能にすることを意図しており、そのためにelse側(statement2)もダミーループで囲われます。

P3785R0 Library Wording Changes for Defaulted Postfix Increment and Decrement Operators

P3668の後置インクリメント/デクリメント演算子default定義を利用して標準ライブラリの規定を変更をする提案。

P3668では後置インクリメント/デクリメント演算子が対応する前置演算子を用いて典型的に記述できることを利用して、そのような実装をデフォルトとするdefault定義できるようにしようとしています。

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

この提案は、標準ライブラリ中で後置インクリメント/デクリメント演算子を定義している部分でこのdefault指定を使用するようにすることで、標準の規定を圧縮しようとするものです。

この提案による変更は既存の動作を変更したりするものではなく、標準の文言を簡素化することにあります。そのため、この変更は編集上のものとしてLWGのレビューにかけることを提案しています。

変更の候補は全部で53個あり、表記の揺れはあるもののデフォルト実装と一致する規定を持つものと、デフォルト実装と一致しているもののコンパイル時の分岐を伴う物や事前条件や制約を持つものの大きく2種類に分けて、その文言とともにリストアップしています。

P3787R0 Adjoints to "Enabling list-initialization for algorithms": uninitialized_fill

P2248R8での変更をuninitialized_fillにも適用する提案。

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

P2248R8では値を取るタイプのアルゴリズムにおいて、イテレータから取得した値型を利用することで{}の指定ができるようになっています。しかし、uninitialized_fillはそこから漏れていたようでその変更が適用可能であるのに適用されていないため、特にfillアルゴリズムの仕様と一致するようにuninitialized_fillも変更しようとする提案です。

int main() {
  std::array<int, 10> arr;  // 未初期化配列

  std::ranges::uninitialized_fill(arr, {}); // この提案後ok
}

P3788R0 Fixing std::complex binary operators

std::complexの二項演算において暗黙変換が考慮されないことを修正する提案。

std::complex<T>Tに暗黙変換可能な数値型から構築することができます。一方で、std::complex<T>を用いた二項演算においては数値として使用可能なのはT型そのもののみで、Tに暗黙変換可能な型を使用できません。

long double ld = 2.5;
std::complex<float> c = 3.14;           // OK!!!

auto res1 = c * 3.14f;                  // OK
auto res2 = c * 3.14;                   // DOES NOT COMPILE!!!
auto res3 = c * ld;                     // DOES NOT COMPILE!!!
auto res4 = c * static_cast<float>(ld); // OK but verbose

if (c == 3.14f) {                       // OK
  // ...
}
if (c == 3.14) {                        // DOES NOT COMPILE!!!
  // ...
}

これはstd::complex<T>の二項演算の演算子オーバーロードの定義のされ方に問題があります。例えば、二項*演算子オーバーロードは次のように定義されています。

template<class T>
constexpr complex<T> operator*(const complex<T>&, const complex<T>&);

template<class T>
constexpr complex<T> operator*(const complex<T>&, const T&);

template<class T>
constexpr complex<T> operator*(const T&, const complex<T>&);

この演算子(およびほかの二項演算子)は非メンバの非friend関数として定義されています。下二つの演算子定義において、std::complexではない値を受け取る引数の引数型がstd::complex<T>Tを直接使用してしまっていることに問題があります。このTはテンプレート引数推論の対象であり、両方のオペランドから推論した型が一致しなければなりません。その結果、Tに対してはほとんどすべての変換は行われず、std::complex<T>に対して丁度T型の値しか渡すことができません。例えば、std::complex<float>doubleを受け取れる*は存在しません。

また、両方のオペランドstd::complexである場合でも、その数値型が異なるとやはり利用できる演算子がなくなります(std::complex<double>std::complex<float>など)。

これと同じことが他の二項演算子においても起きているため、std::complexを使用する場合の使用感を損ねています。特に、std::complexは数式をコードに起こす場合に使用されることが多く、そのようなコードを動作させるためにキャストが多く必要になると元の数式と乖離することで可読性が低下します。

この提案は、これを修正してstd::complex<T>の二項演算でTに暗黙変換可能な型の値を使用できるようにしようとするものです。

後方互換性を重視して、この提案ではTを直接取っていたところをstd::complex<T>::value_typeを使用するように変更することを提案しています。

template<class T>
constexpr complex<T> operator*(const complex<T>&, const complex<T>&);

template<class T>
constexpr complex<T> operator*(const complex<T>&, const typename complex<T>::value_type&);

template<class T>
constexpr complex<T> operator*(const typename complex<T>::value_type&, const complex<T>&);

このようにすることで、下二つのオーバーロードの非std::complex引数はテンプレートパラメータの推論に寄与しなくなり、std::complex<T>引数からTが決定された後でTに対する変換が効くようになります。

これと同じことを、他の二項演算(+ - * / ==)に対しても適用します。

この変更は既存の動作しているコードに対しては影響がなく、オーバーロード解決結果も変更されません。動作していなかったコードが動作するようになります。

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

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

現在のC++のポインタの意味論においては、参照先のオブジェクトの寿命が尽きた後のポインタ値は不定となり、その使用(ポインタ値の読み出し、保存、キャスト、比較)は実質的に許可されていません。一方で、既に広く使用されている並行アルゴリズムの中にはそのような不定なポインタ値が使用できることに依存している場合があります。

この問題はPointer lifetime-end zapと呼ばれており、先行する提案によって否定的に(不定なポインタの使用を禁止する形で)解決されようとしています。その場合、現在存在しているそのような操作に依存しているアルゴリズムは未定義動作になってしまいます。

それを防止するために、この提案では これは、整数値アドレスを格納する変数の様な単純なポインタの意味論(Bag of bits)をエミュレートするライブラリ機能です。

ここで提案しているのは、launder_bag_of_bits_ptr()という関数とbag_of_bits_ptr<T>というクラステンプレートです。

launder_bag_of_bits_ptr()はポインタを受け取り、そのポインタに対応する将来のポインタ値を返す関数です。参照先の寿命が尽きているポインタはそのままだと不定値を取りますが、この関数を通すことで同じアドレスに再度配置される別の(将来の)オブジェクトのアドレスを指す有効なポインタが得られます。名前の通り、この関数はポインタのロンダリングを行います。

bag_of_bits_ptr<T>は参照先の寿命が尽きた後にも無効化しないポインタ型をエミュレートする型です。このクラステンプレートはTのポインタとして使用することができ、このクラスオブジェクトとして使用する限りポインタの参照先の寿命が尽きた後でもポインタ値が有効であり続けます。また、このクラスによるポインタはポインタのprovenanceを切ることもできます。

これらのユーティリティは、zapおよびprovenanceによって問題を受けるコードを保護するためのテクニックである、ポインタ値をreinterpret_castによって整数値に落としてから戻す(戻す際にprovenance再計算とその時点で有効なポインタの取得が行われる)という操作をラップするユーティリティです。現在不定なポインタの使用に依存しているコードでは、生ポインタを使用する代わりにこれらのユーティリティを介してポインタを使用するようにすることで、将来的にPointer lifetime-end zapが否定的に解決されprovenanceが導入された場合でも、従来の動作を維持することができます。

この提案はP2414で提案されていたライブラリ機能を単離したもので、bag_of_bits_ptrは以前はusable_ptrと呼ばれていました。

P3791R0 constexpr deterministic random

決定論的なアルゴリズムによる乱数生成器と、乱数生成器を使用するアルゴリズムconstexpr指定する提案。

この提案では決定論的なアルゴリズムによる乱数生成器、すなわち疑似乱数生成器の全てに対してconstexpr指定しようとしています。それに加えて、shufflesampleアルゴリズムconstexpr指定しようとしています。

constexpr sample_to_sketch(std::span<const T> items, sketch<T> & sk) noexcept {
    auto engine = std::mt19937_64{/* default seed is fine*/}; // この提案後ok
    std::ranges::sample(items, std::insert_iterator(sk), sk.capacity(), engine);  // この提案後ok
}

ただし、std::random_deviceなど非決定論的な乱数生成を伴うものについては対象にしていません。

この動機については他のconstexpr対応と同様に、実行時とのコード共通化や定数式でのテストを可能にすることでUB検出機能付きでテストできるようにすることなどを挙げています。

P3792R0 Why constant_wrapper is not a usable replacement for nontype

std::nontypestd::constant_wrapperで置き換えられない理由を説明する文書。

P3740R1(上の方に詳細あり)ではstd::nontypeをリネームするかstd::constant_wrapperで置き換えるかの2つのオプションを提示しており、LEWGの投票ではリネームすることに合意がありました。std::constant_wrapperはNTTPをより汎用的に取り扱うものであるため、std::nontypeを完全に置き換えられると考えるのは自然なことです。この投票結果においてはそのことがメンバーに共有された上でのものであり、この文書はそれを説明するものです。

std::constant_wrapper<V>はNTTP値Vをラップして通常のオブジェクトとして扱えるようにしたもので、std::integral_constantを汎用化したものです。std::constant_wrapperの特徴はほぼすべての演算子オーバーロードを提供することでVで可能な演算をstd::constant_wrapper上で実行できるようにしていることで、std::constant_wrapperという型を通してコンパイル時定数の計算を型システム上で簡単に行うことができます。

この演算子オーバーロードの中には関数呼び出し演算子も含まれており、次のように定義されています。

template<constexpr-param T, constexpr-param... Args>
constexpr auto operator()(this T, Args...) noexcept
  requires requires(Args...) { constant_wrapper<T::value(Args::value...)>(); }
{
  return constant_wrapper<T::value(Args::value...)>{};
}

std::cw<V>(args...)std::cw<V>std::constant_wrapper<V>の略記)の様な呼び出しは、V(args.value...)の結果を再びstd::constant_wrapperにラップしたstd::cw<V(args.value...)>を返します(std::constant_wrapper<V>::valueVにアクセスできる)。

std::cw<V>(args...)において、この関数呼び出し演算子が使用可能であるためには、V(args.value...)の呼び出しが定数式で実行可能であり、argsはすべてstd::constant_wrapper型の値(もしくは互換のある型の値)である必要があり、戻り値型がNTTPとして利用可能(戻り値型が構造的な型)である必要があります。そうでない場合、この関数呼び出し演算子は使用できません。

これによって、std::constant_wrapper自体が呼び出し可能になる場合があり得ます。それでも、std::nontypeの代わりにstd::constant_wrapperを使用することは可能であり、単にstd::constant_wrapperを受け取るコンストラクタにおいてstd::constant_wrapper<V>::valueを使用するようにするだけです。

では何が問題なのかというと、現在std::nontypeを使用しているところがstd::function_refのみであるため、std::function_refとそれ以外の関数ラッパ(std::move_only_functionなど)との間でstd::cw<f>を渡した場合の挙動が異なってしまうことです。std::function_refでは::valueからf(関数ポインタ)を取り出してそれを保持するのに対して、他の関数ラッパではstd::cw<f>そのものを呼び出し可能オブジェクトとして保持します。前述のように、std::cw<f>(args...)args...の値は静的メンバ変数valueを取り出せなければならないためいつも起こるわけではありませんが、意図しないでこの不一致が発生する可能性が生まれてしまいます。

例えば次のような呼び出し可能な型があり

static constexpr struct foo_t
{
  constexpr auto operator()(auto &&...args) const -> int
    requires(std::integral<std::remove_cvref_t<decltype(args)>> && ...)
  {
    return (0 + ... + args);
  }

  constexpr auto operator()(auto &&...args) const -> int
    requires(std::integral<
                 decltype(std::remove_cvref_t<decltype(args)>::value)> &&
             ...)
  {
    return sizeof...(args);
  }

} foo = {};

次のような型の値があったとして

static constexpr struct baz_t final
{
    static constexpr int value = 42;
} baz = {};

次のコードは動作します

auto main() -> int {
  move_only_function<int(baz_t)> fn(cw<foo>);
  assert(fn(baz) == 42);  // ✅
}

cw<foo>は構築可能であり、cw<foo>(baz_t)の呼び出しは有効でcw<foo(baz_t::value)>を返します。このため、move_only_function<int(baz_t)>への格納も行うことができ、fn(baz)の呼び出しも行うことができて、アサートはパスします。

しかし、このmove_only_functionfunction_refに変えると動作が変化します(std::nontypestd::constant_wrapperで置き換えた場合)

auto main() -> int
{
  function_ref<int(baz_t)> fn(cw<foo>);
  assert(fn(baz) == 42); // ❌ fn(baz) == 1
}

function_refにおいてはcw<foo>からの構築はfooを取り出しこれをそのまま関数呼び出し対象として利用する形になります。それによって、foo_tで定義された関数呼び出し演算子の2つ目の方が選択され(baz_t::valueがアンラップされずにそのまま渡される)るようになります。

これを防止するにはstd::nontypeを維持するか、std::constant_wrapperを取りstd::function_refと同等に動作するコンストラクタを他の関数ラッパにも追加するかのどちらかをする必要があり、前者が選ばれたのがP3740R1のLEWGでの投票の結果です。後者はP2511で提案されていたものの否決されていたこともあり、C++26ギリギリの状況では選択しづらかったものと思われます。

P3793R0 Better shifting

整数のビットシフトを行うライブラリ関数の提案。

C++のシフト演算子<< >>)は基本的にCから受け継いだものそのままで、いくつかの落とし穴が知られています

  1. 演算子の優先順位が+ -二項演算子)よりも低い
    • シフト演算が実質乗除算として機能することを考えると直感的ではない
  2. シフト量がシフト対象のビット幅以上、あるいは負の値の場合、未定義動作となる

1の問題はGCCの最適化がこのことに依存しているため変更することが難しく、2の問題は動作を定義するよりもEBにすることを好む向きがあるため議論の余地があります。この提案では特に2の問題の解決に焦点が置かれており、シフト対象の幅と同じ値を指定した場合の未定義動作を回避するために気を使わなければならない不便さなどを動機として挙げています。

この提案では、これらの問題を解決したシフト演算を提供するために、シフト演算を行うライブラリ関数を追加しようとしています。提案しているのはstd::shl()std::shr()という2つの関数です。

// <bit>に追加
namespace std {
  template<class T>
  constexpr T shl(T x, int s) noexcept;
  
  template<class T>
  constexpr T shr(T x, int s) noexcept;
}

shl(x, s)xsビット左シフトし、shr(x, s)xsビット右シフトします。ここでのシフトは算術シフトを行うため、論理シフトを行う場合はx引数を符号なし整数型にキャストする必要があります。

そして、シフト量が大きい場合のシフトについては未定義動作とせず、シフト対象のビット全てが外に押し出されることになり、論理シフト(符号なし整数型)の場合は0になり、算術シフト(符号付整数型)の場合は負の値であれば-1、そうでなければ0になります。

一方、シフト量が負の場合のシフトについては実装定義の結果を伴うEBとしています。

また、他のビット操作関数と同様にstd::simdstd::simd::vec)に対するオーバロードも用意しています。

P3794R0 An idea or two on renaming the nontype tag

std::nontypeのリネーム名の別案の提案。

C++26でNTTPという言葉が標準で使用されなくなったのを受けて、std::nontypeの名前を変更する必要があることが認識されています。そのリネーム候補としてここでは、swiftのWitness Tableと呼ばれる概念からとったstd::witnessを代わりの名前として提案しています。

LEWGにおける検討では少なくともstd::witnessは支持を得られなかったようです。

P3795R0 Miscellaneous Reflection Cleanup

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

2025年6月に行われた全体会議では、リフレクション関連の提案がほぼ同時にいくつもWDに採択されました。これらの提案はリフレクション機能に関するものではあるものの、それぞれスコープが異なるものが同時並行的に進行していたことによって、それぞれの仕様の間で不整合が生じている部分があります。この提案は、その様な問題点を修正しようとするものです。

この提案の修正は次のものです

  1. 不足している述語の追加
    • inlineconstexprなどの指定を取得するクエリ関数を追加する
      • is_inline(info) -> bool
      • is_constexpr(info) -> bool
      • is_consteval(info) -> bool
  2. スコープ識別機能の追加
    • std::meta::access_contextが追加されたことで、これを利用してある場所がどのスコープ(名前空間・クラス・関数など)なのかを判定することができるようになった
    • このような有用な情報のクエリは正式にサポートすべきなので、対応するメタ関数を追加する
  3. data_member_spec()において、型とアノテーションの指定をdata_member_optionsで指定するようにする
    • define_aggregate()において集成体型のメンバ変数を作成するためのdata_member_spec()関数では、メンバ型のリフレクションとその他プロパティを指定するオプション(data_member_options構造体)を受け取る
      • data_member_spec(info type, data_member_options options) -> info
    • 型だけを特別扱いして他のオプションと別に指定するのは奇妙なので、型の指定もdata_member_options構造体で指定する
    • また、生成したメンバに対してアノテーションを付加しておくためのオプションをdata_member_options構造体に追加する
  4. 関数引数へのアノテーションのサポート
    • P3394R4(リフレクションのためのアノテーションサポート)と、P3096R12(関数引数のリフレクション)は独立して作業されほぼ同時に採択された提案
    • そのため、関数引数へのアノテーションは検討されていなかった
    • 関数引数へのアノテーションサポートを追加する
  5. P1317R2で追加された型特性に対応するconstevalメタ関数の追加
    • P1317R2も6月の会議で採択されたが、そこで追加された3つの型特性に対応するリフレクションメタ関数は提供されていなかったため、これを追加する
      • is_applicable_type(info fn, info tuple) -> bool
      • is_nothrow_applicable_type(info fn, info tuple) -> bool
      • apply_result(info fn, info tuple) -> info
  6. エラー処理APIを一貫させる
    • P3650R2によってリフレクションのエラー報告は例外によるものになったが、P2996R13の型特性関数には適用されておらず、他の関連提案の関数にも適用されていない
    • 未適用のリフレクション関連関数のエラー報告は例外送出で一貫させる
  7. エラーの内容をより詳細に指定する
    • リフレクション関数の失敗時に例外を送出する際に、std::meta::exceptionfrom()から例外送出元関数のリフレクションを取得できることを明記する
    • リフレクション関数F()が例外を送出した場合、そのfrom()からは^^Fが取得できるようにする

この提案はNBコメント解決の一環としてC++26に向けて作業されています。

P3796R0 Coroutine Task Issues

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

std::taskは2025年6月の全体会議でC++26に向けて採択済みですが、その投票の前後でいくつかの問題点が指摘されていたようです。全体会議ではそれらの指摘が遅かったことやstd::taskC++26に間に合わせるためなどの理由によりそのまま投票にかけられてC++26に採択されました。

この提案はそのようなstd::taskに対する問題点とその解決案についてまとめたものです。ここで報告されているのは次のおおきく5つです

  1. affine_onについて
  2. std::taskの開始と終了の方法についての懸念
  3. アロケータの取り扱い
  4. std::stop_tokenの管理方法
  5. その他の設計上の修正

これらの項目それぞれについていくつか個別の問題があり、問題を説明するとともに一部はその解決策も提案しています。

  1. affine_onについて
    1. affine_onのデフォルト実装の仕様不足
      • -> デフォルト実装を指定する
    2. affine_onの動作が明確ではない
      • -> 明確になるようなセマンティクスを提示
        • affine_onsenderの完了は、開始された実行エージェント上でインラインで完了するか、指定されたスケジューラに関連付けられた実行エージェント上で非同期的に完了するかのいずれかとする
    3. affine_onシグネチャが改善できうる
      • -> スケジューラをreceiverの環境から取得できる可能性がある
    4. affine_onの暗黙的スケジュール操作には停止要求を伝播させない
      • コルーチンが中断したコンテキストで再開するために、affine_onの動作をキャンセルすべきではない
      • -> そのための設計選択肢を提示
    5. affine_onの暗黙的スケジュール操作には停止要求を伝播させない
      • 提案の議論時間の都合からカスタマイズについて曖昧にされているが、このアルゴリズムは他のsenderにおいてもカスタマイズが有効である可能性がある
      • -> カスタマイズをサポートする方法を規定することを推奨
  2. std::taskの開始と終了の方法についての懸念
    1. std::taskstart()時に再スケジュールすべきではない
      • std::taskの実行が関連付けられたスケジューラ出確実に行われるようにするために、サスペンドしてスケジューリングしてから再開、という手順を取る
      • 中断再開等の度にスケジューラを往復することになり効率的ではない
      • -> 再スケジュールしないような設計選択肢を提示
    2. std::taskを待機しているstd::taskは再スケジュールすべきではない
      • std::taskはその特性上開始時と同じスケジューラで完了する可能性が高いため、再スケジュールを回避できうる
      • -> その情報をクエリする方法など、そのための仕組みを整備する
    3. std::taskは対称転送をサポートしていない
      • 仕様では対称転送について一切言及がない
      • -> ここでの3つの問題合わせて、affine_onas_awaitableのカスタマイズによって解決することが望ましい
  3. アロケータの取り扱い
    1. std::generatorとアロケータカスタマイズの方法が異なる
      • std::taskの場合、環境からアロケータ型を取得してその型に合致するアロケータをコルーチン引数で受け取る
      • 環境から取得できない場合にデフォルト(std::allocator<std::byte>)以外を受け取れない(ill-formedになる)
      • これは、receiverの環境を介したget_allocatorクエリを処理するためだが、ここでのアロケータはコルーチンフレームの確保に使用されるもの
      • -> コルーチンフレームの確保に使用するアロケータについては、std::generator同様にstd::allocator_arg引数によって任意のものを指定できるようにする
    2. std::generatorstd::allocator_arg引数の位置が異なる
      • std::generatorが引数の先頭なのに対して、std::taskでは任意の位置でよい
      • -> std::taskも引数の先頭にする(任意の位置を許可するのはstd::generatorと同時に別に行う)
    3. 環境のアロケータを隠蔽しない
      • std::task下流からのget_allocatorクエリはstd::taskにおいてコルーチンフレームの確保に使用されたアロケータを返し、あとから接続されたreceiverの環境のアロケータを隠蔽してしまう
      • std::taskでもreceiverの環境のアロケータを使用する方が良い可能性が高いが、std::taskは接続前に環境型を確定してしまう(下流の操作は同じアロケータを使用すべきというコルーチンの原則による)ため、困難がある
      • -> アロケータの型消去や互換性のあるアロケータのみ転送するなど、環境のものを優先することを検討する
  4. stop_tokenの管理方法
    1. stop_sourceを常に新規作成し格納してしまう
      • std::task::promise_typeはストップトークン/ソースがデフォルトであるかに関わらずそれらを保持し、リレー的に上流の停止要求を伝播させる
      • これにはスペースと同期のコストがかかる
      • -> 接続後のoperation_stateにおいてストップトークン/ソースの型がデフォルトである場合は何も保持せず、receiverを介して上流のものを伝播させる
        • 型がデフォルトではない場合にのみ現在と同様に保持してリレーする
    2. stop_token型がデフォルト構築可能であるように扱っている
      • stop_tokenは通常stop_sourceから取得する必要があるためデフォルト構築可能ではないが、1の様に保存のためにメンバ変数を指定していることによってデフォルト構築可能性を要求(あるいは仮定)しているように見える
      • -> 1のように、stop_tokenを保存しないようにする
        • 環境から取得するか、stop_sourceから取得する
  5. その他の設計上の修正
    1. std::taskが積極的に開始される場合があるように見える(遅延実行されない場合があるように見える)
      • -> 紛らわしい文言を改善しかつより明確化することで、常に遅延実行することを明確にする
    2. コルーチンフレームの破棄が遅い
      • std::taskが中断されたコルーチンからfinal_suspendに到達することなく完了した場合、コルーチンフレームはoperation_stateオブジェクトの破棄まで保持され続ける
      • コルーチンフレームは完了関数が呼び出されてからかなり後に破棄される可能性がある
      • -> 完了関数が呼び出される前に破棄されることを指定する
    3. task<T, E>にはデフォルト引数がない
      • -> T = voidE = env<>に設定する
    4. unhandled_stopped()noexceptがない
      • -> この関数はnoexcept関数から呼び出されるため、noexcept指定する
    5. 環境の保存が非効率な場合がある
      • co_awaitで待機したsenderに接続するために使用されたreceiverget_envから返される環境は、プロミス型のstateメンバを参照して指定されているが、この中に場合によって無駄なコピーを発生させるものが入っている
      • -> 本当に必要になるまで環境を保存しないようにする
    6. 完了スケジューラがない
      • std::taskは、std::get_completion_scheduler<Tag>クエリに応答するget_envを定義していない
      • これにより、正しいスケジューラで完了している場合でも再スケジュールが必要になる場合がある
      • -> std::taskは完了スケジューラを知れるのはstd::taskの処理が完了した後であり、get_completion_schedulerが有用な場面はなさそう。将来必要になったら再検討する 7. 待機可能な非sender型がサポートされない
      • await_transform()オーバーロードsenderを満たす引数を必要とするように制約されており、senderではない待機可能な型はstd::taskのコルーチンに内で待機できない
      • -> await_transform()の制約を緩和して、サポートを拡大する
    7. std::task::promise_typewith_awaitable_sendersを使用していないこと
      • with_awaitable_sendersはコルーチンのpromise_typeの基底クラスとして使用して、senderco_await可能にするためのものです
      • しかし、その対象の第一号のはずのstd::taskはいくつかの理由からこれを使用しておらず、with_awaitable_sendersの設計の再検討が必要かもしれない
      • -> 少なくとも、std::taskのプロミス型の仕様のunhandled_stoppedの文言において、set_stopped完了が正しいスケジューラで呼び出されることを指定していない問題は修正すべき
    8. エラー発生時のco_yieldを回避できる実装を取れる可能性
      • std::taskでは、例外を送出せずにエラー発生時に完了させるにはco_yield with_error(x)を使用するが、co_yieldは通常コルーチンの途中の処理であるため、利用者にとって驚きがある可能性がある
      • -> 現在提案中の機能を活用することでより優れたエラーハンドリングを行える可能性があるので、それを考慮しておく
    9. TLSを退避・復帰する機能がない
      • TLSへアクセスしている既存のコードの移行時に、コルーチンの中断とそのスレッドで別のコルーチンの再開が起きたり、std::taskがスレッドプール上で実行していた場合に必ずしも同じスレッドに戻れないなど、移行の問題が起こりうる
      • sender/receiverではreceiverの環境をこの目的で使用することが推奨されているが、既存のTLSを使用しているプログラムからの移行時にはTLSを中断時に退避し再開時に復帰する機能が必要
      • -> affine_onのカスタマイズによって対応できる

affine_on(sndr, sch)continues_on(sndr, sch)とほぼ同じ動作をしますが、sndrの処理がsch上で完了することが分かっている場合に追加のスケジューリングを回避するように動作することを意図するものです。これはstd::taskと一緒に導入されているsenderアルゴリズムです。コルーチンの中断と再開を正しく取り扱うためには、コルーチンが中断したのと同じ実行コンテキストで再開をする必要があり、そのためにcontinues_onが使用できますが、affine_onを使用することでその操作の最適化を図ることができます。

affine_onstd::taskに必須のアルゴリズムとしてstd::taskと同時に導入されているものの、その動作の詳細や最適化・カスタマイズの方法などの仕様がかなりぼかして記述されており明確ではありません。これはstd::taskC++26に間に合わせるための時間制約によるものとみられており、それによってaffine_onに関連して問題が多く指摘されています(それだけではないですが)。

P3798R0 The unexpected in std::expected

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

std::expectedstd::optionalと共通するインターフェースを基本としており、オブジェクトが正常値を保持しているかどうかを調べるために.has_value()もしくはboolへの変換演算子を使用できます。

一方で、std::expectedstd::optionalとは異なり値もしくはエラー値を保持するものであるため、そのエラー状態の側に特化した独自のインターフェースを持つことで、エラー処理に重点を置く場合により自然に記述することができるようになります。

この提案は、そのために.has_error()メンバ関数を追加しようとするものです。

.has_error()はその名の通り.has_value()の逆の動作をするもので、エラー状態の場合にtrueを返します。

現在 この提案
if (!result.has_value()) {
  log_and_exit(result.error());
}
if (result.has_error()) {
  log_and_exit(result.error());
}
現在 この提案
// somewhere in unit tests
ASSERT_TRUE(!result.has_value());
EXPECT_EQ(result.error(), "myError");
// somewhere in unit tests
ASSERT_TRUE(result.has_error());
EXPECT_EQ(result.error(), "myError");

P3799R0 2025-07 Library Evolution Polls

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

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

P3125R5はC++29導入を目指して、P3778R0はC++26DRとするために、LWGに転送するための投票です。

P3801R0 Concerns about the design of std::execution::task

std::execution::taskC++26での採用を再考することを促す提案。

P3796R0とも共通するものですが、こちらにはstd::execution::task(P3552R3)が採択されるまでの過程が詳しく書かれています。

それによれば、P3552R3はC++26 feature freeze期限(おそらくLWGの期限、2月頃)に間に合わなかったものの、期限後にLEWGとLWG共同のスピードレビューによって2025年6月の全体会議にかけられ、異議申し立てはあったもののC++26に採択されました。

しかし、このような例外的な経路を辿っているにもかかわらず必要な手続きが取られておらず、P3552R0の提出も2025年に入ってからと遅かったため議論が尽くされていない可能性があります。実際著者の方も、2025年6月の会議直前までP3552R3がC++26向けに検討されていることに気づいていなかったため問題点の指摘が遅れたとしています。

この提案はそのような拙速なプロセスへの非難と、std::execution::task設計の問題点について指摘し、C++26から取り下げることを提案するものです。

  • 重大な懸念事項
    1. 対称転送をサポートしていないこと
    2. taskのコルーチンフレームの破棄が遅い
      • コルーチン引数とローカル変数の破棄が、taskoperation_stateの破棄まで延期される
      • |でつないだパイプライン全体が終了するまで、コルーチン状態が破棄されない
    3. ダングリング参照への対策の欠如
      • コルーチン引数に参照を渡すと、それが浅くコピーされ(参照のままコピーされ)コルーチンフレームに格納されることで、参照の生存期間に注意を払う必要がある
      • 言語の改善には時間がかかるが、ライブラリソリューションがすでに存在しているため、対応すべき
  • その他の懸念事項
    1. co_yield with_error(x)というエラー処理の方法
      • 直観的ではないため、言語を修正してco_returnを使用できるようにすべき
    2. co_await ex::schedule(sch)という高コストなno-op
      • これは通常のsenderチェーンであればスケジューラを変更することを意味するが、taskにおいては異なる
        • 正確には、change_coroutine_schedulerを使用する
      • 1の変更によってco_yieldを解放し、co_yield schのようにして簡易にスケジューラ変更をできるようにする
    3. コルーチンのキャンセルがアドホック
      • C++20コルーチンはそのキャンセルをサポートしていないため、std::executionではコルーチンのプロミス型を拡張してunhandled_stoppedというカスタマイズポイントを設けた
      • 言語そのものを拡張して、コルーチンのキャンセルをネイティブにサポートすべき
        • これにより、対称転送をサポート可能にもなる

おおくはP3796R0での指摘と共通しています。

その他の懸念事項についてはC++26に間に合わせる必要はないものの、時間的余裕があれば解決が可能だった可能性があるとしています。

P3552R3については設計の検討不足をその著者の方も認めているようで、問題はNBコメントを通して解決することを意図していたようです。この提案はそれに反対しており、問題があることが分かっていたのならば無理に間に合わせるべきではなかったとして、C++26へのP3552R3採択を再検討することを要求しています。

P3802R0 Poor Functions

呼び出されている場所のコンテキストに依存する関数から、そのコンテキスト依存性を分離する提案。

このような関数の代表は、std::source_location::current()です。

namespace std { 

  struct source_location { 
    static consteval source_location current() noexcept; 
    ... 
  }; 
} 

宣言だけを見ればこれは普通の静的関数であり、その他の関数にラップしても動作するように見えます。

consteval auto curr_loc() { 
  return std::source_location::current(); 
}

しかし実際にはこれは意図通りに機能しません。std::source_location::current()コンパイル時のグローバル状態や呼び出しが行われるローカルコンテキストに依存した結果を取得するため、関数にラップしたものを呼び出すとラップしない場合と結果が異なります。

using srcloc = std::source_location; 

consteval auto number(srcloc loc = srcloc::current()) { 
  return loc.line(); 
}

auto r = number(); 

したがって、このコードにおいてsrcloc::current()の呼び出しをcurr_loc()の呼び出しに置き換えることはできません。これは、srcloc::current()の呼び出しはそれが出現するソースロケーションコンテキストを取得するものの、デフォルト引数として使用されている場合はそのデフォルト引数が使用されるコンテキストを取得するためです。

上記curr_loc()の様な関数においてこのような振る舞いを再現する方法はありません。

std::source_location::current()はこのような振る舞いを持つ標準内で最初の関数でしたが、C++26ではstd::meta::access_context::current()という関数が同様の振る舞いを持つものとして新しく追加されています。また、同種の関数の提案が潜在的に存在しているようです。

この提案では、これらの関数について暗黙的なローカルコンテキストに依存するのではなく、ローカルコンテキストを明示的にし、それに依存する通常の関数として定義するようにすることを提案しています。

namespace std { 
  struct source_location { 
    static consteval source_location current(std::meta::info = __local_ctx) noexcept; 
    ... 
  };
}

この__local_ctxというのはキーワードによって指定される言語組み込みのもので、現在std::source_location::current()が特別に持っているローカルコンテキスト情報を表す値です。

これを用いると、curr_loc()は次のように書き直すことができます

consteval auto curr_loc(std::meta::info c = __local_ctx) { 
  return std::source_location::current(c); 
}

これにより、curr_loc()std::source_location::current()のような関数をラップしながらもその望ましい動作を達成することができます。そして、元来std::source_location::current()が持っていたような魔法的な性質は__local_ctxに分離されることで、同種の関数を普通の関数として定義することができるようになります(std::source_location::current()の性質はユーザー定義関数では完全に再現できない部分があり、特別な規定を必要としている)。

__local_ctxは当初は実装依存のものとして導入しておき、将来必要になった際に標準構文へ昇格させることを提案しています。また、型としてはstd::meta::infoが完全に適切としています。

おわり

この記事のMarkdownソース




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

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