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


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

文書の一覧

全部で166本あります。

もくじ

N4977 2025-11 Kona meeting information

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

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

N4995 WG21 agenda: 18-23 November 2024, Wroclaw, Poland

2024年11月にヴロツワフで行われた会議のアジェンダ

N4997 Hagenberg Meeting Invitation and Information

2025年2月にオーストリアのハーゲンベルクで行われる全体会議のインフォメーション。

予定(2025年2月10日~15日)と場所、ホテルの案内などが記載されています。

N4998 WG21 2024-11 Wroclaw Admin telecon minutes

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

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

N4999 WG21 agenda: 10-15 February 2025, Hagenberg, Austria

2025年2月にハーゲンベルクで行われる会議のアジェンダ

N5000 WG21 November 2024 Hybrid meeting Minutes of Meeting

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

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

N5001 Working Draft, Programming Languages -- C++

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

N5002 Editors' Report, Programming Languages -- C++

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

N5003 2025 WG21 admin telecon meetings

2025年に行われる、WG21管理者ミーティングの予定表。

P0178R1 Allocators and swap

アロケータが等価ではない場合のコンテナ間swapを未定義動作ではなくする提案。

C++17で導入されたpolymorphic_allocatorのように、状態を持つアロケータにおいては同じアロケータ型のアロケータオブジェクト同士の比較が必ずしもtrueにはなりません。その場合、そのようなアロケータ同士を保持するコンテナのswapは未定義動作となります。

swapは単にコンテナの内部ハンドルを交換するだけで済み、アロケータが等価であればアロケータそのものの交換作業は不要になります。しかし、アロケータ等価ではない場合にそのようなswapを行ってしまうと交換後のコンテナでは、そのメモリ領域を解放する能力のないアロケータとそのアロケータから取得されていないメモリ領域がペアになった状態になり、この状態では正しくリソース解放が行われなくなります。

C++11以前には状態を持つアロケータがサポートされていなかったため、標準のコンテナのデフォルト動作はアロケータの不一致を考慮しないswap(すなわち、アロケータ以外の内部ハンドル等のswap)を行います。C++11では状態を持つアロケータがサポートされたものの、propagate_on_container_swaptrue_typeの場合にのみswap操作でアロケータが伝播する事を期待できますが、std::allocatorは(およびstd::allocator_traitsstd::pmr::polymorphic_allocatorも)これを定義しないため、標準コンテナのアロケータはステートフルなものが指定されてもpropagate_on_container_swaptrue_typeで定義されていなければアロケータが等価ではない場合のswapは未定義動作となります。

これはおそらく、以前からコンテナswapの要件が定数時間であることも関係していると思われます。

いずれにしても、標準のコンテナはほとんどの場合にアロケータが等価ではない場合のswapを未定義動作としています。

std::swap()は広い契約をもつ関数であるものの、標準のコンテナのswap操作はstd::swapの各コンテナによるオーバーロードを通してカスタマイズされており、各コンテナのswapメンバ関数)はこのアロケータ不一致に関するより狭い契約を持ちます。これは、広い契約を持つ関数を狭い契約をもつ関数で置き換えており、

これによりユーザーは、アルゴリズム等のジェネリックなコードにおいてコンテナのswapを行う場合に、それが標準のコンテナであるかどうか(アロケータの不一致をケアする必要があるかどうか)、すなわちstd::swapの契約とは異なる契約になっていないかを考慮する必要があります。

この現状に対してこの提案は、標準のコンテナのswapがアロケータが等価ではない場合でも未定義動作とならないようにすることを提案するものです。

標準のコンテナのアロケータとswapの関係は現在次のようになっており

swap時の伝播可能性\実行時等価性 アロケータが等価 アロケータが等価ではない
propagate_on_container_swap有効
propagate_on_container_swap無効

この提案は、この表の右下の部分をwell-formedにしようとしています。

具体的には、アロケータが等価ではなくpropagate_on_container_swapも無効な場合には要素ごとのムーブによってアロケータが一致するように要素を交換する事を要求するようにします(同時に、計算量の要件も緩和します)。そのほかの場合の動作は現状通りで変更しません。

とても簡易には次のような実装になります

void CONTAINER_TYPE::swap(CONTAINER_TYPE & other) {
  if( std::allocator_traits<allocator_type>::propagate_on_container_swap
   || std::allocator_traits<allocator_type>::is_always_equal
   || this->get_allocator() == other.get_allocator()) {
    // trust compiler will optimize the compile-time branching
    // take the fast path

    if constexpr(std::allocator_traits<allocator_type>::propagate_on_container_swap) {
      using std::swap;
      swap(this->get_allocator(),other.get_allocator());
    }
  }
  else if constexpr( all traits are consistent ) {
    CONTAINERbuffer{std::move(other)};
    other = std::move(*this);
    *this = std::move(buffer);
  }
  else {
    //Copy into buffer having the right allocator
    CONTAINERbuffer_this {std::move(other), this->get_allocator()};
    CONTAINERbuffer_other{std::move(*this), other.get_allocator()};

    this->swap(buffer_this);
    other.swap(buffer.other);
  }
}

この提案の根拠としては、ブルームバーグ社内コードベースでの10年以上に渡るステートフルアロケータと標準ライブラリの使用経験において、提案するような動作を保守し続けており、現在でもその動作をサポートする理にかなったユースケースが存在している事を確認している事を挙げています。少なくともブルームバーグ社内のユーザーは、線形時間かつ例外を送出しうるswapと未定義動作のどちらかの選択を迫られたときに明確に前者を選択している、としています。

P0260R12 C++ Concurrent Queues

P0260R13 C++ Concurrent Queues

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

以前の記事を参照

R12での変更は

  • St. LouisでのLEWG/SG1フィードバックを反映
    • アロケータ受け入れに関する文言を追加
    • シングルエンドインターフェースを提供しない理由を追記
    • buffer_queueがムーブ不可な理由を追記
    • buffer_queueemplaceを提供しない理由を追記
  • WroclawでのSG1フィードバックを反映
    • sequential consistencyに関する文言を修正
    • async_*に関する文言を修正
    • buffered_queueのキューとしての順序の指定を修正
  • 使用例を追加

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

  • conqueue_errcsuccessを追加
  • エラーハンドリングの更新
  • コンセプトの根拠を追加
  • API(エラーハンドリング・戻り値型・emplace)の更新(根拠も含めて
  • 例を更新

などです。

P0447R27 Introduction of std::hive to the standard library

P0447R28 Introduction of std::hive to the standard library

要素が削除されない限りそのメモリ位置が安定かつメモリ局所性の高いコンテナであるstd::hive(旧名std::colony)の提案。

以前の記事を参照

R27での変更は大量なので省略します・・・

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

  • LWGとリフレクターの変更を適用
  • FAQ19の更新
    • shrink_to_fit()と、アロケータが等しくない場合のムーブ代入・構築について網羅
  • non-reference-implementation appendixに軽微な修正

などです。

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

P0472R3 Put std::monostate in <utility>

std::monostate<utility>からも利用できるようにする提案。

以前の記事を参照

このリビジョンでの変更は、LWGからのフィードバックを適用し<utility>に追加する文言を単なるコピペとしたことのようです。

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

P1040R7 std::embed and #depend

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

モチベーションは#embedと共通しているので、以前の記事を参照(最新のリビジョンは下の方にもあります)。

提案しているstd::embed()は関数テンプレートであり、次のようなインターフェースになっています

namespace std {
  template <typename T = byte>
  consteval span<const T> embed( 
    string_view resource_identifier
  );
  
  template <typename T = byte>
  consteval span<const T> embed( 
    string_view resource_identifier, size_t limit
  );
  
  template <typename T = byte>
  consteval span<const T> embed( 
    string_view resource_identifier, size_t offset, size_t limit
  );
  
  template <size_t N, typename T = byte>
  consteval span<const T, N> embed( 
    string_view resource_identifier
  );
  
  template <size_t N, typename T = byte>
  consteval span<const T> embed( 
    string_view resource_identifier, size_t offset
  );
}

引数offsetおよびlimitの意味はそのまま、リソース先頭からのバイトオフセットと、読み込み最大長です。ただしどちらも、バイト数ではなく読み取る型Tによる要素数の指定になります。型Tとしてはトリビアルに破棄可能(trivial destructibile)な任意の型を指定できます。

引数resource_identifierは取り込むリソースの場所を示す何らかの識別子ですが、このリソースの探索方法は実装定義とされます。これは現在の#includeと同等のメカニズムによる(翻訳単位のカレントディレクトリおよび-Iオプションなどによって指定された場所)事を意図していますが、読み込んだリソースから再帰的に次のリソースを読み込むことをサポートするために、#dependディレクティブも提案されています。

#dependディレクティブはstd::embed()が読み込むリソースを、予め翻訳単位において宣言しておくものです。これによって、ビルドシステムや周辺ツールが把握できないような場所へリソース依存関係を作成することを回避し、コンパイルのフェーズ5以降でリソース探索を実施しないようにしています。

#dependディレクティブによる依存リソースの宣言方法は、globによるリソース名の指定と類似したパターンによって記述できます。

// 単一の依存ファイル/ディレクトリ
#depend <config/graph.bin>
#depend <foo.txt>

// 複数の依存関係
// このパターン(`*`)においてはディレクトリ内を再帰的にマッチしない
#depend "art/*"
#depend "art/mocks/*.json"

// 再帰的な依存関係
// ディレクトリ等を再帰的にマッチする
#depend "assets/**"

// mixed: all resources starting with
// "translation/", with all files that end in ".po",
// that have at least one "/" (one directory)
// after the "translation/", found recursively
// 混合指定
// "translation/"で始まり".po"で終わるすべてのファイル
// "translation/"の後に少なくとも1つの`/`があり
// "translation/"以下を再帰的にマッチする
#depend "translation/**/*.po"

std::embed(resource_identifier)の様な呼び出しは、resource_identifierで表されるリソースが実装定義の探索範囲およびその翻訳単位で#dependディレクティブによって指定された場所の両方から見つからない場合、コンパイルエラーとなります。

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

// リソース探索場所の宣言
#depend <sdk/*>

// std::embed()は<embed>で提供される
#include <embed>
#include <cstddef>

int main () {
  // リソースの読み出し
  constexpr const auto sound_signature = std::embed("sdk/jump.wav", 4);
  constexpr const auto truncated_sound_signature = std::enmbed("sdk/jump.wav", 2, 2);

  // PCM WAVであることを検証
  static_assert(sound_signature.size() == 4);
  static_assert(sound_signature[0] == (std::byte)'R');
  static_assert(sound_signature[1] == (std::byte)'I');
  static_assert(sound_signature[2] == (std::byte)'F');
  static_assert(sound_signature[3] == (std::byte)'F');


  static_assert(truncated_sound_signature.size() == 2);
  static_assert(sound_signature[0] == (std::byte)'F');
  static_assert(sound_signature[1] == (std::byte)'F');
}

std::embed()#embedとの違いは

  • 配列リテラルを作成しない
    • 取り込んだバイナリデータをASTに保存しない
    • コンパイル時のパフォーマンス(取り込み速度及びメモリ使用量)で有利
  • プリプロセッサではなくライブラリ関数であること
    • C++コードによる処理の記述がより自然に行える
  • offset引数の有無
    • #embedにはoffsetの指定は無い
    • ただし、std::embed()にはprefix/suffixの指定がない

にあると思われます。

提案には#embedも含めた既存の方法とstd::embedの先行実装とのパフォーマンスとメモリ使用量の比較ベンチマークが掲載されており、#embedと比べてさらに効率的な結果が得られています。

提案の流れとしては、std::embed()が先に構想されていたものの、再帰的に無限のリソースを読み込むこと(読み取ったバイナリリソースをパースして、別のリソースパスをstd::embed()する)ができてしまうのが忌避された(主にリソースの依存関係探索をコンパイルフェーズで行うことになるのが忌避された)結果として、#embedが派生しそちらがC/C++の両方で導入された、という経緯があります。なお、この問題は現在#dependによって解決されています。

上に書いた例を#embedで行う場合の例

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

  // verify PCM WAV resource
  static_assert(sizeof(sound_signature) == 4);
  static_assert(sound_signature[0] == 'R');
  static_assert(sound_signature[1] == 'I');
  static_assert(sound_signature[2] == 'F');
  static_assert(sound_signature[3] == 'F');
}

P1061R10 Structured Bindings can introduce a Pack

構造化束縛可能なオブジェクトをパラメータパックに変換可能にする提案。

以前の記事を参照

このリビジョンでの変更は、テンプレートの外でパックを利用する機能を削除したことです。

R9までは、この機能を通常の関数(非テンプレート)でも利用可能とし、現れた場合は暗黙のテンプレート領域を導入することで通常関数を暗黙的に関数テンプレートに変換して、パックを扱うようにしていました。これの仕様そのもの及び実装における複雑さのため、この仕様は削除され、この機能はあくまで関数テンプレートの中でのみ使用できるようになりました。したがって、通常の関数で使用したい場合はダミーのテンプレートパラメータを追加するなどして関数テンプレートに変換する必要があります。

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

P1928R13 std::simd - Merge data-parallel types from the Parallelism TS 2

P1928R14 std::simd - Merge data-parallel types from the Parallelism TS 2

P1928R15 std::simd - Merge data-parallel types from the Parallelism TS 2

std::simd<T>をParallelism TS v2から標準ライブラリへ移す提案。

以前の記事を参照

R13での変更は

  • その結果がスカラ関数を要素ごとに適用した場合と同等(“Equivalent to”)になる数学関数をリストアップ
  • reduction-binary-operationの概要にて、see belowを使用するように修正
  • math-common-simd-tにおけるT0, T1, TRestの導入を改善
  • 2つの型のうち片方だけがmath-floating-pointを満たす場合のmath-common-simd-tを修正
  • reduction-binary-operationで、binary-opvconst
  • one-simd-argumentな数学関数において、Vではなくdeduced-simd-t<V>を使用するように修正
  • “has a member type”を“is valid and denotes a type”に書き換え
  • errnoに関して、[simd.math]の“Equivalent to:”という表現を制限
  • [simd.math]の“Equivalent to:”な関数のリストからsqrtを削除
  • 実装定義のvector型への変換の表現を置換
  • 数学関数の引数におけるCTAD変換を削除
  • rebind_simdresize_simdを修正し、Abi1を正しく設定
  • simd-floating-pointコンセプトで欠落していた_vを追加
  • 複合代入演算子をhidden friendsではなくメンバ関数にする

R14での変更は、P3299R2(rangeコンストラクタの追加)をマージしたことです。

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

  • P3299R3の変更点からのdiffマークアップを削除
  • LEWGのフィードバックを適用
    • load_from()simd_unchecked_load/simd_partial_loadに分割
      • store_to()も同様
    • 静的にサイズ指定された範囲からのbasic_simdコンストラクタを暗黙的に作成
  • 複合代入の変更を元に戻した

などです。

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

P1967R13 #embed - a simple, scannable preprocessor-based resource acquisition method

コンパイル時(プリプロセス時)にバイナリデータをインクルードするためのプリプロセッシングディレクティブ#embedの提案。

以前の記事を参照

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

  • #embedの3つの形式のうち2つの場合で展開がどのように機能するかの例を追加
  • std::fgetcの文言を適切に使用し、implementation-resoruce-widthという言葉の使用を減らして、代わりにresource-countを使用する

などです。

この提案は次のリビジョンが2024年11月の全体会議で承認され、C++26に採択されています。

P2014R1 Proposed resolution for US061/US062 - aligned allocation of coroutine frames

P2014R2 Proposed resolution for US061/US062 - aligned allocation of coroutine frames

コルーチンステートを割り当てる際のoperator new呼び出しの際に、std::align_val_tを考慮するようにする提案。

この提案は、C++20のNBコメントUS061とUS063で提起された問題に対する解決策を提案するものです。

コルーチンが最初に呼び出された時にコルーチンステートを確保しようとする際に、プロミス型のコンテキストおよびグローバルなoperator newを探索しますが、その際にstd::align_val_tを取るオーバーロードを考慮していません。それによって、コルーチンの引数やプロミス型など、コルーチンフレームに保存されるものの中にオーバーアライメントされたものが含まれている場合などに正しくアラインされない可能性があります。

US061とUS063はどちらもこの問題の解決を求めるものであり、この提案のR0ではそのためのオプションを2つ提案していました

  1. 割り当てるストレージがnew-extendedアライメントを要求する場合にのみ、 std::align_val_tオーバーロードを優先する
    • 利点: new/delete式の動作とより一貫している
    • 欠点: コルーチン開始時のoperator new探索のルールが複雑化し、2回探索する必要がある。また、std::align_val_tオーバーロードを呼び出すかどうかの選択がコルーチンフレームのレイアウトが判明するコンパイルフェーズの後半まで延期しなければならなくなる
  2. コルーチンフレームやnew-extendedアライメントを要求するかに関係なく常にstd::align_val_tオーバーロードを優先する
    • 利点: より単純
    • 欠点: new/delete式の動作と矛盾する

そして、EWGでの議論の結果オプション1が選択されています。

オプション1でのコルーチン開始時(初期化時)にコルーチン用の追加の記憶域を確保する必要がある場合に行われる2段階の探索は次のようになります。


まず、その記憶域は非配列のoperator newを呼び出すことで確保され、使用するoperator newはプロミス型のコンテキストで探索され、その結果何らかの関数が見つかった場合、その集合からオーバーロード解決を次のように行う

  1. 最初のオーバーロード解決は、引数リストを組み立てたうえで行われる
    • 第一引数は確保サイズ(コルーチンフレームのサイズ)であり、std::size_t
    • 第二引数はコルーチンステートのアライメントであり、std::align_val_t
    • 残りの引数はコルーチンの引数リストとなる
    • オーバーロード解決の結果実行可能な関数が見つかり、第二引数が依存型ではない場合、見つかった関数をoveraligned-allocation-functionとして定義する
      • 依存型ではない = 第二引数がテンプレートパラメータによって指定されていない
    • 見つからなかった場合、最初の2つの引数だけからなる引数リストによって再度オーバーロード解決を行う
      • オーバーロード解決の結果実行可能な関数が見つかり、第二引数が依存型ではない場合、見つかった関数をoveraligned-allocation-functionとして定義する
  2. 第二のオーバーロード解決は、再度引数リストを組み立てたうえで行われる
    • 第一引数は確保サイズ(コルーチンフレームのサイズ)であり、std::size_t
    • 残りの引数はコルーチンの引数リストとなる
    • オーバーロード解決の結果実行可能な関数が見つかった場合、見つかった関数をnormal-allocation-functionとして定義する
    • 見つからなかった場合、第一引数だけからなる引数リストによって再度オーバーロード解決を行う
      • オーバーロード解決の結果実行可能な関数が見つかった場合、見つかった関数をnormal-allocation-functionとして定義する

operator newがプロミス型のコンテキストで見つからなかった場合、overaligned-allocation-function::operator new(std::size_t, std::align_val_t)で、normal-allocation-function::operator new(std::size_t)で定義する。

そして、コルーチンステートがnew-extendedアライメントを持ち、かつoveraligned-allocation-functionが見つかっている場合、コルーチンステートはoveraligned-allocation-functionの呼び出しによって確保される。そうではない場合でnormal-allocation-functionが見つかっている場合、コルーチンステートはnormal-allocation-functionで確保される。それ以外の場合はill-formed。


現在の仕様は、プロミス型のコンテキストでは2番目のオーバーロード解決フェーズのみを行い、またグローバルスコープの探索ではstd::size_t型の引数だけを用いてオーバーロード解決を行い、そこで見つかった関数がそのまま使用されます。

なお、コルーチンステートを解放するoperator deleteも同様にstd::align_val_tオーバーロードを考慮するように修正されます。とはいえnewの場合ほど複雑ではなく

  1. プロミス型のコンテキスト -> グローバルスコープ の順でoperator deleteを探索
  2. プロミス型のコンテキストで見つかった候補の中にdestroying operator deleteが含まれている場合、ill-formed
  3. コルーチンステートがnew-extendedアライメントを持つ場合、std::align_val_t型の引数を取るオーバーロードが優先される
  4. そうではない場合、std::align_val_t型の引数を取らないオーバーロードが優先される
  5. 優先される候補以外の候補は削除され、残った候補のうちではstd::size_t型の引数を取るものが取らないものよりも優先される

のようになっています。

なお、この提案はCWGのレビュー中だったものの、著者の方がこれ以上追及されないとして、取り下げられているようです。

P2319R3 Prevent path presentation problems

P2319R4 Prevent path presentation problems

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

以前の記事を参照

R3での変更は、R2のSG16投票結果を追記したことです。

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

  • system_string()/display_string()のアクセサ関数を復帰した
  • system_string()system_encoded_string()に変更
  • R3のLEWG投票結果を追加

などです。

P2645R1 path_view: a design that took a wrong turn

提案中のpath_viewの問題点を指摘する提案。

以前の記事を参照

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

  • P1030R7の変更を反映するように修正
  • 「Implementation and usage experience」に使用経験に関するデータを追加
  • 「Performance」にベンチマークを追加

などです。

P2656R4 WITHDRAWN: C++ Ecosystem International Standard

C++実装(コンパイラ)と周辺ツールの相互のやり取りのための国際規格を発効する提案。

以前の記事を参照

このリビジョンでの変更は、撤回を通知するための様です。

P2686R5 constexpr structured bindings and references to constexpr variables

構造化束縛にconstexpr指定できるようにする提案。

以前の記事を参照

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

  • コア文言の例を修正
  • 実装に関する注記を追加

などです。

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

P2688R4 Pattern Matching: match Expression

C++へのパターンマッチング導入に向けて、別提案との基本構造の違いに焦点を当てた議論。

以前の記事を参照

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

  • 関連提案の提出について記録
    • P3521R0: Pattern Matching: Customization Point for Open Sum Types
    • P3527R0: Pattern Matching: variant-like and std::expected
  • 文言をさらに追加
  • 2024年11月会議のEWGでの投票についてメモ

などです。

P2717R6 WITHDRAWN: Tool Introspection

C++周辺ツールが、Ecosystem ISにどれほど準拠しているのかを互いに通信する手段を標準化する提案。

以前の記事を参照

このリビジョンでの変更は、撤回を通知するための様です。

P2781R5 std::constexpr_wrapper

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

以前の記事を参照

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

  • constexpr変数テンプレートから不要なinlineを削除
  • 文字列リテラルを含めた配列型をサポートするように実装を修正

などです。

P2786R9 Trivial Relocatability For C++26

P2786R10 Trivial Relocatability For C++26

P2786R11 Trivial Relocatability For C++26

trivially relocatableをサポートするための提案。

以前の記事を参照

R9での変更は

  • trivially_relocate()/relocate()に欠けていた事前条件を追加
  • 動作例を追加
  • FAQエントリを追加
  • コア文言修正
  • デストラクタが削除された型は、trivially relocatableでもreplaceableでもなくなる
  • 置換をする資格があるためには、クラス型に資格のあるコンストラクタと代入演算子が必要
  • swap_value_representationsの新しい文言を追加
  • 文脈依存キーワードのリネームに関する議論を追加

R10での変更は

  • memberwise_trivially_relocatabletrivially_relocatableに、memberwise_replceablereplceableに変更
  • 提案のスコープを最小限の機能セットに縮小する
    • consumer-level, non-trivial, relocateアルゴリズムの削除
    • swap_value_representations()を削除
    • std::swapの最適化をQoIに延期
    • これらの変更に合わせたeditorialな修正

ここでの変更は、ライブラリ機能に関する反対意見や修正の必要性が出る可能性を低減して、言語機能としての導入を優先するためのものです。

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

  • R10でのキーワード変更を元に戻す
  • 提案の内容と未解決の問題をまとめた「Proposal Status」セクションを追加
  • 推奨される解決策と可能な投票を含む「Open Issues」セクションを追加
  • 単一オブジェクトのリロケーションのためのtrivially_relocate_at()を追加
  • R9に存在していたconsumer-levelのリロケーションアルゴリズムを復帰
  • is_nothrow_relocatable特性を追加

などです。

P2825R3 Overload resolution hook: declcall( unevaluated-call-expression )

与えられた式が呼び出す関数の関数ポインタを取得する言語機能の提案。

以前の記事を参照

R3での変更点は明確ではないですが

  • 提案する文言の追加
  • 「Design question about pointers to virtual member functions」セクションの追加
  • 「Guidance Given」セクションの更新
    • 構文をリフレクションに委ねず独自のものを使用することにSG7とEWGの支持があった
    • 評価されないオペランド(Unevaluated operand)のみを取ることにSG7とEWGの支持があった
  • 謝辞の追加

などのようです。

P2830R5 Standardized Constexpr Type Ordering

P2830R6 Standardized Constexpr Type Ordering

P2830R7 Standardized Constexpr Type Ordering

std::type_info::before()constexprにする提案。

以前の記事を参照

R5での変更は

  • 順序を定義するために何を標準化する必要があるかについて、さらに多くの作業を行った
  • SG7では、順序を完全に定義することに支持があった
  • EWGでは、実装定義のままにするという強いコンセンサスがあった
  • 文言を追加
  • 完全に定義された順序についてhistorical appendixにまとめた

R6での変更は、機能テストマクロを追加したことです。

このリビジョンでの変更は、文言を修正したことです。

このリビジョンでは(正確にはR5では)、完全な型の順序を付けることを諦めて、標準では比較の大まかなセマンティクスだけを指定して、実際の順序については実装定義するアプローチを取っています。これは、完全な順序を規定しようとすると作業も規定も著しく複雑化するためです(「Appendix ZZZ: This his how deep the rabbit hole goes」セクションに名残があります)。

セマンティクスは次のように指定されています


XYを(CV or 参照)修飾された型としてTYPE-ORDER(X, Y)を説明専用のマクロとする。この時、TYPE-ORDER(X, Y)std::strong_ordering型の定数式となり、その結果は次のようになる

  • TYPE-ORDER(X, Y) == std::strong_ordering::equalとなるのは、std::same_as<X, Y> == trueとなる場合のみ
  • それ以外の場合、TYPE-ORDER(X, Y)std::strong_ordering::lessstd::strong_ordering::greaterのどちらか

結果がstd::strong_ordering::equalとならない場合にどちらになるかは実装定義であり、実装はTYPE-ORDER(X, Y)を順序関係、すなわち推移的かつ反対称的となるように定義しなければならない。

  • 反対称律: TYPE-ORDER(X, Y) == std::strong_ordering::lessの場合、TYPE-ORDER(Y, X) == std::strong_ordering::greateとならなければならず、その逆も同様
  • 推移率: TYPE-ORDER(X, Y) == std::strong_ordering::lessかつTYPE-ORDER(Y, Z) == std::strong_ordering::lessの場合、TYPE-ORDER(X, Z)std::strong_ordering::lessとなる

実装においては順序が再帰的に一貫している事が推奨されるものの、必須ではない。これを言い換えると

任意のクラステンプレートtemplate <..., typename Pi, ...> class XPii番目のテンプレート引数)と、任意の2つの型T, Uについて、i番目のテンプレート引数のみをT, Uに置き換える場合、TYPE-ORDER(T, U) == TYPE-ORDER(X<..., T, ...>, X<..., U, ...>)となる(事が推奨される)。

実装において順序が再帰的に一貫するようにする場合、それを文書化する必要がある。


さらに、比較を行うライブラリ機能としてtype_order_v<T, U>を追加しています

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;

P2835R7 Expose std::atomic_ref's object address

std::atomic_refが参照しているオブジェクトのアドレスを取得できるようにする提案。

以前の記事を参照

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

  • constexprを付加
  • 特殊化に対しても同様の関数を追加

などです。

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

P2846R5 reserve_hint: Eagerly reserving memory for not-quite-sized lazy ranges

遅延評価のため要素数が確定しない range の ranges::to を行う際に、推定の要素数をヒントとして知らせる ranges::reserve_hint CPO を追加する提案。

以前の記事を参照

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

  • LEWGからの文言フィードバックを適用
  • vectorコンストラクタの計算量の要件からnothrow_move_constructible要件を削除
  • 機能テストマクロの修正

などです。

この提案は次のリビジョンが2024年11月の全体会議で承認され、C++26に採択されています。

P2863R8 Review Annex D for C++26

現在非推奨とマークされている機能について、C++26で削除/復帰を検討する提案。

以前の記事を参照

このリビジョンでの変更は主に追跡中の提案のステータス更新です。

P2865R6 Remove Deprecated Array Comparisons from C++26

C++20の一貫比較仕様に伴って非推奨とされた、配列間の比較を削除する提案。

以前の記事を参照

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

  • 最新のWDに追随
  • マークアップの修正
  • “this international standard”-> “this document”
  • 全ての例文を"[Example: —end example]"の形式に修正

などです。

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

P2866R5 Remove Deprecated Volatile Features From C++26

C++20で非推奨とされたvolatile関連の機能を削除する提案。

以前の記事を参照

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

  • この提案の熟成度を反映するため、内容を損なうことなく概要を簡素化
  • コア言語解析に関するEWGの懸念事項を追加
  • WrocławでのCWGレビューを反映
  • CWGレビューを受けて文言を更新し、明確化
  • 最新のWDに追随
  • “volatile qualified” を “volatile-qualified”に変更
  • 7.6.2.3 [expr.pre.incr]での言葉の繰り返しを解消
  • 9.3.4.6 [dcl.fct]にvolatile関数引数の例を追加
  • C.1.2 [diff.cpp23.expr]において、“atomic operation”ではなく“single access”を使用

などです。

P2897R6 aligned_accessor: An mdspan accessor expressing pointer overalignment

P2897R7 aligned_accessor: An mdspan accessor expressing pointer overalignment

mdspanのアクセサポリシークラスに、参照する領域ポインタにstd::assume_alignedを適用してアクセスするaligned_accessorの提案。

以前の記事を参照

R6での変更は

  • すべてのテンプレートパラメータ名をパスカルケースに変更
  • is_sufficiently_aligned()のテンプレートパラメータ順を入れ替え
  • offsetaccessの両方でassume_alignedを使用
  • aligned_accessorのgcd要件を変更し、コンストラクタをMandateからConstraintに戻す
    • Section 5.9に例と説明を追加
    • 両方のアライメントが2の累乗であるため、“OtherByteAlignment >= byte_alignment is true.”と記述
  • accessの事前条件を削除
    • "Effects"がassume_alignedを使用することと同等(equivalent to)であるため、暗黙的に示されるため
  • Compiler Explorerの実装リンクを更新
  • aligned_accessorクラスにアライメントに関する事前条件を追加
    • default_accessoraligned_accessorに渡されるデータハンドルに対するクラス全体の事前条件を説明する、“Standard accessors already impose preconditions that propagate to mdspan construction”セクションを追加
  • default_accessorへの変換演算子noexceptにする
    • より大きいアライメントから小さいアライメントへの変換コンストラクタも同様
  • offsetaccessのアクセス可能範囲の事前条件を修正し、ポインタ範囲ではなくインデックス範囲を使用するようにする
  • default_accessorへの変換演算子を結果の要素型でテンプレート化
  • 2番目の例を削除し、最初の例をクラス概要の直後に移動

このリビジョンでの変更は、規格編集者からの要望によるeditorialな修正のみです。

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

P2900R11 Contracts for C++

P2900R12 Contracts for C++

C++ 契約プログラミング機能の提案。

以前の記事を参照

R11での変更は

  • 仮想関数の事後条件で非参照引数を使用する場合、オーバーライドする全ての関数の全ての宣言で対応する引数がconstである必要がある、という要件を追加
    • P3484R2の内容
  • 事後条件で使用される非参照引数は、その型が依存型である(テンプレートパラメータによって指定されている)場合は明示的にconstが必要、という要件を追加
    • P3489R0の内容
  • contract_violationクラスに.is_terminating().evaluation_exception()を追加
    • P3227R0の内容
  • evaluation_semantic列挙型にquick-enforceignoreに対応する値を追加
    • P3227R0の内容
  • “Mixed Mode”サブセクションを追加
  • enforcing semanticという用語をterminating semanticに変更
    • P3227R0の内容
  • レジスタ経由で渡されるオブジェクトとレジスタ経由で返されるオブジェクトの意図された動作を明確化
    • P3487R0の内容
  • 非テンプレート関数では、事後条件の結果名は依存型ではなく遅延解析されることを明確化
    • P3483R1の内容
  • 事後条件の結果オブジェクトのトリビアルコピーは、事後条件アサーションの評価と連続して実行されることを明確化
    • P3483R1の内容
  • 再宣言における契約注釈内ラムダ式のセマンティクスを明確化
    • P3483R1の内容
  • 細かな説明とコード例を追加

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

  • コンストラクタの事前条件とデストラクタの事後条件において、非静的データメンバの使用と非静的メンバ関数の呼び出しに関する制限を追加
  • 契約違反による終了について、単なる「実装定義の終了」ではなく具体的なオプションの集合として定義
  • 関数の契約注釈内にラムダ式が含まれる場合、同じ翻訳単位内でその契約注釈を再宣言することを禁止する
  • 事後条件内から使用される非参照引数に対して、明示的なconstの要件を削除
    • 型に対するconst必須の要件ではない
  • 空白演算で区切られた連続した契約注釈の概念を削除
  • ライブラリ機能テストマクロ__cpp_lib_contractsを追加
  • CWG初期レビューからの文言変更を適用

などです。

P2996R8 Reflection for C++26

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

以前の記事を参照

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

  • P3381R0のマージに伴い、リフレクション演算子^から^^に変更
  • (u8)operator_symbol_of(u8)symbol_ofに変更
  • 一部の演算子をリネーム
  • define_classdefine_aggregateに変更
  • define_static_array, define_static_string, and reflect_invokeを削除
  • sizeof(std::meta::info) == sizeof(void*)であることを明確化
  • data_member_options_tdata_member_optionsに変更
  • data_member_optionsname_typeはstructuralではないconsteval型であることを明確化
  • std::meta内の全ての型がアドレス指定可能であることを明確化
  • member_offsetsmember_offsetに変更し、member_offsetのメンバをsize_tからptrdiff_tに変更(将来的に負のオフセットで使用できるように)
  • 型特性の名前を、type_~からよりカスタマイズされた命名スキームに変更
  • consteval-only型と全てのスプライサーのコアとなる文言を書き直し
  • reflect_valueシグネチャTではなくconst T&を取るように変更
  • 注入された宣言に関する制限事項を説明する非公式セクションを追加
  • is_trivial_typeは対応する型特性が非推奨となったため削除

などです。

P3008R3 Atomic floating-point min/max

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

以前の記事を参照

このリビジョンでの変更はLWGレビューに伴う修正です。

P3016R5 Resolve inconsistencies in begin/end for valarray and braced initializer lists

std::valarrayと初期化子リストに対してstd::beginstd::cbeginを呼んだ場合の他のコンテナ等との一貫しない振る舞いを修正する提案。

以前の記事を参照

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

  • LWG3624の解決に<typeindex>を追加
  • LWG Issueに関する解決案を提案する文言に含める

などです。

P3019R11 Vocabulary Types for Composite Class Design

動的メモリ領域に構築されたオブジェクトを扱うためのクラス型の提案。

以前の記事を参照

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

  • indirectのハッシュ仕様から、不要なremove_constを削除
  • indirectpolymorphicの単一引数コンストラクタ、及びindirectの完全転送代入に対して、テンプレートパラメータのデフォルト引数を追加
  • indirectのムーブ代入、ムーブコンストラクタ、アロケータ引数付きムーブコンストラクタにおいて、ムーブされた後のindirectがvaluelessであることを示す事後条件を追加
    • SBOが可能なpolymorphic型には適用されない
  • indirect<T>polymorphic<T>の両方において、Tin_place_tまたはin_place_type_tの特殊化にならないようにする
  • in_place_tin_place_type_tのコンストラクタをまとめる
  • 様々な要件の簡素化のため、UUremove_cvref_t<U>で定義
  • polymorphicの要件では、is_base_of_vではなくderived_fromを使用する
  • in_place_type_t<U>をうけるpolymorphicのコンストラクタに、is_same_v<remove_cvref_t<U>, U>の制約を追加
  • 最初にis_same_v制約をチェックするようにする

などです。

P3037R4 constexpr std::shared_ptr

std::shared_ptrを定数式でも使える様にする提案。

以前の記事を参照

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

  • P3068R4に基づいて、std::bad_weak_ptrcosntexprを追加
  • モチベーションにおけるClangOzの参照を更新

などです。

P3045R4 Quantities and units library

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

以前の記事を参照

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

  • 「Quantity arithmetics」章を更新し、quantity compound assignmentを改善
  • SG16のフィードバックにより、𝜋をπに変更
  • quantity_like_traits, quantity_point_like_traits, QuantityLike, QuantityPointLikeリファクタリングし、タグ型をラップする代わりにexplicit_import, explicit_exportフラグを使用する
  • Unicode characters and their portable replacements」章を追加
  • 「Framework-only class templates」章を追加
  • 「Special values of a quantity」章を追加
  • RepresentationOfコンセプトをリファクタリング
  • 長さの種類の階層において、"position vector"を"displacement"の下に移動

などです。

P3050R3 Fix C++26 by optimizing linalg::conjugated for noncomplex value types

std::linalg::conjugated()を非複素数型に対してアクセサの変更をしないようにする提案。

以前の記事を参照

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

  • 変更点をわかりやすくするために、修正と追加を赤と緑の文字色に変更
  • WDとの整合性を保つために文言を更新
  • フィードバックの適用による変更
    • “whose type T is expression-equivalent to remove_cvref_t” を “whose type is remove_cvref_t<ElementType>.”に変更
    • 削除されたconjの宣言の戻り値型が(T)であることを確認
    • 2.2と2.3を“otherwise, if is_same_v<A, Accessor> is true, a”に統合
      • 意味は変わらないが、1.2と1.3の条件との重複を回避できる
  • LWGレビューに伴う修正
    • diffのフォーマットの改善
    • ADL-findableにおけるStandard-eseの使用を、“not valid,”ではなく“valid”に置き換え、既存のconj-if-neededとの文言の一貫性を保つ

などです。

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

P3051R3 WITHDRAWN: Structured Response Files

ツールが他のツールにコマンドラインオプションをファイルで引き渡す方法についての提案。

以前の記事を参照

このリビジョンでの変更は、撤回を通知するための様です。

P3068R5 Allowing exception throwing in constant-evaluation

P3068R6 Allowing exception throwing in constant-evaluation

定数式においてthrow式による例外送出およびtry-catchによる例外処理を許可する提案。

以前の記事を参照

R5での変更は

  • 文言の注記部分を追加の変更としてマーク
  • 例外の生存期間と暗黙のコピーに関する文言をexpr.const p5へ移動
  • "shall be" => "is"への変更

このリビジョンでの変更は、ライブラリ機能テストマクロを配置するヘッダに関するコメントの追加、などです。

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

P3074R5 trivial unions (was std::uninitialized)

定数式において、要素の遅延初期化のために共用体を用いるコードを動作するようにする提案。

以前の記事を参照

このリビジョンでの変更は、共用体のデストラクタが削除される時のルールが変更されたことです。

union U1 {
    std::string s = "this";
};

union U2 {
    U2() : s("or that") { }
    std::string s;
};

union U3 {
  std::string s;
  U3* next = nullptr;
};

union U4 {
  std::string s;
  int i;
};

R4までのルール(およびCWGの見解)では、U1, U2, U3のデストラクタは削除され、U4だけがトリビアルなデストラクタを持ちます。これは厳しすぎるとして、共用体の(暗黙の)デフォルトデストラクタ削除のルールを「ユーザー定義のコンストラクタが存在するか、デフォルトメンバ初期化子を持つ共用体メンバ型のデストラクタがアクセスできない・削除されている・トリビアルではない、のいずれかの場合」とします。

これによって、上記の例ではU1, U2のデストラクタは削除されるものの、U3, U4トリビアルなデストラクタを持つようになります。

P3096R4 Function Parameter Reflection in Reflection for C++26

P3096R5 Function Parameter Reflection in Reflection for C++26

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

以前の記事を参照

R4での変更は

  • problem statementの改善
  • parameter_variable()の追加
  • type_ofの根拠の明確化

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

  • parameter_variable()variable_of()に変更
  • 使用する単語をP2996R7に倣う
  • 投票結果の追加

などです。

variable_of()は関数定義における引数の正確なリフレクションを取得するためのメタ関数です。

int fun(int a, int b);

int fun(int const c, int b) {
  // parameters_of()だと宣言の引数リフレクションを取得する
  static_assert(!is_const(type_of(parameters_of(^^fun)[0])));
  static_assert(parameters_of(^^fun)[0] != ^^c);

  // parameters_of(^^fun)[0] はfun()の第一引数のリフレクションを取得している

  // variable_of()を使用して、定義の引数リフレクションを取得する
  static_assert(variable_of(parameters_of(^^fun)[0]) == ^^c); // 定義の仮引数名のリフレクションと一致
  static_assert(is_const(variable_of(parameters_of(^^fun)[0])));  // 第一引数はconstである
  static_assert(is_const_v<[: variable_of(parameters_of(^^fun)[0]) :]); // スプライスバックすると型になる?
}

これは関数定義内で引数型のスプライシングを可能にするためのものとのことです。

P3098R1 Contracts for C++: Postcondition captures

契約プログラミング機能の事後条件構文において、値のキャプチャができるようにする提案。

以前の記事を参照

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

  • P2900R11にリベース
  • name lookup、side effects、elision and duplication、interaction between postcondition captures and the function body、lifetime extension of temporaries、failure to evaluate a postcondition capture に関する議論を追加
  • 事後条件キャプチャの初期化子におけるconst化のルールを明確化
    • キャプチャしたエンティティはconst化されない
    • キャプチャの初期化式における外部エンティティはconst化されたものになる
  • 提案文書の理論的な流れを整理するために、サブセクションを再編成

などです。

P3111R1 Atomic Reduction Operations

P3111R2 Atomic Reduction Operations

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

以前の記事を参照

R1での変更は

  • SG6とSG1のフィードバックを適用し、未解決の問題をすべて解消
  • それに伴い、提案の内容を適宜修正
  • SG6
    • 一般化リダクションをデフォルトとし、それを含まないバージョンを提供しないことで合意
    • 非結合的な浮動小数点数アトミック・リダクション演算の規定のためにGENERALIZED_SUMを使用しない
      • これは、2つの値に対しては意味をなさないため
      • 代わりに、並べ替えを不適合とするC標準のAnnex Fから免除されるstd::non_associative_addを追加して使用する
  • SG1
    • リダクションシーケンスに関する文言を追加
    • compare_store演算を追加
    • 浮動小数点アトミック演算のfenverrnoを処理するように更新
    • 一般化された浮動小数点数アトミック・リダクション演算をデフォルトとして、フォールバックを提供しない(fetch_<op>以外
    • シーケンス無し演算をサポート

このリビジョンでの変更は、リダクションシーケンスの文言を単純化したことです。

以前の提案ではアトミックリダクション操作add()に対して一般化アトミックリダクション操作add_generalized()のようになっていたところが、一般化アトミックリダクション操作だけをサポートするようになり、関数名がreduce_add()のようにreduce_~になりました。つまりは、fetch_{OP}に対してreduce_{OP}メンバ関数が追加されることになります。

また、R1で追加されたcompare_store()は、compare_exchange_~()に対応するアトミックリダクション操作です。通常のcompare_exchange_~(expected, desired)expectedに値をストアするのに対して、しないバージョンになります。

同様に新たに追加されたリダクションシーケンス(reduction sequence)という概念は、整数で許可されているのと同等のtree reduction(数学的な仮定に基づいて計算順序を変更すること、だと思われる)を浮動小数点数の(一般化)アトミックリダクション操作において許可するための巧妙な文言の様です。

P3117R1 Extending Conditionally Borrowed

呼び出し可能なものを受け取るタイプのview型を条件付きでborrowed_rangeになる用ようにする提案。

以前の記事を参照

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

  • transform_view以外のビューに対する変更提案を削除
  • ABI互換性を最大化するために、変更提案の戦略を完全に変更する

などです。

P3125R2 constexpr pointer tagging

タグ付きポインタをサポートするためのライブラリ機能の提案。

以前の記事を参照

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

  • スキーマと重いインターフェースを削除することで簡素化
  • SG1で要求された大幅な簡素化を実行
    • pointer_tag_pairsizeof(pointer_tag_pair) == sizeof(T*)である必要がある

このリビジョンでは、以前のtagged_pointerpointer_tag_pairというクラスに置き換わっています。

template <typename Pointee, typename TagType, unsigned Bits>
class pointer_tag_pair {
public:
    using clean_pointer = Pointee *;
    using dirty_pointer = void *;
    using tag_type = TagType;
    
private:
    /* unspecified */ _pointer{nullptr}; // required to have size same as sizeof(Pointee *)

public:
    // constructors
    pointer_tag_pair() = default;
    constexpr pointer_tag_pair(nullptr_t) noexcept;
    pointer_tag_pair(const pointer_tag_pair &) = default;
    pointer_tag_pair(pointer_tag_pair &&) = default;
    
    // to construct from already tagged pointer from external facility
    pointer_tag_pair(std::already_tagged_t, dirty_pointer ptr) noexcept; // no constexpr
    
    // to store and tag pointer (only if eligible)
    constexpr pointer_tag_pair(clean_pointer ptr, tag_type tag) noexcept;
    
    // to store and tag pointer for over-aligned pointers which wouldn't be eligible otherwise
    template <size_t Alignment> constexpr pointer_tag_pair(std::overaligned_pointer_t<Alignment>, clean_pointer ptr, tag_type tag) noexcept;
    
    // destructor
    ~pointer_tag_pair() = default;
    
    // accessors
    dirty_pointer unsafe_tagged_pointer() const noexcept; // no constexpr
    constexpr clean_pointer pointer() const noexcept;
    constexpr tag_type tag() const noexcept;
    
    // support for tuple protocol to access [pointer(), tag()]
    template <size_t I> friend constexpr decltype(auto) get(pointer_tag_pair _pair) noexcept; 
    
    // swap
    friend constexpr void swap(pointer_tag_pair & lhs, pointer_tag_pair & rhs) noexcept;
    
    // comparing {pointer(), tag()} <=> {pointer(), tag()} for consistency
    friend constexpr auto operator<=>(pointer_tag_pair lhs, pointer_tag_pair rhs) noexcept {
        // comparison equivalent to (but not actually needing tuple)
        return std::tuple{lhs.pointer(), lhs.tag()} <=> std::tuple{rhs.pointer(), rhs.tag()};
    }
    friend bool operator==(pointer_tag_pair, pointer_tag_pair) = default;
};

その値をどのように保持するかは規定されていないものの、pointer_tag_pairはポインタの空きビットに格納する情報と格納先のポインタのペアとなるような型です。pointer_tag_pair<Pointee, TagType, Bits>はそれぞれ、ポインタ型・埋め込む情報の型、使用するビット数、を指定します。

pointer_tag_pairのコンストラクタでオリジナルのポインタと埋め込むTagTypeの値を指定して構築し、.pointer()メンバ関数から埋め込んだ情報を取り除いたポインタ値を取得し、.tag()から埋め込まれているTagType値を取得します。

提案文書より、オブジェクトへの単なる参照(所有権なし)か所有権有りのポインタ、のどちらかを保持する統一ポインタ型maybe_owning_ptrの例

template <typename T>
class maybe_owning_ptr {
  enum class ownership {
    reference,
    owning,
  };
  
  std::pointer_tag_pair<T, ownership, 1> _ptr;
public:
  constexpr maybe_owning_ptr(T* && pointer) noexcept: _ptr{pointer, ownership::owning} { }
  constexpr maybe_owning_ptr(T & ref) noexcept: _ptr{&ref, ownership::reference} { }
  
  constexpr decltype(auto) operator*() const noexcept {
    return *_ptr.pointer();
  }
  
  constexpr T * operator->() const noexcept {
    return _ptr.pointer();
  }
  
  constexpr ~maybe_owning_ptr() noexcept {
    if (_ptr.tag() == ownership::owning) {
      delete _ptr.pointer();
    }
  }
};

static_assert(sizeof(maybe_owning_ptr<int>) == sizeof(int *));

P3136R1 Retiring niebloids

Rangeアルゴリズム群を単なる関数オブジェクトとなるように規定しなおす提案。

以前の記事を参照

このリビジョンでの変更は、LWGレビューからのフィードバックを適用したことです。

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

P3138R4 views::cache_latest

P3138R5 views::cache_latest

入力範囲の現在の要素をキャッシュするRangeアダプタ、views::cache_latestの提案。

以前の記事を参照

R4での変更は、sized_sentinel_forの場合にoperator-を定義するようにしたことです。

このリビジョンでの変更は、LWGからのフィードバックを適用したことです。

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

P3146R2 Clarifying std::variant converting construction

std::variantの変換コンストラクタ/代入演算子において、コンパイル時定数からの縮小変換を許可する提案。

以前の記事を参照

このリビジョンでの変更は、この提案の変更のためには文言の変更が必要となることを協調したことです。

P3149R7 async_scope -- Creating scopes for non-sequential concurrency

P3149R8 async_scope -- Creating scopes for non-sequential concurrency

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

以前の記事を参照

R7での変更は

  • セクション8に提案する文言を追加
  • spawn/spawn_futureにおいて、アロケータ選択アルゴリズムstd::allocator<>の選択にフォールバックしてしまった場合、他に選択肢が無いので環境からアロケータを削除する
  • サンプルコードのtypo修正
  • spawn/spawn_futureがspawn操作のための環境をセットアップする方法を微調整

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

  • ~async_scope_association()async_scope_token.disassociate()に置き換え
    • LEWGレビューで提起された、async_scope_associationの特殊なコピーコンストラクタの非正規性の懸念に対処するため

などです。

P3152R1 Add missing constructors and assignment for indirect and polymorphic

P3019で提案されているindirectpolymorphicに欠けているコンストラクタと代入演算子を追加する提案。

以前の記事を参照

このリビジョンでの変更はよくわかりませんが、コンストラクタ/代入演算子テンプレートのテンプレートパラメータにデフォルト引数が追加されていたり、制約が調整されていたりするようです。

この提案はすでにP3019にマージされているようです。

P3179R4 C++ parallel range algorithms

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

以前の記事を参照

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

  • 出力に範囲を使用する設計に戻す
  • 全ての範囲にsizedであることを要求するように戻す
  • サポートされている実行ポリシーを明確化する
  • 新しいAPIstd::rangesアルゴリズム関数オブジェクトを拡張するものであることを明確化
  • 機能テストマクロを追加
  • 実装経験と実装可能性に関する考察を追加
  • シグネチャの文言に関するバグを修正
  • SG1/SG9からのフィードバックを適用

などです。

P3185R0 A proposed direction for C++ Standard Networking based on IETF TAPS

C++のNetworkライブラリの基礎的な設計の指針として、IETFのTAPSを基礎とする提案。

IETFのTAPS(Transport Services Application Programming Interface)とは、いわゆるソケット通信をはじめとするトランスポートプロトコルAPIを再定義するもので、ネットワーク通信を必要とするアプリケーションにネットワーク機能を提供するための、特定の言語やプロトコルに依存しない抽象アーキテクチャを定義することを目的としているものです。

この提案ではC++の将来のNetwork標準ライブラリのAPIや設計をこのTAPSと整合するものにする事を提案しています。その理由としては

  • 言語に依存しないAPIを提案しており、なおかつNetworking TSの欠けている機能として挙げられている領域をカバーしている
  • 既存のネットワークAPI(Berkeley Socketsからの派生など)の概念やそれに対応する命名規則に一貫性がないという問題に対処しようとしている
    • 現在のネットワークライブラリでは、同じものを表す概念やAPI名が歴史的経緯や使用するプロトコルによって大きく異なる
  • TAPSアーキテクチャはアプリケーションとトランスポート間のインターフェースを再定義し、アプリケーションとの契約を根本的に変えることなくトランスポート層を進化させることを目指している
  • TAPS勧告は、一般的なユースケースをシンプルかつ一貫性のあるものにしつつ、トランスポート層をよりきめ細かく制御する必要があるアプリケーションに対してはその方法を提供する事を目的としている

などが挙げられています。

Networking TSを含む従来のソケット通信のモデルは次のように説明できます

  • ソケットAPIを用いて接続を作成し、データを転送する
  • ソケットAPIは、TCPおよびUDPプロトコルの実装へのインターフェースを提供する
    • 実装は通常、システムカーネルによって提供される
  • TCP/UDP実装はネットワーク層インターフェースを介してデータを送受信する
  • ソケットは、トランスポート層およびネットワーク層のアドレスに直接バインドされる
    • これらのアドレスは通常、システムが提供するDNSスタブリゾルバによって実行される別の解決手段によって取得される
+-----------------------------------------------------+
|                    Application                      |
+-----------------------------------------------------+
        |                 |                  |
  +------------+     +------------+    +--------------+
  |  DNS stub  |     | Stream API |    | Datagram API |
  |  resolver  |     +------------+    +--------------+
  +------------+          |                  |
                    +---------------------------------+
                    |    TCP                UDP       |
                    |    Kernel Networking Stack      |
                    +---------------------------------+
                                    |
+-----------------------------------------------------+
|               Network Layer Interface               |
+-----------------------------------------------------+

これに対して、TAPSのAPI構造は次のようになっています

+-----------------------------------------------------+
|                    Application                      |
+-----------------------------------------------------+
                          |
+-----------------------------------------------------+
|              Transport Services API                 |
+-----------------------------------------------------+
                          |
+-----------------------------------------------------+
|          Transport Services Implementation          |
|  (Using: DNS, UDP, TCP, SCTP, DCCP, TLS, QUIC, etc) |
+-----------------------------------------------------+
                          |
+-----------------------------------------------------+
|               Network Layer Interface               |
+-----------------------------------------------------+

TAPSのAPIは、接続の作成とデータ転送のためのインターフェースを定義し、複数のインタラクションパターンのインターフェースを定義したものです。そして、このAPIは特定のプロトコルに特化したものではなく、より汎用的に様々なプロトコルをサポートすることができ、初期セットとしてTCP/UDPのみに限定しても将来的にQUICなどのプロトコルにライブラリを拡張可能です。

名前解決を接続の確立とデータ転送と組み合わせていることで、このAPIの実装(それは各実装のQoIとされる)はより柔軟なものになっています。

そして、従来のソケットベースAPIとの重要な違いは、TAPS APIはデフォルトで非同期かつイベント駆動型であることです。アプリケーションへのデータ転送を表現するためにメッセージという抽象的な概念を用いています。また、アプリケーションがネットワークエンドポイント識別子を解決して複数のアドレス・プロトコル・パスを記述して、これらのストリームを同時に扱う方法を記述しています。

提案にはもう少し詳しくTAPS APIの特徴が紹介されています。

この提案ではSG4に対して、Networking TSの大規模再設計を行うべきか、このTAPS APIのようなより現代的な規範に従うか、の選択を迫っています。

P3204R0 Why Contracts?

C++ Contractsに対する疑問の声に応える文書。

P2900R7がSG21からEWGに転送され、より広い委員会メンバに提示されたときに、Herb Sutterさんが(おそらくMSを代表する形で)2つの質問事項を提示したようです。この文書はそれを受けて、それ(+1)に対する回答を行うとともにそれを文書の形で残して公開しておくためのものです。

ここで回答されているのは次の3つの質問です

  1. 契約プログラミングという概念そのもの及びその言語実装は何十年も前から利用可能であるにもかかわらず、あまり主流ではない。にもかかわらず、C++における契約プログラミング機能が成功すると確信する根拠は何か?
  2. C++0xのコンセプトは標準ライブラリに適用してみようとするまでは素晴らしいものに見えていた。Contractsの標準化は標準ライブラリでどのように使用されるかを特定せずに成功するか?
  3. 現在の環境において、委員会の中で多くの安全性向上に関する提案が競合している中で、なぜContractsが優先されるべきか?

それぞれに対する回答を要約すると

  1. C++ Contractsは既に成功しており、この機能は最初から既存および将来の機能との強力な統合によって既存の全ての機能の優れた代替手段を提供することを目指して設計されている
    • P2900のContracts機能は現在マクロベースで広く利用されているアサーション機能の既存の実践に進化型であり、それら既存のアサーション機能は様々な形と場所で実装され広く使用されており、Contractsがすぐに提供する機能や将来的に提供しようとする機能のほとんどはすでに利用されている
    • 重要なのは、Contractsではそれらマクロベースの既存機能の持つ問題点の多くを修正しており、それらのライブラリサイドでは対処できない問題を解消していることによって、既存のアサーション機能のすべてにとって魅力的な移行先となること
    • 特に、P2900の設計原則の1つである「契約注釈の存在及びその評価は、プログラムの正当性を変更するべきではない」という原則によって、契約注釈をプログラムに追加することでプログラムの正当性を検証することができ、なおかつ既存のプログラムに容易に導入可能となる
    • そして長期的な目標として、プログラムの正しさを担保するアノテーションを付与するための強固な基盤を提供し、それによってプログラムの正しさを堅牢に静的解析することを可能にする、というものがある
  2. Yes、なぜなら、C++コミュニティはContractsの様な契約チェックを最新の標準ライブラリ実装を含む既存のライブラリで使用してきた豊富な経験を持っているため
    • さらに、標準ライブラリの仕様はC++20の頃から、契約と契約チェック機能を意識して記述されている
    • ただし、現在の標準ライブラリ実装が持つマクロベースのソリューションによって契約チェックを提供することをすぐにやめる(事を促す)べきではなく、ライブラリがコア言語に移行する準備ができ、その意思があり、移行できる状態にあることを前提に契約アサーションを標準ライブラリ仕様に盛り込むことは標準というものに対して無責任な行為である
      • 当面の間(おそらくは無期限に)、ライブラリ実装が正確性のチェックにContracts機能を使用するかしないか、あるいはまったくチェックしないかどうかは実装のQoIとしておくべき
  3. Contracts機能は、安全性を脅かす未定義動作の根本的なバグに対処するための基盤的なソリューションである
    • 未定義動作を悪用することで発生するCVEの問題は、ほぼ同語反復的にそのような問題の発生を可能にするソフトウェアバグの結果である
    • 未定義動作の悪用を防ぐことは症状に対処することであって、バグを含むプログラムこそが真の問題であるはず
    • Contracts及び契約プログラミングの概念は、そのようなバグを識別しその発生源を軽減する方法を提供する
    • そして、Contracts機能による契約注釈の導入は、あらゆる品質のコードベースへの導入に当たって可能な限り障害が少なくなるように設計されている。これによって、レガシーコードベースを段階的に修正していくことが可能となる
      • さらに、レガシーコードベースにまず契約注釈を導入していくことで、開発者はそのコードベースの意味を把握できるだけでなく、その動作を明文化し、期待通りに動作しなかった場合にそれを継続的に補足することができる
    • したがって、契約チェックは最新のコンパイラコンパイル可能なあらゆるC++プログラムで利用可能な正確性向上への入り口となる

より詳細な回答は文書を参照してください。もっと良いことが書いてあります。

P3222R1 Fix C++26 by adding transposed special cases for P2642 layouts

std::linalg::transposedlayout_left_padded/layout_right_paddedのサポートを追加する提案。

以前の記事を参照

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

  • 変更を強調するために変更点を赤と緑の文字色に変更
  • WDの語順との一貫性を保つための修正
  • 第4段落の一貫性と簡潔性向上のために、a.mapping().stride(1)a.stride(0)の代わりにa.stride(1)a.stride(0)を使用
  • 追加と削除(された文言)の書式を修正

などです。

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

P3227R1 Fixing the library API for contract violation handling

契約違反ハンドラが呼び出されている状況をより詳細に取得するためのAPIを追加する提案。

以前の記事を参照

このリビジョンでの変更は、マイナーな文言の修正のみです。

この提案はすでにP2900にマージ済みです。

P3230R1 views::unchecked_(take|drop)

より効率的なviews::take/dropである、views::take_exactlyviews::drop_exactlyの提案。

以前の記事を参照

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

などです。

P3232R1 User-defined erroneous behaviour

呼ぶとErroneous behaviourとなるライブラリ関数を追加する提案。

以前の記事を参照

このリビジョンでの変更は、さらにモチベーションを追加したことです。

P3237R2 Matrix Representation of Contract Semantics

契約注釈のセマンティクスについて、特性の行列(表)によってセマンティクスを指定しようとする提案。

以前の記事を参照

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

  • プロパティ(契約セマンティクスを表す行列の列の表現)が2つ以上の状態を持つことができるようにした
  • コード例もそれに応じて更新
  • その他さまざまな修正

などです。

この提案はSG21において指示を得られずリジェクトされています。

P3247R2 Deprecate the notion of trivial types

C++ における「トリビアル型」の概念を非推奨にする提案。

以前の記事を参照

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

  • トリビアルなデフォルトコンストラクタを明確化するために文言を調整
  • 根拠を追加
  • [inplace.vector.overview]の文言を調整

などです。

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

P3261R2 Revisiting const-ification in Contract Assertions

契約注釈中での式の暗黙const化について、代替の設計を探る提案。

以前の記事を参照

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

  • R1のSG21での議論結果を公表
  • 懸念事項の関連性分析に関するプレゼンテーションをセクション2.3に追加
  • EWGへの対応として議論を改訂
  • const化が削除された場合の一貫性を確保するための提案Gを追加
  • contract_assertについて検討すべき既存の別のエスケープハッチを追加

などです。

P3284R2 write_env and unstoppable Sender Adaptors

新しいsenderアルゴリズムの提案。

以前の記事を参照

このリビジョンでの変更は明確ではないですが

  • finallyアダプタの削除
  • schedule_fromアダプタの仕様変更を削除

などのようです。

P3287R2 Exploration of namespaces for std::simd

std::simdに関するAPIを標準ライブラリ中にどのように配置するのかについてを探る提案。

以前の記事を参照

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

  • std::datapar::simdについての議論(名前空間名変更の検討)の追加
    • std::simdstd::datapar::simdエイリアスになる可能性がある

などです。

P3296R3 let_async_scope

提案中のcounting_scopeの問題を修正する提案。

以前の記事を参照

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

  • 許容されるエラー仕様を修正
  • 新しいspawnオーバーロードが想定されていることを明確化

などです。

P3299R3 Range constructors for std::simd

std::simdrangeコンストラクタを追加する提案。

以前の記事を参照

このリビジョンでの変更は、提案がP1928R14にマージされたことを通知することのようです。

P3309R3 constexpr atomic and atomic_ref

std::atomic/stomic_refconstexpr化する提案。

以前の記事を参照

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

  • ライブラリ機能テストマクロの場所を追加
  • atomic<shared_ptr>atomic<weak_ptr>を削除

などです。

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

P3310R5 Solving issues introduced by relaxed template template parameter matching

P0552R0の影響を緩和するための提案。

以前の記事を参照

R3での変更は、明確化と文言の更新などです。

この提案はDRとして扱われるようです。

P3319R2 Add an iota object for simd (and more)

std::simdオブジェクト(SIMDレジスタ)を連番の値で初期化するAPIの提案。

以前の記事を参照

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

  • SG9での投票結果を追加
  • std::simd_iotaの文言を追加

などです。

P3323R1 cv-qualified types in atomic and atomic_ref

std::atomicではCV修飾された型を使用できないこと、およびstd::atomic_refでは逆に使用できることを明確にする提案。

以前の記事を参照

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

  • notify_onenotify_allの制約に「is_const_v<T> is false」を追加
  • 文言の修正

などです。

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

P3325R4 A Utility for Creating Execution Environments

P3325R5 A Utility for Creating Execution Environments

std::exceutionにおけるExecution Environmentsを扱うためのライブラリ機能の提案。

以前の記事を参照

R3での変更は

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

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

P3329R0 Healing the C++ Filter View

views::filterの問題を修正する提案。

Filter view(views::filter/filter_view)はその.begin()の最初の呼び出しがイテレータの先頭位置をキャッシュする事などによって、コンパイル時/実行時に意図しないエラーが起こる問題があります。

この提案で報告されている問題は

  1. views::filterconst参照で受けているとイテレーションできない
    • views::filterはconst-iterableではないため
  2. empty()メンバはステートレスではない
    • view_interface::empty()によって提供されており、begin()が呼び出される
    • empty()の呼び出し有無によってそれ以降のコードの動作が変わりうる
  3. std::println()の呼び出しによって状態が変更される
  4. views::filterが適用された範囲を複数スレッドからイテレーションするとUB
    • begin()の呼び出しがスレッドセーフではないため
  5. views::filter適用とイテレーション開始までに元の範囲を変更すると意図しない動作をする
    • begin()のキャッシュが原因
    • 特に、元の範囲がrandom_access_rangeかどうかでこのキャッシュの動作が異なるため、この問題についても異なる結果をもたらしうる
  6. views::filterイテレーション中に要素を変更するとUB
    • 変更後の要素がフィルタ条件を満たさなくなると、UB
  7. filter_viewオブジェクトを値渡しすると
    • コピーした時にキャッシュが伝播しないため
  8. end()がキャッシュされないこと
    • フィルタ後の最後の要素の位置によってイテレーションパフォーマンスが変化する
    • 手動でイテレータによるfor文を書いたとき、複数回end()が呼び出されうる

などです。

これらの問題は一つ一つは些細なものではありますが、このようにいくつもの問題があることによってそれに遭遇する確率を上げています。最初に書いた時点では問題の無かったコードが、後で変更(empty()チェックの追加やprint()の追加、元のコンテナの変更など)した際に突然遭遇する可能性があります。そのうえ、これらの問題は1番目のものを除いて実行時に現れます。

これらの問題はめったに使用されないようなパターンで問題となるのではなく、基本的なユースケースで遭遇する可能性があり、より問題を深刻化しています。

この提案は、views::filterのこれらの問題を解消しようとするものです。

これらの問題の解決のためにこの提案ではいくつかのオプションを検討したうえで

  • begin()のキャッシュを行わないようにする
  • filter_viewイテレータは最も強くてもforward_iteratorとする

事を提案しています。

これによって、現在のfilter_viewにあるコンパイル時/実行時のエラー及び意図しない動作についても修正され、直観的に動作するようになります。ただし、これによってreverse化のサポートが無くなります。

また、これはC++20に対する破壊的変更でもあります。この提案の解決策が適用されたとすると、次のような影響があります

  • イテレーション前にviews::filterempty()チェックを行うコードは、場合によっては遅くなる可能性がある
    • 計算量は変わらない
  • views::filterの適用後にviews::reverseするようなコードはコンパイルエラーになる

Rangeアダプタはテンプレートエンティティであるため、ABI互換性の破損は問題にならないとしています。

提案より、ユースケースと操作についての問題点の一覧表

ユースケース\事象 constイテレーション 範囲の読み込み イテレーション前の範囲変更 イテレーション中の要素変更 イテレーション中の要素変更(判定結果を変化させる)
一回のイテレーション 💀
empty()呼び出し後にイテレーション 💀 💀
複数回のイテレーション 💀 💀
行き来する 💀 💀
並行イテレーション 💀 💀 💀 💀
1回の逆順イテレーション 💀 💀
2回の逆順イテレーション 💀 💀
  • ✅: well-formed(問題なし)
  • ❌: ill-formed(コンパイルエラー)
  • 💀: UB(実行時の意図しない動作)

この提案の解決が適用されれば、この表は次のようになります

ユースケース\事象 constイテレーション 範囲の読み込み イテレーション前の範囲変更 イテレーション中の要素変更 イテレーション中の要素変更(判定結果を変化させる)
一回のイテレーション
empty()呼び出し後にイテレーション ✅🐢 ✅🐢
複数回のイテレーション ✅🐢 ✅🐢
行き来する 💀
並行イテレーション 💀 💀
1回の逆順イテレーション
2回の逆順イテレーション
  • 🐢: パフォーマンスが現在より若干悪くなる(計算量は変わらない)

このように、views::reverseが使用できなくなるものの、ほとんどのケースで現在の問題を解消することができます。

SG9での投票においては問題の解決の必要性は合意されたものの、そのソリューションについてはまだ方向性が定まっていないようで、破壊的変更も好まれていないようです。

P3335R3 Structured Core Options

P3335R4 WITHDRAWN: Structured Core Options

コンパイラフロントエンドの共通コマンドラインオプション構文の提案。

以前の記事を参照

R3での変更は

  • キーからstdプリフィックスを削除
  • null定義値が1と等しくなるように変更
  • 定義値が1の場合をオプションとする

このリビジョンでの変更は、撤回を通知するための様です。

P3339R1 WITHDRAWN: C++ Ecosystem IS Open License

Ecosystem ISをCC-BYライセンスの下で公開する提案。

このリビジョンでの変更は、撤回を通知するための様です。

P3342R1 Working Draft, Standard for C++ Ecosystem

P3342R2 WITHDRAWN: Working Draft, Standard for C++ Ecosystem

Ecosystem ISのワーキングドラフト。

R1での変更は不明です。

このリビジョンでの変更は、撤回を通知するための様です。

P3355R2 Fix submdspan for C++26

C++26std::submdspanへの修正提案。

以前の記事を参照

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

  • 文言の変更点を明確するために、赤と緑の文字色を使用
  • “unit-stride slice for decltype(*this)”を“unit-stride slice for mapping.”に変更

などです。

この提案は2024年11月の全体会議でC++26に向けて採択されています。

P3367R1 constexpr coroutines

P3367R2 constexpr coroutines

コンパイル時でもコルーチンを動作可能にする提案。

以前の記事を参照

R1での変更、コルーチンの生存期間に関する文言の変更です。

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

  • コルーチンのプロミス型におけるアロケータ関連の機能を省略するようにした
  • 代替実装戦略に関する議論を追加
  • この提案の参考にした様々なグループにおける決定に関する情報を追加

などです。

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

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

以前の記事を参照

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

  • スカラーα引数を持たないrank-1 and rank-k updateオーバーロードを削除
  • “linear algebra value type”をmdspanおよび実行ポリシーのいずれにも制限しない
  • このリビジョンでの変更を説明するセクションを追加

などです。

P3378R1 constexpr exception types

例外型をconstexpr指定する提案。

以前の記事を参照

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

  • <exception>の例外のステータスを更新
  • ライブラリ機能テストマクロに関する注記を追加

などです。

P3380R1 Extending support for class types as non-type template parameters

NTTPとして扱えるクラス型の制限を拡張する提案。

以前の記事を参照

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

  • 正規化に関するセクションを更新し、文字列リテラルについての説明と文字列リテラルの適切な処理の提案を追加
  • カスタマイズポイント名をoperator templateからto_meta_representation()from_meta_representation()に変更
  • 設計をinfoの範囲を返すものから、シリアライズ/デシリアライズ引数を取るものに変更

などです。

SmallStringの例の場合、このリビジョンでは次のようになります

class SmallString {
  char data[32];
  int length;

  // シリアライズ、以前は`operator template()`
  consteval auto to_meta_representation(std::meta::serializer& s) const -> void {
    s.push_range(*this);
  }

  // デシリアライズ、以前はタグ付きコンストラクタ
  static consteval auto from_meta_representation(std::meta::deserializer& ds) -> SmallString
  {
    auto str = SmallString();
    str.length = ds.size();
    std::ranges::fill(str.data, '\0');
    std::ranges::copy(ds.into_range<char>(), str.data);
    return str;
  }
};

P3383R1 mdspan.at()

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

以前の記事を参照

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

  • P2821R5に合わせて文言を更新
  • __cpp_lib_mdspan_atを追加する代わりに、__cpp_lib_mdspanバンプアップする
  • at()freestanding deletedコメントし、ヘッダとクラスのfreestandingコメントを調整

などです。

P3385R2 Attributes reflection

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

以前の記事を参照

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

  • サンプルコードの問題を修正
  • 文言の修正
  • P2996R8へリベース

などです。

P3386R1 Static Analysis of Contracts with P2900

P2900のContracts仕様が現在のままでも静的解析に十分に役立つことを説明した文書。

以前の記事を参照

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

  • 多数の修正と明確化を行った
  • 結論を直接的な表現にした

などです。

P3396R1 std::execution wording fixes

std::executionに関する規格文言の問題修正をまとめた提案。

以前の記事を参照

このリビジョンでの変更はよくわかりません。

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

P3409R1 Enabling more efficient stop-token based cancellation of senders

stop_tokenを用いたsenderのキャンセルをより効率化する提案。

以前の記事を参照

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

  • 提案する文言の更新
  • single_inplace_stop_tokenfinite_inplace_stop_tokenoperator==inplace_stop_tokenとの一貫性を保つためにhidden friendではなくメンバ関数に変更
  • Appendix Bのリファレンス実装に軽微な修正を適用
    • single_inplace_stop_tokenに欠けていたoperator==を追加
    • いくつかの式を簡素化

などです。

P3422R1 Allow main function in named modules

名前付きモジュールでmain()を定義することができるようにする提案。

以前の記事を参照

このリビジョンでの変更は提案する文言の軽微な修正のみのようです。

P3424R0 Define Delete With Throwing Exception Specification

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

operator deleteはユーザーによって明示的にnoexcept(false)等と指定されない限り、デフォルトでnoexceptになります。noexcept(false)と明示的に指定して例外を投げられるようにすることもできるのですが、その場合の振る舞いは未定義動作となります。したがって、operator deleteに例外を送出しうる例外仕様を指定する有効なユースケースは存在していません。

そのため、この提案ではdelete演算子オーバーロードに対する例外を送出しうる例外仕様の指定をill-formedにしようとしています。

この提案の推奨は、例外を送出しうる例外仕様(noexcept(false)等)を禁止し、例外を送出しない例外仕様(noexcept等)は非推奨としておくことです。前者については意味がない使用法ですが、後者については妥当な使用法であるためです。

ただし、GCCの現在の実装にはdestroying operator deleteに対する暗黙のnoexceptを行わないというバグがあるため、これが修正される前にこの提案が適用されると、そのバグによりdestroying operator deleteが全てコンパイルエラーを起こすようになってしまう可能性がある、と報告しています。

P3427R1 Hazard Pointer Synchronous Reclamation

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

以前の記事を参照

このリビジョンでの変更は、SG1での投票結果を追加したことです。

P3428R1 Hazard Pointer Batches

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

以前の記事を参照

このリビジョンでの変更は、SG1での投票結果を追加したことです。

などです。 - P3428 進行状況

P3429R1 Reflection header should minimize standard library dependencies

静的リフレクション機能のライブラリ部分について、他のヘッダへの依存関係を減らす提案。

以前の記事を参照

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

  • P2996R8ヘリベース
  • 不要になった投票推奨を削除
  • 動機を詳しく説明するようにした
  • info_arrayの代替案について議論を追加

などです。

この提案はすでにP2996にマージされています。ただし、おそらくマージされたの提案1(インクルードリストの削除)のみです。

P3430R1 simd issues: explicit, unsequenced, identity-element position, and members of disabled simd

std::simd(P1928)のLWGレビューで見つかったIssueとその解決についてまとめた提案。

以前の記事を参照

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

  • LEWG投票結果を追加
  • 複合代入演算子をhidden friendではなくメンバにする推奨をIssueとして追加
  • Issue4に動機づけの例を追加

などです。

安全性に関わるUBを取り除く提案。

以前の記事を参照

このリビジョンでの変更は分かりやすくするために説明を一部編集したことです。

P3437R1 Proposed principles: Reflect C++, generate C++ (by default)

リフレクションとコード生成機能の設計に関する原則の提案。

以前の記事を参照

このリビジョンでの変更は明確ではないですが

  • タイトルの若干の変更
  • 「Proposal: Reflection and generation should automate that (by default)」セクションの追加
  • "Source of Truth"が構文木などと混同されることを避けるために、"source code is the UI of C++"という比喩を使用
  • by defaultの強調的な使用

などのようです。

P3449R1 constexpr std::generator

std::generatorconstexpr対応する提案。

以前の記事を参照

このリビジョンでの変更は、constexprにおいては代替アロケータのサポートを省略するようにしたことです。

P3466R1 (Re)affirm design principles for future C++ evolution

EWGにおける言語機能の設計原則の提案。

以前の記事を参照

このリビジョンでの変更は明確ではないですが

  • 「General principle: Design defaults, explicit exceptions」セクションの追加
  • 原則4.1「Make features safe by default, with full performance and control always available via opt-out」の追加
  • リンク互換性の原則において、ABI破損が許容される可能性とその際のプロセスに関する記述を追加
  • その他既存の原則の表現の修正
  • 原則4.4(R0の3.3)にcombinatorial decorationsについての注記を追加
  • 原則4.7(R0の3.6)の条件明確化と理由の追記

などのようです。

P3468R0 2024-10 Library Evolution Poll Outcomes

2024年2月に行われたLEWGの投票の結果。

次の13の提案が投票にかけられ、否決されたものはありません。

最後のものを除いて、どれもC++26に向けてLWGに転送されます。

賛否の票数や投票に当たって寄せられたコメントが記載されています。

P3471R1 Standard Library Hardening

P3471R2 Standard Library Hardening

標準ライブラリに堅牢化モードを導入する提案。

以前の記事を参照

R1での変更は

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

  • 完全な文言を追加
  • FAQセクションを追加

などです。

R0では堅牢化モードはContractsとは無関係に導入しようとしていましたが、R1では少なくとも堅牢化条件に違反した後にどうするのかについてはCotnractsのフレームワーク(特に違反ハンドラ周り)を使用するように変更しています。ただし、実装が堅牢化モードのためにContractsを使用する必要はない、としています。

P3477R1 There are exactly 8 bits in a byte

1バイトを8ビットであると規定するようにする提案。

以前の記事を参照

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

  • Abstractの追加
  • Rationaleセクションの拡充
    • 現存している1バイトが8ビットではないアーキテクチャにおけるより具体的なC++サポート状況の追加
    • HW実装者が1バイトが8ビットではないHWでのモダンC++サポートを望む場合、この提案を撤回する用意があることを追記
    • ホスト処理系ではCHAR_BITを8と規定するが、フリースタンディング処理系では現状通りにする、という代替案を追加
  • 提案する文言の修正

などです。

P3480R1 std::simd is a range

P3480R2 std::simd is a range

std::simdrangeにする提案。

以前の記事を参照

R1での変更は

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

  • SG9での投票結果を追加
  • 新しい番兵型を追加する代わりに、default_sentinel_tを使用
  • constbegin()オーバーロードにおいて、ほぼ可変なイテレータを使用する
  • for_eachの例をRangeバージョンに修正

などです。

P3482R0 Proposed API for creating TAPS based networking connections

IETFのTAPSに基づいたネットワークライブラリのAPIの提案。

IETFのTAPSについては少し上のP3185R0を参照。

この提案は、P3185R0に続いてTAPSに基づいた実際のライブラリAPIを提案するものです。ただし、全く新しいものを提案するのではなく、P2762(R2)で提案されているもの、すなわちNetworking TSにstd::execution(sender/receiver)を組み込んだ形のライブラリをベースにして、それに対してTAPSの概念や言葉に基づいた変更を加えています。

TAPSのプロパティ

TAPSではある接続に対する要件を記述するためにプロパティベースのアプローチを採用しています。TAPS実装では、接続の際に指定されたプロパティを満たす1つ以上の接続方法の候補セットを特定し、ユーザーの選択や追加の指定などによってその中から1つを選択して接続に使用します。

そのようなプロパティには次のものがあります

  1. 事前接続(Preconnections)
    • 接続の確立は事前接続から開始される
    • TAPSでは、事前接続オブジェクトを定義する4つのプロパティグループを定義している
      1. ローカルエンドポイント(Local Endpoint)
      2. リモートエンドポイント(Remote Endpoint)
      3. トランスポート(Transport)
      4. セキュリティ(Security)
  2. エンドポイント(Endpoints)
    • エンドポイント接続の始点と終点を定義し、ローカルとリモートに区別される
    • エンドポイントには次のプロパティがある
      1. ホストネーム: 例"nyarlathotep.example.org"
      2. インターフェース: 例"en0"
      3. サービス: 例"https"
      4. マルチキャストグループ: 例"224.0.0.252" or "ff02::114"
      5. ホップ制限: 数値
  3. トランスポート(Transports)
    • トランスポートは基盤となるインフラストラクチャが満たすことが期待される要件の集合によって定義される
    • ほとんどのトランスポートは、次のいずれかの値を取る設定として表現される
      1. Require
      2. Prefer
      3. None
      4. Avoid
      5. Prohibit
    • トランスポートには、要件を表現するために使用できる次のプロパティがある
      1. interface
        • (interface, preference)のようなタプル値の集合
      2. reliability: preference
      3. preserve_msg_boundaries: preference
      4. per_msg_reliability: preference
      5. preserve_order: preference
      6. zero_rtt_msg: preference
      7. multistreaming: preference
      8. full_checksum_send: preference
      9. full_checksum_recv: preference
      10. congestion_control: preference
      11. keep_alive: preference
      12. use_temp_local_address: preference
      13. multipath
        • 列挙型: { disabled, active, passive }
      14. advertises_alt_addr: preference
      15. direction
        • 列挙型: { both, send, recv }
      16. soft_error_notify: preference
      17. active_read_before_send: preference
  4. セキュリティ(Security)
    • トランスポートのセキュリティは、満たさなければならない一連の要件によって定義される
    • 次のプロパティがサポートされている
      1. allowed_security_protocols: プロトコル識別子のシーケンス
      2. server_cert: 証明書のシーケンス
      3. client_cert: 証明書のシーケンス
      4. pinned_server_cert: 証明書のシーケンス
      5. alpn: アプリケーション層のプロトコルネゴシエーション値のシーケンス
      6. supported_group: グループ識別子のシーケンス
      7. ciphersuite: 暗号スイート識別子のシーケンス
      8. signature_algorithm: アルゴリズム識別子のシーケンス
      9. max_cached_session: 整数値
      10. cached_session_lifetime: 期限を示す値
      11. preshared_key: 鍵の実体
      12. trust_verification_handler: senderのシーケンス
      13. challenge_handler: senderのシーケンス

エンドポイント・トランスポート・セキュリティの3つのプロパティセットはまず事前接続を確立するために使用され、その確立後に事前接続は接続(オブジェクト)を作成するために使用されます。TAPSでは、事前接続型に対して接続(Connection型オブジェクト)を作成するための3つの関数を定義します

  1. Initiate() -> Connection: アクティブオープン接続
    • クライアントがサーバーと通信する場合などに使用する
    • client_connection()という名前でも良い
  2. Listen() -> Connection: パッシブオープン接続
    • サーバーがクライアントからの接続を待ち受ける場合などに使用する
    • server_connection()という名前でも良い
  3. Rendezvous() -> Connection: ピアツーピア接続
    • peer_to_peer_connection()という名前でも良い

追加で、事前接続がエンドポイント解決(名前解決)を行えることを規定しており、そのための関数を定義します

  1. Resolve() -> (local_endpoint[], remote_endpoint[])

Framer

TAPSによるユーザー(ライブラリ利用者)とのやり取りは明確にメッセージベースであり、従来のソケット通信ライブラリ(バークレイソケットやNetworking TSなど)とは異なっています。TAPSでは、受信データの1つ以上のチャンクから完全なメッセージ(またはエラー)を生成するFramerという概念を導入しています。Framerは、送信や受信等のイベントを何かしらの単位でフレーム化することを担っており、そのフレーム化のためのルールを表現するものです。

Framerは事前接続から接続を取得する際の(関数の)オプション引数でもあります。Framerはイベント駆動型で、基盤となるトランスポートから、接続の開始・受信/送信データ・接続の終了、に関するイベントを受信します。

Framerでは、送信メッセージのエンコード方法や受信メッセージのデコード方法の定義等を行うことで接続のプロトコルスタックを拡張することができ、ストリーム指向のトランスポートプロトコルを使用する場合でも明確に定義されたメッセージ境界を提供することができます。

例えば、デフォルトであるdefault_framerはバークレイソケットなどと同様に動作して、現在受信しているバイトを返すだけですが、より高レベルのFramerとして基礎のバイトストリームからHTTPのレスポンスをデコードし、ヘッダとメッセージを分離して返すhttp_framerを考えることができます。

メッセージコンテキスト

データの送受信呼び出しにおいては、アプリケーションはメッセージコンテキストを指定することができ、このメッセージコンテキストにはいくつかの役割があります

  • 送信/受信操作に渡されるメッセージコンテキストは、Framerがメッセージデータをどのように扱うかについてを制御するFramer固有のプロパティを伝達する手段として使用される
  • メッセージコンテキストは完了したメッセージ(イベント)を受信するreceiver(のset_valueチャネル)とやり取りするために使用できる
  • 複数の部分的な送信/受信操作を関連付けるために使用できる
  • メッセージコンテキストにはインターフェースやリモートエンドポイントなどに関する情報が含まれているが、Framerはこれを拡張してフレーム化されるメッセージについての追加のメタデータを指定できる
    • 例えばHTTPヘッダなど

TAPSとP2762R2の調和

この提案のライブラリは、ここまでで説明したようなTAPSの概念を優先しつつも、既存の提案であるP2762をベースとしたものになっています。例えば、P2762でがソケット型とそれに対する操作を定義するCPO群を定義していますが、ここで提案しているconnect, listen, rendezvousCPOはソケット型ではなくConnection型を返し、async_send()async_receive()などのCPOはConnection型に対する操作として再定義されます。

TAPSのプロパティや概念は基礎のトランスポートがTCP/IPベースであることを仮定しています。しかし、例えば基礎のトランスポートとしてMPIを使用して、Infiniband等の高性能ファブリックを使用する、といったニーズがある他、Asioにはシリアル通信やプロセス間通信を基礎トランスポートとしたソケットのサポート等があります。

プロセス間通信の場合エンドポイントはホスト名を持たず、セキュリティプロパティも無関係です。同様にトランスポートのプロパティもオプションになる場合もあれば、エンドポイントやトランスポートの種類などにより暗黙的に決定される場合もあります。

TAPSでは事前接続型はそのプロパティに応じて3つの異なる種類の接続形態(アクティブオープン、パッシブ(リッスン)、ランデブー(P2P))を想定していますが、TCP/IPベース以外のトランスポートをサポートしようとする場合、常にこの3つの接続形態が利用可能であると仮定することはできなくなります。

そこで、この提案ではこの3つの接続形態(及び基礎となるトランスポート種別)に対応する接続型(Connection型)を共通ではなく異なるものとして、接続型は基礎となるトランスポートのサポートする操作のみをそのインターフェースとして公開し利用可能にするようにします(TAPSでは接続型は単一の型であり、異なるトランスポートをサポートするための最大公約数的なインターフェースを公開するものとされている)。そして、事前接続型に応じた3つの接続操作はCPOとしてカスタマイズポイントになることで、ベンダー/ユーザーはTCP/IPベース以外のトランスポートをサポートできるようになります。

標準ライブラリとしては、TAPSで示されているエンドポイント・トランスポート・セキュリティのプロパティに準拠したデフォルトのトランスポート実装を提供し、std::net::ip名前空間に配置します。

そのような事前接続のプロパティセットを実装がクエリするための方法として、P3325で提案されている実行環境用のプロパティクエリと定義の仕組みを応用することを提案しています。これによって、事前接続型がP3325の方法によって各種プロパティを取得できるようになってさえいれば、ユーザー側で定義した任意の型(=任意のプロパティセット)を事前接続型として使用できるようになります。標準ライブラリとしてはデフォルトの事前接続型を提供します。

ここでも、3つの接続操作(connect, listen, rendezvous)がCPOであることによって、事前接続型に応じた接続型の選択がカスタマイズ可能になります。

TAPSでは信頼検証とチャレンジ処理のためのセキュリティハンドラをコールバック操作として記述していますが、これは本質的に非同期操作であり、sender/receiverの語彙によって表現でき、またそうすべきです。ただし、現在のsenderではこのための能力が不足しているため、ここではsequence_senderというタイプの新しいsenderを提案しています。

sequence_sendersenderに対してset_next_value()操作を追加したもので、set_next_value()操作は(io_contextによって)値を受信した際に実行する処理をスケジューリングできるsenderを返します。

例えばセキュリティコールバックの場合、接続型からそのためのsequence_senderを取得し、set_next_value()によってセキュリティコールバック(信頼検証やIDチャレンジ)に必要な追加の値を受け取って、その操作を表すsenderを返します。このsenderに対してreceiverを接続することでセキュリティコールバックの結果を取得します。

Framerは受信したバイト列をデコードしてメッセージ型に変換するだけではなく強く片付けされたヘッダを導入することもでき、Framerに対してこのようなヘッダセットやその型に関する情報を伝達するのにもP3325で提案されているruntime_envの仕組みを使用することを提案しています。標準ライブラリとしては、受信したデータの生のバイト列をConnection型固有のメッセージコンテキストともに返すdefault_framerを提供します。

接続のネゴシエーションと確立、フレーミング、メッセージ送受信はすべて、TAPSにおいても本質的に非同期操作です。したがって、これらの機能をC++ライブラリにエンコードする際には、sender/receiverの語彙を使用することができます。ただし、足りない部分があるのでsequence_senderという拡張を提案しています(sequence_senderに関しては追加の提案でさらに詳細に説明される予定)。

事前接続におけるリモートエンドポイントの解決(名前解決)も非同期操作であるため、それを行うresolve()操作もsenderを返すようにすることを提案しています。これにより、問い合わせのタイムアウトやキャンセル、中断などを自然にサポートできるようになります。

Connection型を構築するプロセスは通常このリモートエンドポイントの名前解決に依存するため、resolve()が非同期的でsenderを返す場合、Connectionを作成するエントリポイントも同様にsenderを返すようにします

Connectionはまた、受信/送信操作に基づくメッセージの配信に関するイベントに加えて、接続そのもののライフサイクルに関するイベントを配信するsenderでもあります。このライフサイクルにはたとえば、ソフトエラー、パス変更、Closed、Abort、CloseGroup、AbortGroup、ConnectionError等があります。

P2762の非同期操作のほとんどは引き続き利用可能ですが、これらの事を踏まえて一部が修正されます。特に

  • async_accept()は、提案されているasync_listen()に統合される
  • async_connect()は、提案されているasync_initiate()に統合される
  • async_resolve_address()async_resolve_name()は、async_resolve()操作に統合される

P2762のデータ送信操作は次のように変更されます

  • async_send()はメッセージ全体・ファイル全体などを送信するためのカスタマイズポイントとなる
    • 基礎となるトランスポートにメッセージをエンコードするための方法はFramerに移譲される
    • async_send()はメッセージコンテキストを受け入れるようになる
  • async_send()message_flags引数は削除され、トランスポートによるメッセージ配信を指示するプロパティはすべてメッセージコンテキストの一部になる
  • async_send()の戻り値型はsend-senderとなる
    • send-senderはメッセージの送信中に発生した可能性のあるエラーに加えて、メッセージがつつがなく送信されたことや送信前にタイムアウトしたことなどを表す値を返す場合がある
  • ユーザー視点からはストリームトランスポートへのデータ送信とデータグラム指向トランスポートへのメッセージ送信に違いは無いため、async_send_to()は削除
  • TAPSに則って部分送信操作をサポートしており、このような部分送信操作はメッセージコンテキストオブジェクトによって紐づけられる
  • 部分送信操作は、その個別の部分メッセージの送信によって生じた結果を受信するsend-senderを返す

同様に、P2762のデータ受信操作も一部が次のように変更されます

  • async_receive()はメッセージ全体・ファイル全体などを送信するためのカスタマイズポイントとなる
    • メッセージの定義(バイト列からの1メッセージの抽出方法)はFramerに移譲される
    • async_receive()はメッセージコンテキストを受け入れるようになる
  • ユーザー視点からは、ストリームトランスポートへのデータ受信とデータグラム指向トランスポートへのメッセージ受信に違いは無いため、async_receive_from()は削除
  • TAPSではasync_read_some()に似た部分受信操作を許可している
    • 同じメッセージコンテキストオブジェクトを指定する部分受信操作は、同じ論理受信操作の一部となる
    • ここでは、async_receive()オーバーロードとして最大読み取り長を含めるか、async_receive_some()操作を導入すること
  • async_receive()の戻り値型はreceive-senderを返す
  • async_receive()によって最終的に送信されるset_value()呼び出しには、操作開始時に提供されたメッセージコンテキストとメッセージの全体または部分的メッセージが含まれる
  • async_receive()std::executionの他の非同期操作と同様に、stop_tokenを用いてキャンセル可能

この提案の現在のリビジョンでの変更はまだ作業途中であり、将来のリビジョンで更なる作業が予定されています。

P3483R0 Contracts for C++: Pre-Wroclaw technical clarifications

P3483R1 Contracts for C++: Pre-Wroclaw technical clarifications

P2900R10のコーナーケースについて明確化する提案。

C++26に向けたContracts MVPの提案(P2900R10)は既にGCCとClangにおいて先行して実装されており(P3460R0で報告されています)、限定的ながら実装と展開の経験が得られています。この提案はその際に判明した仕様のコーナーケースについて説明し、改善を図るものです。

報告されているのは次のものです

  1. 戻り値型推論を利用している関数の事後条件での結果オブジェクト名は遅延解析するべき
  2. 事後条件内での結果オブジェクトのトリビアルコピーは事後条件評価と逐次的に行う
  3. 事後条件内でodr-usedされる引数名の場合、そのconstは依存型の一部とすることができる
    • P3489R0に分離
  4. 再宣言された事前・事後条件内でラムダ式が出現できる

この提案ではこのうちの1、2と4について現在の仕様を改善する文言を提案しています。

1. 戻り値型推論を利用している関数の事後条件での結果オブジェクト名は遅延解析するべき

戻り値型推論を利用している(autodecltype(auto)等を戻り値型に指定している)場合、事後条件は定義である宣言に指定されていなければならないと規定されています(したがって定義以前に宣言が存在できない)。これは事後条件のパースを完了するには戻り値型が分かっている必要があるためです。この規定に関して、事後条件に<が含まれている場合の実装方法と振る舞いに関する曖昧さがあります。

例えば次のようなコードにおいて

struct A {
  template <int N>
  bool f() const;
};

auto h()
  post (v: v.f<6>())
{
  return A{};
}

h()の事後条件内で現れる<トークンは、<演算子として解釈されるべきかテンプレートパラメータリストの開始山かっことして解釈されるべきかは、h()の戻り値型(vの型)が分からないと決定できません。この実装方法に関しては2つの選択肢があります

  1. h()のような関数における事後条件内では、戻り値型は遅延解析される
    • 事後条件の述語式は関数本体がパースされた後にパースされる
  2. h()のような関数における事後条件内では、戻り値名は依存名として扱われる
    • 上記例の場合、templateキーワードが必要となる(このままだとill-formed)

P3460R0ではClang実装者の意見として1を採用すべきとされています。この提案でも同様に1のソリューションを採用することを提案しています。これはClangおよびGCCの実装にてすでに実装されている方法でもあります。

提案としてはこの1の方法で実装すべきであることを明確化する注釈を追記することを提案しています。

なお、この問題自体はC++20の頃のContractsの仕様において把握され解決が(この1のソリューションと同じ方法)採択済みであり、それはP2900にも受け継がれていましたが、他の変更などによってその文言の意図するところが不明瞭になっていたようです。

2. 事後条件内での結果オブジェクトのトリビアルコピーは事後条件評価と逐次的に行う

関数の戻り値がRVOスロットを持たず、レジスタで直接的に返される場合、このような場合、結果オブジェクトはメモリ上に存在しないため、事後条件内から結果オブジェクトを参照する場合に実装はそのコピーを作成する場合があります。このような場合のコピーはトリビアルでなければならないため、このケースは戻り値型がトリビアルコピー可能な場合にのみ発生します。

この場合の問題は、このようにコピーされて一時的に実体化された戻り値オブジェクトは、複数の事後条件にわたって同じ値を使用する必要があるかどうか、というこtです。

例えば次のようなコードにおいて、全ての契約アサーションがチェックされるセマンティクスで正確に一回だけ評価されるとして

int f()
  post(r : ++const_cast<int&>(r) == 1)
  post(r : ++const_cast<int&>(r) == 2)  // true or false?
{
  return 0;
}

2つめの事後条件の評価は、最初のアサーションによって行われた変更を認識するかしないか(認識するとしたらtrueに、しないとしたらfalseになる)、という問題です。

P2900R10の現在の仕様では、戻り値型がトリビアルコピー可能ではない場合はrは常に同じオブジェクトを参照する必要があり、戻り値型がトリビアルコピー可能である場合はコンパイラは追加のコピーを作成することができるものの、コピーは事後条件の評価と同時に行う必要がある、とされています。言い換えると、この例の場合にrが参照するオブジェクトがコピーされるにしても、そのことは事後条件の評価結果に影響を与えません。

したがって、この例においてはどちらの事後条件アサーションの評価結果もtrueにならなければなりません。

この提案では、このことを明確化するように注記とサンプルコードを追記することを提案しています。

ただし、上記の例のような契約注釈を行うべきではなく、この場合の仮定も常に保証されるとは限りません。この例はあくまで問題の解説のためのコードです。

4. 再宣言された事前・事後条件内でラムダ式が出現できる

通常、全く同じトークン列であっても、ラムダ式は出現した場所によって異なるエンティティとして扱われます。

int main() {
  auto l1 = []{};
  auto l2 = []{};
  // l1とl2は異なるクロージャ型を持つ
}

この場合の問題は、事前・事後条件式内でラムダ式が使用されている場合に、その契約注釈が複数の宣言にわたって繰り返されているとき、どうなるべきかという点です。

契約注釈を持つ関数の再宣言そのものは許可されていますが、この場合、最初の宣言と全く同じ契約注釈を持つか、契約注釈を一切持たないか、のどちらかのみが許可されています。異なる場所で宣言されているラムダ式が異なるエンティティとなる場合、再宣言にトークンレベルで同一の契約注釈を指定していても、この規定を満たすのかどうか、あるいは満たすべきかどうか、が不透明です。

// f.h
void f() pre([]{ _ = scoped_lock(obj_mtx); return obj.is_valid(); }());

// f.cpp
void f() pre([]{ _ = scoped_lock(obj_mtx); return obj.is_valid(); }()) {
  // implementation
}

この提案では、この場合にのみ、最初の宣言と再宣言の契約注釈内のラムダ式を同一のエンティティとして扱うようにすることを提案しています。これは、inline関数内のラムダ式と同じ扱いとなり、ODR違反となるかのルールについて全く同じ扱いをする必要があります。

この設計意図を明確化するために、文言に例を追加することを提案しています。

この提案は既に、3の報告を除いてP2900に適用済みです。

P3484R0 Postconditions odr-using a parameter modified in an overriding function

P3484R1 Postconditions odr-using a parameter modified in an overriding function

P3484R2 Postconditions odr-using a parameter modified in an overriding function

仮想関数のオーバーライドと事後条件からの引数参照が組み合わさった場合の問題を解消する提案。

P2900R10のContracts MVPでは、事後条件から非参照関数引数を参照する場合、その引数はすべての宣言においてconst指定されていることを要求します。これは、非参照非constの引数は関数本体内で任意に変更される可能性があり、それによって呼び出し側から見た事後条件の意味が暗黙的に変化してしまうことを防止するためです。

また、コルーチンにおいてはconstの有無にかかわらず事後条件からの関数引数を参照することは禁止されています。なぜなら、コルーチンはその初期化時に全ての引数をコルーチンステート内にムーブするため、事後条件から参照するものは常にムーブ後状態になってしまうためです。

これらのことによって、コードの読み手及び性的解析機は、事後条件内で使用されている関数引数がその呼び出し中に変更されることがないことを静的に確認することができるようになります。

しかし、これにはまだ抜け穴があります。例えば次のような仮想関数を定義した基底クラスがあるとき

struct Base {
  virtual Integer f(const Integer i) post (r: r >= i);
};

このクラスを継承した派生クラスにおいては引数のconstをドロップすることが許可されています。そのため、次のようにオーバーライドすることができます

Integer Derived::f(Integer i) {
  i = 0;
  return i;
};

P2900R10での仮想関数における契約条件評価のセマンティクスでは、静的に呼び出される基底クラスの関数Base::f()と動的に決定されるオーバーライドする関数Derived::f()の2つの関数における契約が評価されます。基底クラスの参照から派生クラスの関数を呼び出そうとすると、このような場合に望ましくないことが起こります

void test(Base& b) {
  Integer j = b.f(3); // 契約違反は検出されない
  // jは0が返されており、事後条件(3以上のはず)は成立しない
}

int main() {
  Derived d;
  test(d);
}

あるいは別のケースで、値(非const)で受けている引数をそのままリターンする場合、暗黙ムーブが起こることになりますが、これによって事後条件から使用している引数がムーブ後状態になってしまう場合もあり得ます

struct Base2 {
  virtual std::string g(const std::string p) post(r : r.starts_with(p));
};

class Derived2 : public Base2 {
  std::string f(std::string p) override {
    return p; // 暗黙ムーブ
  }
};

これらはまさに、P2900が通常の関数およびコルーチンにおいて防止している事後条件からの関数引数参照の問題と全く同じことです。通常の関数同様に、仮想関数においてもこれは防止される必要があります。

この提案はこの問題の報告と解決を提案するものです。

まずソリューションとして次の6つを提示しています

  1. 仮想関数の事後条件で非参照引数を使用する場合、それがconstであるかどうかにかかわらず使用を禁止する
    • ただし、その関数あるいはクラスがfinalである場合は除く
  2. 仮想関数の事後条件で非参照引数を使用する場合、その引数はすべてのオーバーライド宣言においてconstでなければならない
  3. 仮想関数の事後条件で非参照引数を使用する場合、その引数はすべてのオーバーライド定義においてconstでなければならない
    • 定義ではない宣言においてはconstを省略可能
  4. 任意の関数の事後条件で非参照引数を使用する場合、その引数はその関数およびそれをオーバーライドする関数の定義においてconstでなければならない
    • 定義ではない宣言においてはconstを省略可能
  5. オーバーライドする関数が非参照引数のconstを削除できるようにする
    • つまり現状維持
  6. オーバーライドする関数が非参照引数のconstを削除できるようにするが、基底クラスの対応する関数でconstで宣言されている非参照引数を変更しようとする場合、未定義動作とする

提案ではこれらのオプションの持つ特性を詳細に検討しています。そのうえで、4と6は実現可能ではないとして、SG21での検討のために1、2、3、5のソリューションを提案しています。

提案より、比較をまとめた表

特性 \ オプション 1 2 3 4 5 6
仮想関数で非参照引数を事後条件から使用することを許可するか
事後条件から使用される非参照引数の変更とそれに伴うバグを防ぐか
仮想関数に事後条件を追加することが、それをオーバーライドする派生クラスを破壊しないか
仮想関数に事後条件を追加することが、それをオーバーライドする派生クラスの利用者を破壊しないか
仮想関数オーバーライドと通常の関数との間に新たな不整合を導入しないか
明確に規定でき、実用的に実装可能か
未定義動作の新たな発生源を導入しないか

オプション4と6は、「明確に規定でき、実用的に実装可能」ではないため、実現可能ではないと判断されました。

なお、オプション1は将来的に互換性を維持しながら他のオプションに移行でき、オプション2は3もしくは5にのみ移行でき、オプション3は5にのみ移行でき、オプション5は他のオプションに移行できません。

この提案は関数事後条件における問題を報告する3セットの提案(P3484・P3487・P3489)の1つであり、これら3つの提案はまとめて扱って一貫した方法で解決されるのが望ましいとしています(残り2つは下の方にあります)。

SG21の投票では、オプション2がコンセンサスを得ており、P2900に導入されたようです。

ただし、オプション2では基底の関数に非参照引数を使用する事後条件を追加しようとすると、それをオーバーライドしているすべての関数の宣言において引数をconstにする必要があります。現在非参照関数引数のconst付加があまり一般的ではないのを考慮すると、もしそれらがライブラリや共有ライブラリの形で所有者が分割されている場合、修正不可能なコンパイルエラーに繋がります。

なお、オプションに関わらず、この提案のそもそもの問題からですが、派生先(オーバーライドする関数)で引数を使用する事後条件を追加する分には特に問題がありません。

P3485R0 Grouping using declarations with braces

using宣言をグループで行えるようにする提案。

名前空間から特定の名前を現在のスコープで利用可能にするためには通常using namespaceよりも個別の名前をusingする方が好まれます。

using std::format;
using std::format_to;
using std::formatter;
using std::chrono::duration;
using std::chrono::time_point;
using std::chrono::duration_cast;

しかし、この方法はかなり冗長であり、あまり見やすくもありません。

一応C++17ではusing宣言のパック展開サポートの副作用としてusingを省略できるようになっています

using std::format, std::format_to, std::formatter;
using std::chrono::duration, std::chrono::time_point, std::chrono::duration_cast;

しかし多少はましとはいえまだ冗長です。一つのusing宣言のグループ内では共通する部分の名前空間を繰り返す必要はなさそうに見えます。例えば次のように書くことができれば、かなり簡潔にusing宣言を記述できるようになります

using std::{format, format_to, formatter};
using std::chrono::{duration, time_point, duration_cast};

この提案は、using宣言においてこのようにグルーピングして記述できるようにしようとするものです。

これは新機能の提案ではなく、純粋に可読性を向上させるための機能拡張の提案です。

提案では、ワイルドカードの使用(using std::chrono::*;)や波かっこのネスト(using std::{formatter, chrono::{duration, time_point}};)についても検討していますが、いずれも不許可としています。

筆者の方はClangをフォークして実装してみたところかなり簡単に実装できたと報告しています。

P3487R0 Postconditions odr-using a parameter that may be passed in registers

関数の引数/戻り値がレジスタ経由で渡される場合に、その事前・事後条件内での扱いについて明確化する提案。

C++では、基本型のような単純な型以外でも、いわゆるトリビアルなクラス型についてはその値をレジスタ経由で関数とやり取りすることが許可されています。その際、レジスタ上にあるオブジェクトにはアドレスが無いため、そのような引数/戻り値を参照するC++のコードは、その(トリビアルな)コピーから構築された一時オブジェクトを参照しているかのように扱うことによって、C++のオブジェクトモデルからの逸脱を回避しています([class.temporary]/3にそのための規定があります)。

現在のC++においては、ほぼこれが起きていることを観測する方法が無いために、この仕様によって問題が引き起こされることはありませんでした。しかし、Contractsの事前・事後条件からこのような引数/戻り値を使用する場合、呼び出し側でのチェックの実装可能性について問題がある場合があります。

P2900R10のContractsでは契約注釈の評価の実装方法として、呼び出し側チェック(caller-side check)と呼び出し先チェック(Callee-side check)の2つの実装方法を許可しています。これは文字通り、ある関数を呼び出した際にその関数でなされている契約注釈の条件式の評価(チェック)を関数を呼び出した側で行う(チェックコードを呼び出し側に展開する)のか、関数の呼び出し先で行う(チェックコードを関数本体内に展開する)のかの違いです。

呼び出し先でのチェックは常に実装可能と想定されているのに対して、呼び出し側でのチェックは場合によっては実装できないことが分かっています。例えば、関数ポインタに格納された関数の契約チェックは、どの関数が呼ばれるかが実行時に決まるため呼び出し側ではチェックできません。また、関数引数の破棄を呼び出し先で行うABI(Windows ABIなど)の場合は事後条件のチェックを呼び出し元で行うことができません。

全てのチェックが呼び出し側で行うことはできずとも、P2900ではなるべく多くのチェックを呼び出し側で行えるようにしようとしています。なぜなら、それによって契約チェックをオフにしてコンパイルされたライブラリにおいて、再コンパイルすることなく利用側で契約チェックを有効化できる、という重要なユースケースがサポートされるほか、仮想関数の契約チェックも呼び出し側チェックと呼び出し先チェックを組み合わせることで実現されるためです。

事前条件

レジスタ経由の引数渡しにおける呼び出し側チェック実装の問題の一つ目はまず、事前条件からそのような関数引数を使用する場合です。

事前条件は多くの場合関数の引数を使用することになりますが、使用しようとする引数がレジスタ経由で渡されている場合、事前条件内で使用されるレジスタ渡しされている引数についてを考慮する規定が無く、[class.temporary]/3の規定からその場合は関数本体内で参照される対応する一時オブジェクトと同じものを参照する必要性が導かれます。これによって、事前条件の呼び出し側でのチェックは実装不可能となっています。

この提案では、これについては単純にそれを考慮した規定(レジスタ渡しされている引数オブジェクトについては、一時コピー前のものか一時コピー後のものかいずれかを使用するようにする)を追加することを提案しています。呼び出し側チェックの場合は関数に渡される前の引数を参照すればよく、呼び出し先チェックでは現状通り一時コピーを参照すれば、どちらのチェックも実装可能です。

事後条件と戻り値の使用

事後条件においては事前条件とは異なり、引数だけでなく戻り値のオブジェクトも使用できます。ただし、この戻り値がレジスタ渡しされる場合に事後条件が一時コピー前後のどちらを参照するかについてはすでに考慮された規定が盛り込まれており、呼び出し側チェックと呼び出し先チェックのどちらの実装も可能になるようになっています。

このことは割と簡単に観測できてはしまいます

class X { /∗ ... ∗/ };

X f(const X* ptr)
  post(r: &r == ptr)  // 👈
{
  return X{};
}

int main() {
  X x = f(&x);
}

Xトリビアルではない場合、f()の事後条件から参照されるのは戻り値のオブジェクトそのものなので、この事後条件は常に満たされます。一方、Xトリビアルレジスタ経由で返される場合、事後条件から参照されるのは戻り値オブジェクトの一時コピーなので、事後条件は失敗する可能性があります。

とはいえ、呼び出し側チェックと呼び出し先チェックのどちらによっても事後条件チェックは実装可能であるため、その観点からは問題ありません(そしておそらく、このようなチェックを行いたいユースケースは無いと判断されているため、このような挙動も問題視されていません)。

事後条件と引数の使用

問題があるのは、事後条件から関数の引数を使用する場合です。

X* ptr;

void f(const X x)
  post (ptr == &x)
{
  ptr = &x;
}

こんな契約条件を書くなという点はさておき、この事後条件はxレジスタ渡しされている場合、現在の規定の下では(事前条件の場合と同様の理由によって)この事後条件は常に満たされます。それはすなわち、事後条件からはこの引数の一時コピーを参照しなければならないということでもあり、それにより呼び出し側でのチェックは実装不可能となります(Windows ABIのようにそもそも実装できないケースはさておき)。

前2つのケースと同様の規定をこの場合に対しても追加することで問題の解消を図ることができるのですが、事後条件の場合引数オブジェクトが参照されるのが関数の呼び出し終了後であるという決定的な違いがあります。これにより、関数本体内で引数オブジェクトが変更され、なおかつそれがレジスタ渡しされている(一時コピーの)オブジェクトに対するものである場合、呼び出し側チェック(一時コピー前のオブジェクトを参照)と呼び出し先チェック(一時コピー後のオブジェクトを参照)の間で観測するオブジェクトの状態(アドレスだけではなく)が異なるということが起こりえます。

事後条件から使用される引数は常にconstである必要があるため、そのような事が起こる場合は関数本体内でconst_cast等の手段でオブジェクトの状態を変化しているということなので、そのような契約を記述することは間違ってはいます。ただし、オブジェクトの状態を変更することと正しさが結びついているような型を考えることができてしまいます。

class RandomInteger {
  mutable bool _computed = false;
  mutable int _value;

public:

  int value() const {
    if (!_computed) {
      _value = rand();  // 遅延計算し、値をキャッシュする
      _computed = true;
    }

    return _value;  // 2回目以降の呼び出しでは、キャッシュされた値を返す
  }

};

このRandomIntegerクラスはトリビアルなコピーコンストラクタとデストラクタを持つため、レジスタ渡しで渡すことができます。

int f(const RandomInteger i)
  post(r: r & i.value() == 0) // ここでのすべてのiが同じオブジェクトを参照しているなら、満たされる
{
  return ~i.value();
}

事後条件内で使用されているiと関数本体内で使用されているiが、同じオブジェクトを参照する場合にのみこの事後条件は常に満たされます。しかし、事後条件の呼び出し側でのチェックを許可するためにレジスタ渡しされている引数オブジェクトの参照先を曖昧(一時コピー前でも後でもどちらでもいい)にしてしまうことによってそのような保証は無くなり、このコードは正しく動作しなくなります。

このコードは現在のC++(及びP2900R10)では、レジスタ渡しされた元のオブジェクトを変更する手段が無いため観測できず、問題になりません。

この提案では、この解決のために8つのオプションを提示しています

  1. 事後条件アサーションをP2900から削除する
  2. 事後条件アサーションから、あらゆる関数引数の使用を禁止する
  3. 事後条件アサーションから、非参照関数引数の使用を禁止する
  4. 事後条件アサーションから、スカラ型ではない非参照関数引数の使用を禁止する
    • 事後条件からは、レジスタ渡しされている引数オブジェクトについては、一時コピー前のものか一時コピー後のものかいずれかを使用するようにする、規定を追加
    • 事後条件の呼び出し側チェック実装を可能にする(条件を満たしていれば)
  5. 事後条件アサーションから、レジスタ渡し可能な条件を満たすスカラ型ではない非参照関数引数の使用を禁止する
    • オプション4と同じ規定を追加して、事後条件の呼び出し側チェック実装を可能にする
    • レジスタ渡しの条件を満たさなければ、事後条件からクラス型引数の使用が可能になる
  6. 事後条件アサーションから、レジスタ渡し可能な条件を満たし1つ以上のmutableサブオブジェクトを持つ型の引数の使用を禁止する
    • オプション4と同じ規定を追加して、事後条件の呼び出し側チェック実装を可能にする
  7. オプション4と同じ規定を追加するが、それ以上の制限を設けない
    • 上記のような問題を許容する
  8. 事後条件アサーションから非参照関数引数が使用される場合、関数本体と同じオブジェクトを使用する
    • 現状維持
    • 呼び出し側での事後条件チェックの実装のためには、一部の実装でABI破壊が必要となる

オプションは厳しいものからの昇順で並んでいます。

各オプションの比較

特徴 \ オプション 1 2 3 4 5 6 7 8
一般的に事後条件を許可する
事後条件からの参照引数の使用を許可する
事後条件からの参照+スカラ型引数の使用を許可する
事後条件からのあらゆる型の非参照引数の使用を許可する
特定の型の特性に基づく、脆弱な区別を回避する
フットガンを許容する
ABI破損無しで呼び出し側チェックを可能にする

これらのオプションには同時に満たすことの出来ない3つの要件が存在します

  1. 事後条件から任意の型の非参照引数を使用できるようにする
  2. mutableサブオブジェクトによるフットガンを回避する
  3. レジスタ経由の受け渡しを削除するABI破損なしで、事後条件の呼び出し側チェックを可能にする

1つ目の要件を放棄するとオプション1~6を選択でき、2つ目の要件を放棄するとオプション7が最適になり、3つ目の要件を放棄するとオプション8が最適になります。

オプション1~3はこの提案の議論についてはオプション4~6と比較すると何の利点もありませんが、P3484とP3489で報告されている問題の一貫した解決の方法としては選択肢たり得る可能性があります。

オプション8を除くオプションは、より数字の大きなオプションに事後的に移行できます(オプション8から他のオプションへは移行できません)。また、オプション4を選択するとコルーチンの事後条件の制約が緩和(スカラ型の非参照引数を使用可能になる)される可能性があります。

この提案では、これらのオプションはどれも検討する価値があるとして、これ以上絞り込まずに判断をSG21に委ねています。

この提案は関数事後条件における問題を報告する3セットの提案(P3484・P3487・P3489)の1つであり、これら3つの提案はまとめて扱って一貫した方法で解決されるのが望ましいとしています(残りは上の方と下の方にあります)。

SG21の投票では、オプション7だけがコンセンサスを得ることができたようです。

P3488R0 Floating-Point Excess Precision

P3488R1 Floating-Point Excess Precision

浮動小数点数の超過精度(excess precision)の扱いについて議論する提案。

浮動小数点数の超過精度とは、実際の計算に使用されている浮動小数点数の精度と幅が、それに対応するコード上で型によって制約されている精度と幅よりも高い精度と幅で保持すること(を許可すること)です。

この提案のきっかけとなったCWG Issue 2752では例えば、次のようなコードにおいて

int main() {
  // float型の定数
  constexpr auto x = 3.14f;

  assert( x == 3.14f ); // can fail?
  static_assert( x == 3.14f ); // can fail?
}

超過精度が許可されるとしたら、これら2つのアサートは失敗する可能性があります。許可する/しない、が定数評価中と実行時で異なる場合、これらのアサートはそれぞれコンパイル時と実行時で異なる振る舞いをするかもしれません。

超過精度を扱うような環境としては例えばx87 FPUが利用できる環境があり、GCC13以降で-m32もしくは-mfpmath=387-std=c++23を指定して上記例のコードをコンパイルすると両方のアサートが失敗します(この場合、FLT_EVAL_METHODが非ゼロとなる)。

C++において、このような浮動小数点数の超過精度が許可されるのか、やその扱いははっきりとしておらず、かなりあいまいになっています。この提案は、超過精度の扱いについて明確することを目指すものです。

この提案では、CWG 1752の解決だけに留まらないより包括的なアプローチを試みており、次の問題の解消(明確化)を検討しています

  • CWG 2752: 浮動小数点数リテラルの値を超過精度で保持することは可能か?
  • ライブラリのもの、特にCから継承されたものがコア言語に制約を追加すべきではない
    • ライブラリのものはあくまで、コア言語の実装上の選択を反映する程度にとどめておく必要がある
  • 浮動小数点数の式は、コンパイル時により高い中間精度(及び幅)を使用できるか?
    • 言い換えると、FLT_EVAL_METHODは実行時評価のみに適用されるか?
  • C++では、より高い中間精度(及び幅)を制約なしに使用できるが、FLT_EVAL_METHODによってこれはdoublelong doubleに制限されている
    • これによって、std::float16_tstd::bfloat16_tstd::float32_tの中間精度で評価することが不可能になっている

このために、提案では次の3つの設計オプションを提示しています

  1. 厳密: 超過精度を許可しない
    • [expr.pre]において、明示的にその型よりも高い精度/幅の使用を禁止する
    • したがって、FLT_EVAL_METHODは常に0になる
  2. C互換: Cと全く同じことを行う
    • [lex.fcon]では、浮動小数点数をより高い精度/幅で表現可能にする
    • 定数式の評価及びコンパイル時の式の評価では、超過精度が使用される場合がある
    • 実行時及びコンパイル時の評価における中間の丸めは、FLT_EVAL_METHODの値が反映される
  3. Cと同じことを行うが、それは実行時に限定される
    • 浮動小数点数リテラルの値は常にその型の精度に丸められる
    • 定数式における浮動小数点数式の評価では、超過精度を使用できない
    • FLT_EVAL_METHODの値は実行時にのみ適用される
    • 定数畳み込みは実行時と同じ動作をする
    • 実行時の浮動小数点数式の評価では、本来の型とは異なる浮動小数点数型のより高い幅と精度を利用することができ、キャスト及び代入時に本来の型の幅と精度に丸める必要があるだけ
      • 中間精度はFLT_EVAL_METHODの値が反映される(とりあえずは
    • [expr.pre]に、評価で超過精度が許可される一方でこれはパフォーマンス上の理由からのみ許可されており、中間精度と幅は浮動小数点数型と一致するのが望ましい、という注釈をつけることを検討する

SG6での投票においては、オプション1に強い支持があった一方でオプション2はあまり支持されず、オプション3には弱い支持がありました。

なお、関連していそうな問題として浮動小数点縮約(Floating-point contraction)がありますが、これはISO/IEC 60559:2020規格に則って超過精度とは別の問題として議論することにしています。現在のC++では、浮動小数点縮約はFLT_EVAL_METHODの値とは独立した最適化としてデフォルトで許可されているようで、ここではこれを変更することを意図していません。

P3489R0 Postconditions odr-using a parameter of dependent type

依存型の引数が事後条件で使用される場合の挙動について明確化する提案。

P2900R10のContracts MVPでは、事後条件から非参照関数引数を参照する場合、その引数はすべての宣言においてconst指定されていることを要求します。これは、非参照非constの引数は関数本体内で任意に変更される可能性があり、それによって呼び出し側から見た事後条件の意味が暗黙的に変化してしまうことを防止するためです。

しかし、関数テンプレートにおいてテンプレートパラメータを型名として宣言されている引数(提案では、この引数のCV修飾含めた型のことを依存型と言っている)については、関数の宣言時点ではその引数がconstであるかどうかはわからず、この引数が事後条件で使用されている場合にどうすべきかがP2900R10の仕様では明確ではありません。

これは次のような単純な関数テンプレートと事後条件で容易に起こりえます

template <typename T>
void f(T t)
  post(t > 0);

この関数テンプレートはconstな型でもそうでない型によってもインスタンス化される可能性があり、型推論によって型が導出される場合もそうでない場合もあります。いずれにせよ、この関数テンプレートを最初にパースする際には引数tconst有無は判明せず、事後条件におけるtの使用をコンパイルエラーとすべきかは分かりません。

より場合分けすると、この関数テンプレートがconstではない型Tによってインスタンス化される場合はコンパイルエラーにすべきであることは明白です。

int main() {
  int i = 1;
  f<int>(i); // error
}

しかし、constな型によってインスタンス化される場合の動作が明確ではありません。

int main() {
  int i = 1;
  f<const int>(i); // OK?
}

この提案はこのケースの規定の明確化を図るものです。

提案ではソリューションとして次の2つのオプションを提示しています

  1. 依存型の引数が事後条件で使用される場合、その引数宣言には明示的なconstを必要とする
  2. 依存型の引数が事後条件で使用される場合、その引数宣言のconst修飾を依存型の一部とすることを許可する
    • すなわち、上記の例のfのような関数テンプレートの宣言の時点でエラーにはしないが、非constな型でインスタンス化される場合はill-formed

2つのオプションのトレードオフは、エラーが報告されるタイミングです。オプション1はかなり早期にエラーが報告される一方で、オプション2はともすればかなり後になってからエラーが報告されます。しかし、オプション1の場合は上記例でf<const int>(i)のようにしたときのように意味的には許可されるべきコードがエラーになってしまうというデメリットもあります。また一方で、オプション2ではテンプレートパラメータのconst性によって関数テンプレートのコンパイル結果が変化するという問題もあります。

オプション1を選択すると、後からオプション2に移行することができますが、オプション2からオプション1へ移行することはできません。

この提案ではどちらのオプションもトレードオフが明確でありどちらにも検討する価値があるとして、決定をSG21に委ねるために両方のオプションを提案しています。

この提案は関数事後条件における問題を報告する3セットの提案(P3484・P3487・P3489)の1つであり、これら3つの提案はまとめて扱って一貫した方法で解決されるのが望ましいとしています(残り2つは上の方にあります)。

SG21の投票ではどちらのオプションもコンセンサスを得たようですが、オプション1の方が賛成票が多かったようです。

P3490R0 Justification for ranges as the output of parallel range algorithms

並行Rangeアルゴリズムの出力引数にイテレータではなくrangeを取るようにする提案。

P3179R2では、Rangeアルゴリズムに対して実行ポリシーをとる並行版を追加しようとしています。そこでは主に次のような設計選択が行われていました

  • 並行Rangeアルゴリズムでは、出力引数を持つ場合出力のためにイテレータではなくrangeを受け取る
  • 並行Rangeアルゴリズムでは、入力範囲としてrandom_access_{iterator,range}を要求する
  • 入力範囲と出力範囲の少なくともどちらか一方は、境界付き(sized_range/sized_sentinel_for)である必要がある

SG1/SG9でのレビューにおいては後ろの2つには合意が取れたものの、最初の出力のためにrangeを受け取る設計については支持を得られませんでした。主に次のような懸念がありました

  • 並行アルゴリズムと非並行アルゴリズムの間で不整合が生じ、それらの間で使用を切り替えようとすると多くの作業が必要になる
  • std::ranges::copy(policy, vec1, vec2)のようにコンテナをアルゴリズムの出力に使用する場合、その動作について曖昧さが生じる
  • 非並行アルゴリズムの出力の改善が複雑化する可能性がある。これは将来の拡張として検討されているが、C++26ではない

このため、P3179R3では出力にrangeではなくイテレータを取るように(既存のRangeアルゴリズムと一貫するように)修正されています。

この提案は、P3179に対して改めて出力のためにrangeを受け取る設計に戻すようにすることを提案するものです。

ここでの提案は、出力を行う並行Rangeアルゴリズムに対して、入力のrangeを取る場合は出力もrangeで受け取るようにすることと、入力をイテレータと番兵で受け取る場合は出力もイテレータと番兵で受け取るようにすること、および出力rangesized_rangeとして入力とは独立した境界を持つものとなるようにして、短い方の境界に達したら処理を終了するようにすることを提案しています。

ここでの出力のrange/イテレータペアは、入力同様にrandom_access_{iterator,range}である必要があります。

このアプローチの利点は

  • 出力データ範囲の境界が既知である場合、より安全なAPIとなる
    • 出力範囲にも入力同様にsized_range/sized_sentinel_forが要求される
  • 全てのアルゴリズムで出力サイズが入力サイズで決まるわけではない
    • 例えばcopy_ifでは、出力範囲が入力範囲よりも短くなる可能性があり、出力の想定サイズが分かっていると効率的な実装が可能になる
  • 出力に範囲を渡すようにすると、コードが多少シンプルになる

等が挙げられています。なお、これらの利点はほぼそのまま非並行アルゴリズムに対しても言えることです。

また、非並行アルゴリズムの出力によく使用されるstd::back_insert_iteratorstd::ostream_iteratorC++17の並行アルゴリズムの時点で使用できないため並行Rangeアルゴリズムにおいてもサポートされず、これらの物を使用しているコードは並行アルゴリズムへの移行のためにどのみち変更が必要となります。

提案文書より、使用感の比較

// P3179R3
void normalize_parallel(random_access_range auto&& v) {
  auto mx = max(execution::par, v);
  transform(execution::par, v, views::repeat(mx), ranges::begin(v), divides);
}

// この提案
void normalize_parallel(random_access_range auto&& v) {
  auto mx = max(execution::par, v);
  transform(execution::par, v, views::repeat(mx), v, divides);
}

P3179R2の設計への懸念に関しては

  • 並行アルゴリズムと非並行アルゴリズムの間の不整合
  • 出力コンテナのサイズが異なる場合の動作
    • 短い方のコンテナに合わせて処理を打ち切り、出力サイズ変更などは行わない
    • これは、ranges::uninitialized_copyranges::uninitialized_moveが採用しているセマンティクスと同じであり、一貫している
  • 将来の改善への影響
    • この提案の変更は並行Rangeアルゴリズムに対する変更のみで、既存のRangeアルゴリズムに対する変更は一切ない
    • そのため、そちらへの改善を妨げるものではない
    • また、並行アルゴリズムの場合は入力と出力の範囲に対する要件がより厳しいため、並行アルゴリズムで使用できる範囲は非並行アルゴリズムでも使用可能になり、将来的に競合する可能性が低いと思われる

の様に応えてています。

また、仮にこの提案がC++26に採択されない場合、ここで提案している出力イテレータには番兵も必須にする事も行われない可能性が高く、委員会においてアルゴリズムオーバーロード増加が懸念されていることを考えると、これも後から追加することは難しいと思われます。並行アルゴリズムにおいては入出力共に境界が判明しているほうが望ましいためP3179R2の現状は並行コードにとって使いやすいものではない、としています。

この提案のAPIranges::transformの2入力版において適用した例

namespace std {
  // イテレータぺアを入出力に取るオーバーロード
  template<typename ExecutionPolicy,
           random_access_iterator I1, sentinel_for<I1> S1,
           random_access_iterator I2, sentinel_for<I2> S2,
           random_access_iterator O, sized_sentinel_for<O> SO,
           copy_constructible F,
           class Proj1 = identity, class Proj2 = identity>
  requires indirectly_writable<O, 
                               indirect_result_t<F&, projected<I1, Proj1>, projected<I2, Proj2>>>
           && (sized_sentinel_for<S1, I1> || sized_sentinel_for<S2, I2>)
  constexpr binary_transform_result<I1, I2, O>
      transform(ExecutionPolicy&& policy, I1 first1, S1 last1, I2 first2, S2 last2, O result,
                SO result_last, F binary_op, Proj1 proj1 = {}, Proj2 proj2 = {});

  // 範囲を入出に取り、イテレータを出力に使用するオーバーロード
  template<typename ExecutionPolicy,
           ranges::random_access_range R1,
           ranges::random_access_range R2,
           random_access_iterator O,
           copy_constructible F,
           class Proj1 = identity, class Proj2 = identity>
  requires indirectly_writable<O,
               indirect_result_t<F&,
                   projected<ranges::iterator_t<R1>, Proj1>,
                   projected<ranges::iterator_t<R2>, Proj2>>>
           && (sized_range<R1> || sized_range<R2>)
  constexpr binary_transform_result<ranges::borrowed_iterator_t<R1>,
                                    ranges::borrowed_iterator_t<R2>,
                                    O>
      transform(ExecutionPolicy&& policy, R1&& r1, R2&& r2, O result, F binary_op,
                Proj1 proj1 = {}, Proj2 proj2 = {});

  // 範囲を入出力に取るオーバーロード
  template<typename ExecutionPolicy,
           ranges::random_access_range R1,
           ranges::random_access_range R2,
           ranges::random_access_range OutR,
           copy_constructible F,
           class Proj1 = identity, class Proj2 = identity>
  requires indirectly_writable<ranges::iterator_t<OutR>,
               indirect_result_t<F&,
                   projected<ranges::iterator_t<R1>, Proj1>,
                   projected<ranges::iterator_t<R2>, Proj2>>>
           && (sized_range<R1> || sized_range<R2>) && sized_range<OutR>
  constexpr binary_transform_result<ranges::borrowed_iterator_t<R1>,
                                    ranges::borrowed_iterator_t<R2>,
                                    ranges::borrowed_iterator_t<OutR>>
      transform(ExecutionPolicy&& policy, R1&& r1, R2&& r2, OutR&& result, F binary_op,
                Proj1 proj1 = {}, Proj2 proj2 = {});
}

この提案はすでにP3179R2へマージされているようです。

P3491R0 define_static_{string,object,array}

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

C++20からコンパイル時にstd::stringstd::vectorを使用することができるようになっているのですが、コンパイル時に構築したそれら(正確にはコンパイル時に確保した動的メモリ)を実行時に持ち越すことができないという重大な制約があります。コンパイル時のstd::stringstd::vectorを実行時に持ち越すためにはなんとかして静的ストレージに移す必要がありますが、このための方法に簡単なものはありません。

この提案は、コンパイル時のものを静的ストレージに昇格させて実行時でも使用できるようにするための、より一般的かつ簡易な方法を提供しようとするものです。提案しているのは次のようなライブラリ機能です

namespace std {
  consteval auto is_string_literal(char const* p) -> bool;
  consteval auto is_string_literal(char8_t const* p) -> bool;

  template <ranges::input_range R> // only if the value_type is char or char8_t
  consteval auto define_static_string(R&& r) -> ranges::range_value_t<R> const*;

  template <ranges::input_range R>
  consteval auto define_static_array(R&& r) -> span<ranges::range_value_t<R> const>;

  template <class T>
  consteval auto define_static_object(T&& v) -> remove_reference_t<T> const*;
}
  • is_string_literal(): 文字列ポインタを受けてそのポインタが文字列リテラルの一部かどうかを判定する
  • define_static_string(): 文字列範囲rを静的なストレージに配置しそのポインタを返す
    • 返される文字列はnull終端される(二重にされることは無い
  • define_static_array(): 文字列以外の範囲rを静的ストレージに配置しその参照(span)を返す
    • この場合長さも一緒に返すためにspanを使用
    • 要素型(range_value_t<R>)は構造的な型(structural type)である事を要求する
  • define_static_object(): 単一オブジェクト版のdefine_static_array()

この提案における文字列の文字型は、char/char8_tのどちらかに限定されています。

std::stringstd::vectorは非型テンプレートパラメータ(NTTP)として使用することもできませんが、この提案の機能によって返されたポインタを介してNTTPとしての使用が可能になります。

template <const char *P>
struct C { };

const char msg[] = "strongly in favor";  // just an idea..

C<msg> c1;                          // ok
C<"nope"> c2;                       // ill-formed
C<define_static_string("yay")> c3;  // ok

define_static_string()およびdefine_static_array()によって静的ストレージに配置されたオブジェクトはpotentially non-unique objectとして文字列リテラルinitializer_listの基底配列と同じ扱いをされ、異なる呼び出しが同じストレージを共有することができるようになっています。ただし、これによるNTTPの等価性の問題を回避するために、異なるpotentially non-unique objectのポインタの比較結果は未規定としています。

constexpr char const* a = define_static_string("dedup");
constexpr char const* b = define_static_string("dup");

static_assert(b == b);                               // ok, #1
static_assert(b + 1 == b + 1);                       // ok, #2
static_assert(a != b);                               // ok, #3
static_assert(a + 2 != b);                           // error: 未規定
static_assert(string_view(a + 2) == string_view(b)); // ok, #4(内容の比較)

define_static_array()においてそのような最適化を許可するとともに、異なる2つの呼び出しが異なる結果を生成することが無いようにするために、define_static_array()の入力範囲の要素型は構造的な型に制限されています。

template <auto V>
struct C { };

// この2つは同じ型となる
C<define_static_array(r).data()> c1;
C<define_static_array(r).data()> c2;

提案文書より、使用例

// std::stringの範囲を返す
constexpr std::vector<std::string> get_strings() {
  return {"Jason", "Was", "Here"};
}

// std::stringの範囲を静的ストレージに配置して、string_viewの範囲として返す
consteval auto promote_strings(std::vector<std::string> vs) -> std::span<std::string_view const>
{
  // 文字列を結合して静的ストレージに配置
  std::string_view promoted = std::define_static_string(
      std::ranges::fold_left(vs, std::string(), std::plus()));

  // 結合され安定化されている文字列から元の文字列に対応するstring_viewを得る
  std::vector<std::string_view> views;
  for (size_t offset = 0; std::string const& s : vs) {
      views.push_back(promoted.substr(offset, s.size()));
      offset += s.size();
  }

  // 結合され静的ストレージに配置された文字列を参照するstring_viewの範囲を静的ストレージに配置して返す
  return std::define_static_array(views);
}

constexpr auto views = promote_strings(get_strings());

define_static_object()を使用してsource_locationのサイズをポインタ一つ分まで削減する例

class source_location {
  struct impl {
    char const* filename;
    int line;
  };
  impl const* p_;

public:
  static consteval auto current(char const* file = __builtin_FILE(),
                                int line = __builtin_LINE()) noexcept -> source_location
  {
    // first, we canonicalize the file
    impl data = {.filename = define_static_string(file), .line = line};

    // then we canonicalize the data
    impl const* p = define_static_object(data);

    // and now we have an external linkage object mangled with this location
    return source_location{p};
  }
};

この提案の動機の大きい部分であるコンパイル時のstd::stringstd::vectorを実行時に持ち越せないという問題は、現在の仕様が非一時的なメモリ確保(non-transient constexpr allocation)を認めていないための制限であり、当然これを認めようとする提案も提出されています。そのため、それが認められればこの機能はほぼ不要になりますが、非一時的なメモリ確保はどうやらC++26には入らなさそうなので、この提案はそれが入るまでのつなぎとしての機能です。

P3492R0 Sized deallocation for placement new

配置new式において、オブジェクト初期化失敗時に選択されるdelete演算子の候補として、サイズ引数を取るものを考慮するようにする提案。

new式はメモリの確保とオブジェクトの構築の2段階の処理を行いますが、オブジェクトの構築中に例外が送出された場合、確保してあったメモリ領域を解放します。その際に使用されるoperator deleteは使用されたoperator newに対応するものが選択されて使用されるようですが、追加のサイズパラメータが考慮される可能性があります(この辺の選択は厳密には未規定ですが)。

例えば、new T;operator new(sizeof(T))が選択された時(非配置なoperator newの場合はサイズ引数を取らないものがない)、Tのコンストラクタが例外を送出した場合にメモリ解放のために自動で呼ばれるdelete演算子の候補は、operator delete(ptr)operator delete(ptr, sizeof(T))になります(非配置operator deleteの場合はサイズ引数を取らないものがあり、オーバーロード可能)。

配置new式の場合はメモリ確保を行わずにオブジェクトの構築のみを行いますが、この提案で言っている配置newというのは、ユーザー定義の配置operator new演算子を呼び出すタイプのものです。

// 配置operator new演算子(置換不可
void* operator new  ( std::size_t count, void* ptr );

// ユーザー定義配置operator new演算子
void* operator new  ( std::size_t count, /* args... */ );

この下側の演算子およびそれを呼び出すものの事をここでは配置new(式)と言っており、この形式のoperator newはサイズ引数の後に任意の引数を取るように定義することができます。例えば、new (args...) T;のように呼ぶと、args...に応じてこれらのオーバーロードが選択されます。

この形式の配置new演算子の場合はユーザー定義されているときにメモリの確保を担う可能性があるため、この形式の演算子の呼び出しが例外を送出した場合、通常のnew式の場合と同様にoperator deleteが呼ばれます。ユーザー定義の配置operator deleteも定義可能であり、args...に応じてそれが存在していれば呼び出されます。

// ユーザー定義配置operator delete演算子
void operator delete  ( void* ptr, args... );

ただしこの場合、ユーザー定義の配置operator deleteにはサイズ引数を取る形式が存在していないため、このオーバーロードに対してサイズ引数sizeof(T)を渡す方法がありません。

このユーザー定義配置形式のoperator new/operator deleteは、ユーザー定義のアロケータにおいて次のようなnew式の呼び出しをサポートすることができます

// ユーザー定義アロケータのオブジェクトallocがあったとして
T* ptr = new (alloc) T(args...);

この場合、allocのアロケータがユーザー定義の配置operator deleteを定義していたとしても、その中からptrに対して割り当てられたサイズを知る方法が無いため、適切なメモリ解放のためには面倒な実装が必要になる可能性があります。

// ユーザー定義のアロケータ型
class my_allocator {};

// ユーザー定義配置形式operator new/operator deleteを定義する
void* operator new(std::size_t count, my_allocator& alloc) {
  // 確保すべきサイズはcountで与えられている
  ...
}

void operator delete(void* ptr, my_allocator& alloc) {
  // 解放すべきサイズが分からない
  ...
}

この提案はまさにこのようなユースケースのために、ユーザー定義の配置operator deleteがサイズ引数を取れるようにするとともに、それが配置new式の初期化失敗時に選択されるようにしようとするものです。

提案ではまず、ユーザー定義の配置operator deleteの有効な形式として次の2つを追加します

  • operator delete(storage-ptr, std::size_val_t(sizeof(T)), args...)
  • operator delete(storage-ptr, std::size_val_t(sizeof(T)), std::align_val_t(alignof(T)), args...)

そのうえで、ユーザー定義の配置operator deleteを選択する場合はこのサイズ引数を取る形式のものを優先するようにします。

ここでのstd::size_val_tはサイズ引数を伝播するための新しい型です。

namespace std {
  enum class size_val_t : size_t {};
}

これは、ユーザー定義の配置operator deleteの第二引数以降は任意の引数を取れるように定義可能であるため、単純にsizeof(T)を取るようにしてしまうと既存のユーザー定義オーバーロードと衝突する可能性があり、それを回避するためです(std::align_val_tと同じ動機です)。

これらの変更によって、ユーザー定義の配置operator deleteでもサイズ情報が得られるようになり、解放関数として選択された場合にもより単純に開放する領域を決定できるようになります。

// ユーザー定義のアロケータ型
class my_allocator {};

// ユーザー定義配置形式operator new/operator deleteを定義する
void* operator new(std::size_t count, my_allocator& alloc) {
  // 確保すべきサイズはcountで与えられている
  ...
}

void operator delete(void* ptr, std::size_val_t(sizeof(T)) count,  my_allocator& alloc) {
  // 解放すべきサイズをstd::size_t(count)から取得可能
  ...
}

P3493R0 Ruminations on reflection and access

リフレクション機能はC++の正しいアクセス制御を反映するべき、とする提案。

P2996で提案されている静的リフレクション機能では、クラスのメンバについてのリフレクションを取得して何かするということができるようになっていますが、その際にアクセス制御(クラスメンバ/サブオブジェクトのprivatepublic性)をすべて無視してしまうことが問題となっています。

この提案は、そのような現状を修正したうえで、C++の通常のアクセス制御を反映したようなAPIにすることを提案するものです。

あるクラスのサブオブジェクトに対しては、そのクラス自身からでもフルアクセスを行うことはできません。例えば、サブオブジェクトのprivateメンバにはアクセスする方法が無く、それはまた継承時のアクセス制御などによっても変化します。したがって、現在のC++のコードのセマンティクス上のコンテキストにはあるクラスに対してそのクラスのすべてのものにアクセス可能なフルアクセス権のようなものは存在していません。クラスのアクセス制御は、アクセスを行おうとする文脈によって変化します。

実際のコードにおけるデータにアクセスする際は(スプライスの際など)アクセス制御を反映しておいて、クラスのメタデータ(型、名前、メンバ数、など)についてのアクセスはフルアクセスを与えれば良いという提案もありますが、C++のアクセス制御はメタデータへのアクセスについても制御しているため、この考え方も通常のC++コードのアクセス制御から離れたものです。

そして、メタデータへのアクセスもprotected継承や仮想関数オーバーライド等を考慮すると一様ではなく、文脈によってアクセス可能範囲は変化します。

したがって、リフレクションのクエリにおいてもC++言語のアクセス制御に従ったものになるべきです。この提案ではそのために次のようなAPIになる必要があるとしています

  1. アクセス可能なメンバのみを返すクエリ機能
  2. 特定の型でアクセスされたときにアクセス可能なメンバを返すクエリ機能
    • 保護されたアクセスを行うためのもの
  3. オーバーライド可能な仮想関数のメタデータのみを返すクエリ機能
    • データは返さず、オーバーライドを生成するために十分な情報を返す

この提案によるAPIでは、同じ形式のクエリでもそのコンテキストによって異なる結果を返します(例えばクラスの内側と外側など)。これはC++のアクセス制御がコンテキストによって異なることを反映した自然なものであり、アクセス制御を無視してアクセス出来てしまう方が遥かにエラーを誘発するとしています。

アクセスできないものにアクセスしようとした場合にエラーになるようなAPIを考えることもできますが、C++のアクセス制御が非常に複雑であることからこのようなフィルタを構築することが困難になるとしています。P2996の現状はフルアクセスの上にユーザーが自前でこのようなフィルタを構成する必要があるため、それは非常に困難で使いづらいものになっています。

P3495R0 Remarks on Basis Statistics, P1708R9

P1708R9に対する意見書。

P1708R9では、C++の標準ライブラリに初等的な統計処理機能を導入することを提案しています。P1708R9については以前の記事を参照

この提案では、P1708R9が標準規格として採用可能となるために足りていない部分を指摘し、更なる作業を促すものです。

  1. 未規定の結果
    • NaN/Infの入力
      • この入力がなぜ未規定の結果をもたらすのかの根拠がない
        • <cmath>の関数はこのような場合の結果が厳密に指定されている
      • これらの入力があった場合に、FE_INVALIDは発生するか?
    • 不十分な要素数
      • 設計空間の問題として言及
    • アンダーフロー/オーバーフロー
      • アンダーフローの場合、なぜ結果が未規定となるのかが不明瞭
      • どちらの場合も、定数評価中に何が起こるかはP0533R9に従うことで簡単に明らかにできる
  2. 不十分な現状の設計の説明
    • アキュムレータに関して、BoostのAccumulatorsが引用されているがリンク切れしている
      • 提案自体にその説明があったほうが親切
    • アキュムレータと通常関数の性能について比較したグラフには改善の余地がある
      • エラーバーがない
      • 一部の処理において劣っているように見えるのはなぜか?
      • 並列実行するとどう変化するか?
  3. 設計空間
    • 不十分な要素数の範囲の入力
      • 検討されている統計量の計算はすべて、要素数が0ではない範囲を必要とする
      • これは当然であるとしても、必要要素数に満たない範囲が入力された場合に何が起こるかについて議論されていない
        • std::expectedの採用など、他のアプローチの比較検討などを特にしていない
      • ユーザーに対して未規定の結果を返す方法をこれほど多く提供することは、ユーザーにとってむしろ不親切となりうる
      • 定数評価中の場合に何が起こるかも明確ではない
    • 精度(accuracy)
      • 計算順序
        • 例えば、平均値の計算結果の精度は累積の計算順序に依存する。速度よりも精度を優先する場合、ソートしてから入力することで最小値から最大値の方向で計算を行いたいと考えるかもしれない
        • しかし現状では、各関数における計算順序は指定されていないため、このような事前ソートは無駄になる可能性がある
        • さらに、各関数にそれが指定された場合、アキュムレータではどのような保証になるのか?
      • 同じ統計量を返す関数とアキュムレータの間での結果の一致
        • ある統計量を計算するフリー関数と、それに対応するアキュムレータに対して、同じ範囲を入力したとき、結果が一致するか?
        • 現在の参照実装ではそうなっていない
        • ここにも、トレードオフを伴う設計余地があり、適切に議論する必要がある
      • 高い精度(precision)の結果型の指定
        • ユーザーが範囲の要素型の精度よりも高い精度の結果型を指定することがある
        • この場合、<linalg>の関数が行うように結果型の精度で計算が行われることを保証する必要がある
    • 並列化
      • フリー関数には実行ポリシーを取るオーバーロードがあるが、アキュムレータについては無い
      • アキュムレータが並列化に対応できるか、どのように対応するか、について議論が無い
      • ユーザー定義reducerのためのコンセプトを定義していないが、どのように定義できるかを検討することは有益かもしれない
  4. APIに関する懸念
    • デフォルトbool
      • デフォルトbool値が存在するとAPIが難解になる
      • 例えば、kurtosis(r, true, false)はユーザーにとって難解。各統計計算の入力パラメータは構造体で保持する方が望ましい
    • 表現力豊かな戻り値型
      • 一部の関数はstd::pairを返すが、返される内容をフィールド名で表現できる名前付き構造体を返したほうが良いかもしれない
    • アキュムレータの構築
      • アキュムレータが要素の範囲からの構築をサポートしない正当な理由はあるか?
    • アキュムレータのoperator()の戻り値型
      • 現在voidを返すが、*thisを返すことは検討しているか?
    • 明示的なテンプレートパラメータ指定
      • フリー関数では戻り値型を明示的に指定できるオーバーロードが存在するが、std::reduceに倣って初期値を引数で受け取ることでその型をそのまま使用するという方法もある

この提案は、統計関数自体に必要性やそれを導入しようとするP1708に反対しているわけではなく、現状の設計では標準ライブラリに求められる厳密さのレベルに達していないため、その点について指摘し追加作業を促すものです。

P3497R0 Guarded Objects

ロックと値をペアで扱うためのライブラリ機能の提案。

並行処理で複数のスレッドから共有される変数には、std::mutex等のロック機構を使用して変数アクセスの競合を防止する必要があります。アトミックではなくstd::mutex等の明示的ロックを使用する場合、保護対象の変数とは別にstd::mutex等を作成したうえで、ロックが必要な場所で手動でロックを取得する必要があります。

ミューテックスとデータのこのような関係はコード上で手動で表現されているにすぎない暗黙的なものであり、保護が必要な個所では適切なロックの使用を手動管理する必要があります。これはヒューマンエラーによるバグの原因となります。

この提案は、ロックと保護対象のデータをカプセル化して一体的に扱うことで、このような手動でのロック管理を自動化するライブラリ機能を提案するものです。

#include <string>
#include <iostream>

// 参照実装
#include "https://raw.githubusercontent.com/copperspice/cs_libguarded/master/src/cs_plain_guarded.h"

int main() {
  // ロック付きのstd::string
  libguarded::plain_guarded<std::string> guarded_string;

  // ロックを取得
  auto accessor = guarded_string.lock();  // 'accessor'がスコープ内にある限り、`guarded_string`のロックは取得されたままになる

  // 変更は安全
  *accessor = "Hello, World!";  

  // 読み込みも安全
  std::cout << *accessor << '\n';

  return 0;
}  // accessorが破棄されると、ロックも解放される

plain_guarded<T>の変数を宣言することで、ロックが紐づけられたTの値が得られます。内部ではロックとTのオブジェクトが保持されており、Tのオブジェクトは透過的にアクセスできず、明示的なアクセス関数を通してのみアクセス可能で、それらのアクセス関数はロックの取得と解放をそのアクセススコープ内で自動化します。

.lock()関数によってアクセス権を得ることができ、その戻り値(プロクシ参照)を通したアクセスはロックのスコープ内でのアクセスとなり常に安全に行うことができます。.lock()の戻り値はロックの所有権を保持しており、RAIIによってロックは管理されています。

このような型とAPIを使用することで、ロックを取得せずに誤ってデータにアクセスすることはほぼ不可能となり、またユーザーコードにおいては保護データへのアクセスに伴うロックの取得とそのスコープの明示のようなボイラープレートコードが無くなることで、認知負荷が軽減されるとともにヒューマンエラーも防止されるようになります。

また、このような型が存在していれば、クラスの実装に伴うスレッドセーフ性の考慮は不要になり、クラスがその目的とする実装とスレッドセーフ性の実装を分離して考えることができるようになります。

#include <string>
#include <vector>
#include <iostream>

#include "https://raw.githubusercontent.com/copperspice/cs_libguarded/master/src/cs_plain_guarded.h"

struct person {
  std::string name;
};

using contacts_t = std::vector<person>;

// contacts_tのインターフェースはスレッドセーフ性を考慮しない
void print(const contacts_t& contacts) {
  for (const auto& person: contacts) {
    std::cout << person.name << '\n';
  }
}

int main() {
  // あるクラスに対してスレッドセーフが必要な場合、plain_guardedで包むことでそれを付与できる
  libguarded::plain_guarded<contacts_t> synchronized_contacts;
  // ロックとともにアクセスされるため、スレッドセーフな呼び出しとなる
  print(*synchronized_contacts.lock());

  return 0;
}

ロックを取得中に行う処理を渡すタイプのAPIを持つ実装の例

#include <mutex>
#include <string>
#include <iostream>

template<typename T>
class naive_guarded {
private:
  T data;
  mutable std::mutex mtx;

public:
  template<typename Func>
  auto with_lock(Func&& func) {
    std::lock_guard<std::mutex> lock(mtx);
    return func(data);  // Pass the data to the provided function.
  }
};

ちなみにこのような機能の提案は初めてではなく、以前に提案されたsynchronized_value<T>(P0290R4)がConcurrency TS v2にマージされています。

P3498R0 Stop the Bleeding but, First, Do No Harm

メモリ安全性の問題に緊急性をもって取り組むことを強く提言する提案。

米国のNSA等の提言をきっかけとして、プログラミング言語およびプログラムに対する安全性への要請がかなり高まっており、それによってC++の使用そのものに対してのかなりの圧力をもたらしているようです。

このことは、C++が長年使用されてきたアプリケーション領域においてC++の将来性を脅かすものであり、(筆者の方々はMicrosoftの社員ですが)C++大規模なコードベースを保有し、その開発環境も提供してきたMicrosoftだけの問題ではなく、C++を使用するすべてのソフトウェアプロバイダーが今後必ず直面する問題になります。

この提案は、そのような圧力を受けてWG21/C++に対して安全性に関する問題への取り組みを緊急かつ目的意識をもって取り組むべき、と提言するものです。ただし、その取り組みは現在の出血を止め、なおかつ害をなすようなものはないことを求めています。

現在のコードに適用できず、将来これから書かれるコードにだけ安全性をもたらすような革新的な言語機能は既存のコードに対して役に立たず、問題の解決を遅延させ現在の出血を止めることはできません。

ここでは、具体的な行動として次の事を提案しています

  • 境界チェックの保証を提供する
    • C++20の開発サイクル中に、インデックスアクセスの境界チェック付きのstd::spanを提案したが、否決されている
    • 振り返ってみればこの決定は逆効果だった
    • std::spanに限らず、境界チェックの保証を標準C++として提供するべき
  • 既存のコードベースへの適用可能性
    • 追加のアノテーションを最小限にする
    • C++の現在のセマンティクスの下で既に利用可能な情報を利用するツールや機能を優先すべき
    • プロファイル提案は有望
  • Contracts機能の再考
    • MicrosoftはContracts機能の導入を支持している
    • しかし、P2900の現在の提案は安全性とコード分析に関して大きく的外れなものであり、C++26に急いで導入する必要はない
    • 次の段階に進む前に更なる作業が必要

今や安全性はC++の関連する領域におけるプログラミング言語の際立たせる要素の一つとなっており、安全性に関する懸念に対して有意義な共通の解決策で対処できなければ、C++コミュニティだけではなくソフトウェア業界の分断が進むことになる、としています。

P3502R0 Slides for D2900R11 - Contracts for C++

P2900R11(Contracts提案)の説明スライド

P2900の前回会議(2024/06 セントルイス)からの変更点、11月の会議(このスライドがプレゼンされたところ)の達成目標、なぜContractsが必要なのか、について解説されています。

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

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

この提案は、std::promisestd::packaged_taskのアロケータ対応に関する複数のLWG Issueをまとめて解消するためのものです。

まずLWG Issue 2095で報告された、uses_allocator<promise<R>, A>::value == trueとなることが指定されているにもかかわらず、std::promiseの右辺値を受け取るアロケータコンストラクタが欠落している、という問題がありました。

using prom = promise<void>;

tuple<prom> t1{ allocator_arg, a };
tuple<prom> t2{ allocator_arg, a, prom{} }; // ill-formed

一方で、同じヘッダにあるstd::packaged_taskでは、LWG Issue 2921/2976によってアロケータコンストラクタは削除されており(std::functionとのインターフェース類似性を保つためだった)、これを受けてstd::promiseのアロケータコンストラクタも削除しようというLWG Issue 3003が報告されていました。

それらの流れを受けてのLEWGの議論では、std::promiseのアロケータコンストラクタは実際に有用であったことと削除すると既存コードを壊してしまうことによって、std::promiseのアロケータコンストラクタは削除しない事が決定されます。

そしてそのうえでLWG Issue 2095を解決するために、std::promisestd::uses_allocator特殊化を削除してuses-allocator構築のサポートを無くすることで解決することを決定しました(ムーブコンストラクタへの追加引数は無視されるため)。

また同時に、std::packaged_taskの削除されたコンストラクタを復帰するもののstd::uses_allocator特殊化を再追加しない、事も決定されました(LWG Issue 2921/2976の決定は間違っていたとして)。

しかしこのstd::packaged_taskの再修正によって、std::packaged_task::reset()が内部でメモリを確保するのにアロケータを使用しないというLWG Issue 2245の問題が再浮上しました(LWG Issue 2921によってpackaged_taskがアロケータを使用しなくなったためクローズされていた)。この解決のためには、復帰したアロケータコンストラクタから受け取っているアロケータを使用するように修正することが決定されました。

このような流れがあったものの、これらのことが明文化されたり統合したIssue/提案として扱われることなかったため、その流れと一連の解決をまとめてLEWGのレビューにかけるためのものがこの提案です。

結局、この提案の変更は次の点です

  • std::promisestd::uses_allocator特殊化を削除する
    • 既存のアロケータを受け取るコンストラクタは削除しない
    • LWG Issue 2095を解決
  • std::packaged_taskの削除されたコンストラクタを復帰する
    • LWG Issue 3003を解決
  • std::packaged_taskstd::uses_allocator特殊化を再追加しない
    • LWG Issue 2095と同種のバグを回避
  • std::packaged_task::reset()は構築時に渡されたアロケータを使用する
    • LWG Issue 2245を解決

この提案の内容は個別のIssueで十分に検討済みのものであるため、直ぐにLWGに転送されています。

P3504R0 C++ Standard Library Ready Issues to be moved in Wroclaw, Nov. 2024

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

P3508R0 Wording for constexpr for specialized memory algorithms

P2283R2のconstexprな未初期化領域に対するアルゴリズムでplacment newを使用するようにする提案。

この提案は、P2283R2で提案されている未初期化領域に対するアルゴリズムconstexpr化において、定数式での処理にstd::construct_atではなくplacement newを使用するようにすしようとるものです。

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

P2283R2ではほぼすべての未初期化領域にに対するアルゴリズムconstexpr対応させ、定数式中でのオブジェクト構築のためにはstd::construct_atを使用していました。しかし、std::construct_atは関数テンプレートであるためその引数で受けることでprvalueの伝播を妨げてしまうという問題がありました。

その後、P2747R2がC++26に採択されたことでplacement newが使用できるようになったことでstd::construct_atを使用する必要がなくなったため、この提案ではP2283R2の変更に対してplacment newを定数式でも使用するようにする変更を適用しようとするものです。

この提案では結局、未初期化領域に対するアルゴリズム関数のシグネチャconstexprを付けるだけ、の変更になっています。

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

P3510R0 Leftover properties of this in constructor preconditions

P3510R1 Leftover properties of this in constructor preconditions

P3510R2 Leftover properties of this in constructor preconditions

コンストラクタの事前条件とデストラクタの事後条件において、thisの暗黙的な使用を禁止する提案。

契約プログラミング機能はコードの信頼性や安全性を向上させるための機能であるため、その導入に当たっては新しい種類のUBを不必要に導入しないようにする、というポリシーがあります。

しかし、次のようなUBが導入されています

class X {
  std::string name;

public:
  explicit X(const char * n)
      pre(name != nullptr)  // まずこっちが評価され
    : name{n}               // 次にこっちが評価される
    {}
};

コンストラクタの事前条件はそのクラスのサブオブジェクトの初期化前に評価されるため、コンストラクタの事前条件から非静的メンバにアクセスすると初期化前の変数へのアクセスとなりUBです。より正確には、コンストラクタの事前条件から*thisの領域にアクセスしようとするとUBになります。そして同様に、デストラクタの事後条件から*thisの領域にアクセスしようとするとUBになります。

この問題はP3172R0で認識されて報告されていたものの、コンストラクタの事前条件とデストラクタの事後条件においてのthisの明示的/暗黙的な使用を禁止することにはコンセンサスが集まらず、明示的なUBとしてP2900にマージされました。

P3172R0ではthisを別の関数に渡した後でどのように使用されるかをコンパイラが検出することは困難であるとしてUBとしていたようです。しかし、関数渡し以前に、事前条件/事後条件においてthisを使用する責任はそのコードを書いたユーザーにあり、そのコンテキストにおいてthisの使用そのものはチェック可能であるため、P3172の主張には説得力がないと思われます。

この提案はこのようなUBを取り除くための変更をP2900に提案するものです。具体的には、コンストラクタの事前条件およびデストラクタの事後条件において暗黙的な*thisの使用(非静的メンバへのアクセス)をill-formedとする一方で、明示的なthisの使用は許可します。

このようなコンテキストで明示的にthisを使用するのは非常に稀であり、その使用はコードとして目立つものになります。一方で、暗黙的なthisの使用はそうではなく、危険性を導入しながらもそれが目立たずUBに気づきづらいものになっています。また、Mixinクラスのオフセットを求めるようなユースケースにおいてはthisポインタの使用が必要です。これらの理由から、明示的なthisの使用は許可しようとしています。

EWGのレビューではこの提案はコンセンサスを集めたため、P2900にマージされています。

P3517R0 Slides presented to LEWG on trivial relocation in Wroclaw

P2786R11で提案されているtrivial relocationについて解説するスライド。

P2786の提案内容や、(これが発表された)2024年12月会議における変更点、trivial relocationそのものやReplaceabilityという概念についての説明、ライブラリAPIの一覧などが説明されています。

P3518R0 Slides for Standardized Constexpr Type Ordering

P2830R4の紹介スライド。

P2830R4で提案されている定数式における型の順序付けに関して、そのモチベーションや提案の現状などについて簡単に紹介されています。

P3519R0 Slides for P3425 presentation to LEWG

P3425R0の説明スライド。

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

P3425R0の動機となる問題についての視覚的な解説と、提案するソリューションについての解説が行われています。

P3520R0 Wroclaw Technical Fixes to Contracts

P2900(Contracts提案)に対して2024年11月の会議で提起された小さな問題を修正する提案。

2024年11月の会議期間中のCWGにおけるP2900R11のレビュー中に提起された次の4つの問題の報告と、その修正についての提案です。

  1. プログラムを終了する方法の指定について
  2. 契約注釈内でのラムダ式の使用禁止
  3. 依存型引数におけるconstの規則の修正
  4. 契約アサーションの評価シーケンスの定義の修正

1. プログラムを終了する方法の指定が曖昧

enforce/quick-enforceセマンティクスによる評価においては契約違反が起きた場合にプログラムが終了しますが、現在その終了方法は実装定義の方法と指定されています。しかし、この規定だけではどのような終了方法が適合しているのかが不透明であるため、具体的に列挙することがCWGによって要求されました。

現在のC++にはプログラムを終了させる方法が大きく次の6つ存在します

  1. std::exit: 完全なクリーンアップを伴う通常のプログラム終了
  2. std::quick_exit: 通常のプログラム終了だが、クリーンアップ処理は限定的
  3. std::_Exit: さらに限定的なクリーンアップ処理による通常のプログラム終了
  4. std::terminate: プログラムを異常終了させるC++の方法
  5. std::abort: プログラムを異常終了させるCの方法
  6. __builtin_trapなど: プログラムを異常終了させる実装定義の方法

これらの方法は、静的ストレージのオブジェクトのデストラクタを呼び出すかや、コールバックの呼び出しとその登録方法などが異なっていますが、どれもプログラムを終了させる方法ではあります。

契約違反が起きている状態はプログラムの異常状態なので、終了方法は異常終了させる後ろ3つのものが適切であり、かつこの3つはコンパイラベンダーの要求や実装経験に適う方法でもあります。

そのため、契約違反時の終了方法(contract-terminated)としては、この3つの方法のいずれかを取ることを明示的に指定するように変更することを提案しています。

2. 契約注釈内でのラムダ式の使用禁止

(上の方にある)P3483R1の内容も参照してください。

契約注釈を持つ関数の再宣言における繰り返しの契約注釈内でラムダ式を使用できるようになっており、契約注釈内にラムダ式がある場合でもそれら宣言の間で同一のラムダ(クロージャ型)となります。

void f() pre([]{ return true; }());
void f() pre([]{ return true; }()); // OK

この同一の判定はトークンベースではなく、ODRの観点からそのラムダ式を含む述語(契約条件式)の定義が同じであるかどうか、によります。それによって、定義としては同じであるもののトークンが異なるラムダ式を同一のものとして扱う必要があります。

int f(int i, int j) pre([&](){ return i + j > 5 ; }());
int f(int k, int l) pre([&](){ return k + l > 5 ; }()); // OK

しかし、これは実装がかなり困難であるだけではなく、C++言語の他の部分の規則とも一貫していません。

一方で契約注釈内でラムダ式の使用にはユースケースがあるため、それをとにかく禁止してしまうことは厳しすぎます。

したがってこの提案では、再宣言において契約注釈を繰り返す場合にのみ、ラムダ式が契約注釈内に現れることを禁止するようにすることを提案しています。

3. 依存型引数におけるconstの規則の修正

(上の方にあるP3489R0の内容も参照してください)

P3489R0では関数テンプレートの事後条件からそのテンプレートパラメータに依存する非参照引数を使用する場合、テンプレートパラメータに推論される型のconstに関わらず明示的にconstを指定しておくようにすることが提案され、P2900にマージされました。

しかし、constな型エイリアスを介して宣言される場合もエラーになってしまうという見落としがありました

using const_int_t = const int;
void f(const_int_t i) post (i > 0); // error but should be OK

template <typename T>
void f(std::add_const_t<T> t) post(t > 0); // error but should be OK

P3489R0の目標を維持しつつこれらの例を許可するために、この提案ではP3489R0で提案されていたオプション2を選択することを提案しています(現在の規定がオプション1)。これは、推論され置換された後のテンプレートパラメータがconstであれば良いというものです。

template <typename T>
void f(T t) post (t > 0);

int main() {
  f(1);       // error: deduced parameter type (int) is not const
  f<int>(1);  // error: parameter type (int) is not const
  f<const int>(1); // OK
}

4. 契約アサーションの評価シーケンスの定義の修正

契約注釈の評価の回数は未規定とされており、0回にも2回以上にもなる可能性があります。それでも、そのような評価の繰り返しは契約アサーションシーケンス(contract-assertion sequence)という範囲によって制限されています。ある契約注釈はそれが属する契約アサーションシーケンスの内部の任意の点でのみ再度評価される可能性があり、なおかつそのような繰り返しは属する契約アサーションシーケンス内で一度評価された後でのみ発生します。

すなわち、ある契約アサーションシーケンスに属する契約注釈が別の契約アサーションシーケンス内で繰り返し評価されることはなく、契約アサーションシーケンス内で評価の順番が入れ替わることはありません(繰り返し評価が割り込むことはあります)。

契約アサーションシーケンスとは、連続しているとみなされる複数の契約注釈をまとめた単位であり、2つの契約注釈が空の操作(vacuous operations)によってのみ区切られている場合にその2つの契約注釈は同じ契約アサーションシーケンスに属します。

この空の操作とは、その操作(式の評価)に伴ってプログラムの状態を変更しない操作、を指定することを意図しています。その具体的なものは割愛しますが、空の操作という概念の定義の難しさによって、契約アサーションシーケンスが不必要に大きくなってしまう場合があります。

例えば次のようなコードにおいて

void f()
  pre(pre1())
  pre(pre2())
  post(post1())
  post(post2())
{
  contract_assert(ca1());
  contract_assert(ca2());
  int i = 0;
  contract_assert(ca3(i));
}

void g() {
  f(); // checks pre1(), pre2(), ca1(), ca2(), ca3(i), post1(), post2()
  f(); // checks pre1(), pre2(), ca1(), ca2(), ca3(i), post1(), post2()
}

g()内での2回のf()の呼び出しによる契約アサーションシーケンスは

  • pre1(), pre2(), ca1(), ca2(), ca3(i), post1(), post2(), pre1(), pre2(), ca1(), ca2(), ca3(i), post1(), post2()

となります。

契約アサーションシーケンス内では、一度評価された後ならその契約注釈何度でもその契約アサーションシーケンス内で繰り返される可能性があるため、例えば一回目のf()の呼び出しの事前条件(pre1(), pre2())が2回目のf()の呼び出しの事後条件(post1(), post2())の後に繰り返される、といったことが起こりるかあるいは許可されることになります。

契約アサーションシーケンスという概念の意図は、呼び出し側と呼び出し先での契約チェックの実装自由度を高めつつ、契約注釈の繰り返し回数を明確に規定しないという目的(副作用に依存させないため)を達成することにあり、これはその意図に沿った結果ではなく、この自由度は本来必要ありません。

そのためこの提案では、契約アサーションシーケンスをより明確に定義しなおすことによって、契約アサーションシーケンスをより小さなものになるようにすることを提案しています。

新しい定義では、契約アサーションシーケンスとは次の条件を満たす契約注釈のグループとして定義されます

これによって、先ほどのg()内での2回のf()の呼び出しによる契約アサーションシーケンスは次のように分離されます

  • pre1(), pre2()
  • ca1(), ca2()
  • ca3(i)
  • post1(), post2()
  • pre1(), pre2()
  • ca1(), ca2()
  • ca3(i)
  • post1(), post2()

この新しい定義こそが契約アサーションシーケンスが確保したい実装自由度そのものです。

ここで提案されている4項目はいずれも大きな反対はなく、P2900にマージされています。

P3521R0 Pattern Matching: Customization Point for Open Sum Types

パターンマッチングにおいて、ユーザー定義の直和型をマッチングできるようにするためのカスタマイゼーションポイントを追加する提案。

現在のパターンマッチング仕様は2つの提案がありますが、そのどちらでも標準ライブラリの直和型であるstd::variantstd::anyをサポートしていてもユーザー定義の同種の直和型サポートが十分ではありません。

この提案では、std::variantのようにコンパイル時に取りえる型が定まっている直和型を閉じた直和型、std::anyのように実行時にしか分からないものを開いた直和型として区別し、後者の開いた直和型に対して標準ライブラリであるかユーザー定義であるかを問わずにパターンマッチングで利用できるようにするためのカスタマイズポイントを提案するものです。

なおこの提案の提案先はP2688R3のパターンマッチング提案に対してのものです。

P2688R3では択一パターン(Alternative Pattern)という構文によって直和型のパターンマッチングを行うことができます。

int f(const std::variant<int, double>& v) {
  return v match {
    int: let i => i;
    double: let d => int(d);
  };
}

この択一パターンにおいては次の3つのいずれかに該当するものをマッチングすることができます

  1. std::variant-like
    • variantプロトコルvariant_size, variant_alternative, get, index)を使用
  2. キャスト
    • cast<S>::operator()<type-id>(s)の様な物を使用
  3. 多態的な型
    • dynamic_castを使用

この提案では、2番目のキャストのマッチの際にtry_cast()という関数を考慮するようにすることを提案しています。

try_cast()は、std::anyに対してはstd::any_cast()std::exception_ptrに対してはstd::exception_ptr_cast()(P2927R2)のラッパ関数として定義されますが、マッチングの入力に対するADLによっても探索されることでユーザー定義型に対しても機能します。

namespace ns {
  struct Widget { /* ... */ };

  template <typename T>
  const T* try_cast(const Widget& w) noexcept {
    return // ...
  };
}

より詳細な動作については、まず次のような択一パターンのみを使用したマッチングの場合において

subject match {
  type: subpattern => // ...;
  _ => // ...;
};

Etry_cast<type>(subject)の呼び出しとして、try_cast()の探索が行われて

  1. Eは適格
    • try_cast()の呼び出し可能な候補が見つかる
  2. Eは文脈的にboolに変換可能
  3. Eは間接参照可能

の全てを満たす場合にキャストの択一パターンのマッチングが採用され、Etrueに変換される場合にそのパターンにマッチングし、*Esubpatternにマッチする場合にマッチングに成功し、そのパターンと=>右辺の式が選択されます。

このような、言語機能における特定の名前を持つ関数の使用には構造化束縛におけるget()関数や範囲forにおけるbegin()/end()という前例があります。そのどちらにおいても、CPOのような複雑なライブラリアプローチや、std::hashのような標準ライブラリクラステンプレートの特殊化などのアプローチは選択されておらず、ADLのみあるいは+αの直接探索のみが行われています。この提案のtry_cast()もそれ(特に構造化束縛)に倣っています。

P3524R0 Core Language Working Group "ready" Issues for the November, 2024 meeting

11月に行われたWrocław会議でWDに適用されたコア言語に対するIssue報告の一覧。

P3525R0 Explicit Implicit Template Regions

関数内の任意の場所にテンプレート領域を作り出す構文の提案。

P1061(構造化束縛によるパラメータパックの導入提案)のR9では、構造化束縛によってパラメータパックを導入する機能を任意の場所で使用できるようにするために、使用された場合その宣言以降を暗黙的にテンプレート化する、という仕様を当初採用していました。

struct Point { int x, y; };

// sum()はテンプレートではない
auto sum(Point p) -> int {
  // が、パックが導入されており
  auto [... parts] = p;

  // 畳み込み式も使用できる
  return (... + parts);
}

しかし、テンプレートと非テンプレートでは性質が異なる部分がいくつもあり、暗黙的にそれが変化してしまうのが忌避された結果、R10ではその仕様が削除され構造化束縛によるパック導入はテンプレートの内部でのみ使用可能にされました。

これによって、非関数テンプレート内でP1061の機能を使用しようとするとそのためだけに関数をテンプレート化しなければならなくなります。

template <class>
auto sum(Point p) -> int {
  auto [... parts] = p;
  return (... + parts);
}

あるいはジェネリックラムダを使用する方法もあります。しかしいずれの方法にも面倒さが付きまといます。

この提案は、この問題の解決のために、関数の一部を明示的にテンプレート化する機能を導入しようとするものです。

auto sum(Point p) -> int {
  auto [... bad_parts] = p; // error: テンプレート内ではない

  template {
    auto [... good_parts] = p; // OK, テンプレート内(明示的!
    return (... + good_parts);
  }
}

これは、P1061R9で提案されていた暗黙のテンプレート領域(implicit template region)に対して、それを明示的に行うものです。また、構文としてはP3289R0のconstevalブロックの構文に影響をうけています。

template { ... }のブロック内はcompound-statementという構文が来ることができて、これは通常のブロックスコープ内と同じ扱いになります。ただし、その内部はテンプレートであるため、関数呼び出しに当たっては部分的にインスタンス化されたりします。

これにより、関数全体をテンプレート化したりラムダ式を使用したりするよりもはるかに軽量かつローカルにテンプレート化することができます。また、P1061R9の暗黙のテンプレート領域は既に実装されていたため、この提案のためには新しい構文を追加するだけで実装することができます。

P3527R0 Pattern Matching: variant-like and std::expected

パターンマッチングでstd::expectedを使用できるようにする提案。

P2688R3のパターンマッチング提案では、std::variantがパターンマッチングで使用できます

auto v = std::variant<int32_t, int64_t, float, double>{/* ... */};

v match {
  int32_t: let i32 =>
    std::print("got int32: {}", i32);
  int64_t: let i64 =>
    std::print("got int64: {}", i64);
  float: let f =>
    std::print("got float: {}", f);
  double: let d =>
    std::print("got double: {}", d);
};

しかし、std::expectedstd::variantと類似した特徴を持ちながらもここでサポートされていません。

この提案はstd::expectedstd::variant同様にマッチングできるようにすることを提案するものです。このために次の3つの事を提案しています

  1. 説明専用のvariant-likeコンセプトを導入する
  2. std::expectedvariant-likeになる用ようにする
  3. std::visitを拡張してvariant-like型を扱えるようにする(オプション

このvariant-likeの定義方法として、2つのオプションを提示しています

  1. 有効な型名をリスト化する
    • tuple-likeと同様の定義方法なので一貫する
    • variant-likeを拡張するのに文言変更(すなわち提案)が必要
  2. より一般的で複雑なコンセプト定義
    • std::variantstd::expectedを包含できるような型の特徴を指定したコンセプトを追加する
    • 保守性が向上するが、現時点では候補が2つしかなく、将来にわたってうまく機能するか不透明

この提案ではどちらのオプションも選択しておらず、EWGに選択を委ねています。

オプションとして提案しているstd::visit()の拡張のためにはパターンマッチングとはほぼ関係ありませんが、variant-likeが関与することと有用性があるということでここで提案されています。

P3530R0 Intrinsic for reading uninitialized memory

未初期化メモリを合法的に読み取ることのできるライブラリ関数の提案。

初期化されていないメモリ領域を読み取ることは、C++20まではUBでありC++23からはEBとなります。どちらにせよ、未初期化メモリを何の問題もない形で読み取ることは標準C++の範囲では不可能であり、そのような動作に依存するアルゴリズムやデータ構造は何かしらの特殊な仮定が無ければ記述することができません。

提案ではそのような例として、次のようなSparseSetを挙げています。

template <int n>
class SparseSet {
  // 不変条件: 
  // - 全ての 0 <= i < size について、index_of[elements[i]] == i
  // - elements[0..size-1]にはsetのすべてのデータが格納されている
  // これらの不変条件によって、正確性が保証される
  int elements[n];
  int index_of[n];
  int size;
public:
  SparseSet() : size(0) {}  // 2つの配列はいずれも初期化しない(構築がO(1))

  void clear() { size = 0; }

  bool find(int x) {
    // xは[0, n)内の値であると仮定

    // 初期化前のindex_of[x]を読み取りうる
    int i = index_of[x];
    
    // 上記読み取り自体のUB/EBを除いて、このアルゴリズムはindex_of[x]から得られる任意の値に対して正しく動作する
    // なぜなら、不変条件によって以下の条件が満たされる場合にのみxがset内に存在していることが保証されるため
    return 0 <= i && i < size && elements[i] == x;
  }

  void insert(int x) {
    // xは[0, n)内の値であると仮定

    if (find(x)) { return; }

    // 不変条件の保護
    index_of[x] = size;
    elements[size] = x;
    size++;
  }

  void remove(int x) {
    // xは[0, n)内の値であると仮定

    if (!find(x)) { return; }

    // 不変条件の保護
    size--;
    int i = index_of[x];
    elements[i] = elements[size];
    index_of[elements[size]] = i;
  }
};

このSparseSetは構築も含めたすべての操作がO(1)で行えるデータ構造です。そして、その動作はfind()内での未初期化な領域読み取りに大きく依存しています。

このデータ構造は使用されると実際に未初期化領域を読み取るのですが、その一方で読み取った結果得られた値が何であれ未初期化領域を読み取った場合はfind()falseを返します。

i = index_of[x]の値は保存隅のxに対しては常にsize未満となるため0 <= i && i < sizeの条件をパスする一方、ランダムな値の多くはこの条件を突破できません。未保存のxがもしこの条件を突破しても、elements[i] == xの条件は必ずfalseになります(elements[i]iのインデックスに対応した値が保存されているため、未保存の値とは等しくならない)。

筆者の方の知る限り、未初期化領域読み取りに依存せずにこのようなデータ構造の要件(全ての操作がO(1))を達成する方法は存在しない、としています。

この提案は、このようなユースケースのために未初期化領域を合法的に読み取ることのできる関数を用意しようとする提案です。

提案ではその方法として2つのオプションを提示しています

  1. std::read_maybe_uninitialized()というコンパイラマジックによる特殊な関数を用意する
  2. std::start_lifetime_as_array_uninitialized(ptr, n)という関数により、ptrの領域がn要素配列によって(未規定だが有効な値に)初期化されたとみなすようにする
    • std::start_lifetime_as_array()と同様のアプローチ

1のオプションは特に、LLVMfreeze命令として実装されているものと同様のアプローチです。

提案ではまだどちらも選択しておらず、その選択はEWGIに委ねています。

P3533R0 constexpr virtual inheritance

定数式で、仮想継承をしているクラス型を扱えるようにする提案。

規格においては、関数がconstexprとして適格であるかのその関数の性質による判定条件としてconstexpr-suitableという概念を使用しています。関数がconstexpr-suitableではない場合、その関数は定数式で呼び出しできません。現在の定義では、関数がconstexpr-suitableではないのは次のどちらかの場合です

  • 関数がコルーチンである
  • 関数がコンストラクタ/デストラクタであり、そのクラスが仮想基底クラスを持つ

P3367の提案が採択されると1つ目の条件が消えるため、残る1つを緩和することでconstexpr-suitableの概念を規格から削除することができるようになり、それにより規格の記述を単純化できます。

また、仮想継承しているクラス型オブジェクトを定数式で扱えるようにする利点は、<iostream>関連の機能が定数式で使用できるようになることです。特に、std::stringstreamが定数式で使用可能になり、<chrono>のストリームパース/フォーマット、basic_istream_view等が定数式で使用可能になります。

この提案は、これらの理由からconstexpr-suitableの仮想継承禁止規定を緩和することを提案するものです。

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

// 最基底
struct Superbase {
    string id{"name"};
};

// 中間共有基底
struct Common: Superbase {
    unsigned counter{0};
};

// 菱形の中間の一角
struct Left: virtual Common {
  unsigned value{0};
    constexpr const unsigned & get_counter() const {
    return Common::counter;
    }
};

// 菱形の中間の一角
struct Right: virtual Common {
    unsigned value{0};
    constexpr const unsigned & get_counter() const {
        return Common::counter;
    }
};

// 最派生
struct Child: Left, Right {
    unsigned x{0};
    unsigned y{0};
    // ...
};

constexpr auto ch = Child{}; // 現在: コンストラクタ呼び出しが定数式で実行できない
                             // この提案: ok

筆者の方はclangにおける具体的な実装戦略についても記しており、他の実装者の方に聞いてみても大きな問題は無い、としています。

この提案は、P3367(コルーチンのconstexpr許可)の後に続いて導入することを前提としています。

P3537R0 Presentation on Concurrent Queue API LEWG Telecon 2024-Dec-10

P0260の紹介スライド。

P0260R13は最初の方にあります。

並行キューの設計空間の広さについての問題、その回答としての現在の提案のAPIの設計選択についての説明が行われています。

P3539R1 Consistent Function Label Naming for Sections

規格の関数に関するセクションのラベル名を一貫させる提案。

規格のあるセクションにはラベルが貼られており(例えば[constexpr.functions]など)、これによってバージョン間でも一貫した形で一意の場所を指定することができるようになっています。

関数に関して記述された場所を指定するラベルは通常、[constexpr.functions]のようにフルのfunctionsが使用されるか、[conv.func]のように省略形のfuncが使用されるかのどちらかです。

しかし、最近追加された[print.fun](std::print()についてのセクション)は既存の2パターンのいずれとも異なる言葉の使い方がされています。

この提案はこれを修正するように求めるもので、[print.fun]を[print.func]にすることを提案しています。

この提案はeditorialなものですが、ラベルは安定化した後変えないようにしているため、却下されています。

P3540R0 #embed Parameter offset

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

#embedでは指定されたファイルをバイト列として読み取る際に、limitによる最大長の制限や、prefix/suffixによって読み取りバイト列の前後に特定のバイト列を付加することができます。しかし、オフセットの指定、すなわち読み取るファイルの読み取り開始位置を指定する方法はありませんでした。

オリジナルであるstd::embed(上の方)にはオフセット引数があるほか、C23の#embedの採択によるGCC/Clangにおける実装では実装定義の方法(gnu::offset/clang::offset)によってオフセット引数のサポートがなされています。

この提案は、既存のそれらの実装をそのまま標準化して、#embedでオフセット引数を指定できるようにする提案です。

このニーズは元々の提案の議論中にも認識されていたものの、おそらく機能追加による議論の遅延を避けるためにC23で現在の仕様による採択を優先していたようです。その後C++にもそれがほぼそのまま導入されたため、ここでは改めてその機能拡張を提案しています。

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

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

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


// verify PCM WAV resource
static_assert(sizeof(sound_signature) == 4);
static_assert(sound_signature[0] == 'R');
static_assert(sound_signature[1] == 'I');
static_assert(sound_signature[2] == 'F');
static_assert(sound_signature[3] == 'F');

static_assert(sizeof(truncated_sound_signature) == 2);
static_assert(sound_signature[0] == 'F');
static_assert(sound_signature[1] == 'F');

P3541R0 Violation handlers vs noexcept

違反ハンドラ(契約注釈)とnoexcept演算子の相互作用について明確にすることを求める提案。

Contractsの現在の仕様では契約違反が発生した後に呼ばれる違反ハンドラ内から例外を送出することができ、それによって契約違反が起きたコードの続行を中断し、他の場所からプログラムの実行を再開するという選択を取ることができます。しかし、それによって全ての契約注釈はその評価に(伴う違反ハンドラ呼び出しの可能性に)よって例外を送出する可能性があります。

この問題はP2900の早い段階で認識されており、事前条件と事後条件は関数本体内で評価されるものとしてnoexceptと相互作用し、contract_assert()を文にすることでnoexcept演算子を適用できなくするなどの工夫によって、契約注釈とnoexcept演算子の相互作用の問題を回避しています。

しかし、P2900の成熟とともにContractsの違反ハンドラを有効活用しようとする提案がいくつか提出されています

  • P3471R0 Standard library hardening
  • P3081R0 Core safety Profiles: Specification, adoptability, and impact
  • P3100R1 Making erroneous behaviour compatible with Contracts

これらの提案を標準に入れるかどうかはともかくとして、これらの提案ではいずれも違反ハンドラが使用されているため、これらの提案による実行時検査が有効になると、これらの提案が保護しようとしているUBが例外送出に変化する可能性があります。

例えば次のようなコードでは

Tool::~Tool() // noexcept by default
{ 
  for (int i = 0; i <= size(); ++i) {
    static_assert(noexcept(_vector[i]));
    _vector[i].retire(i);
  }
}

これはC++23においては正常にコンパイルされるコードです。しかし、P3081R0で提案されているように安全性プロファイルの適用によって境界チェックが導入された場合、_vector[i]の添え字アクセスは範囲外アクセスに伴う違反ハンドラ呼び出しを介して例外を送出する可能性があります。

このとき、このnoexcept演算子は何を返すべきなのか?それは境界チェックを行うプロファイルが適用されているかどうかで変化するべきなのか?という問題があります。さらに、違反ハンドラ呼び出しが式の外側で行われるというP3081が提案する方針を採択する場合でも、暗黙noexceptである外側のデストラクタで実行されることでstd::terminate()呼び出しに繋がってしまいます。

このように、これまでUBが発生していた箇所すべてで代わりに例外を送出するという考え方はnoexceptのセマンティクスと矛盾しており、例外安全性保障の考え方とも矛盾しています。

また、現在提案されている機能の中には任意の文が例外を送出しうるかをチェックできうる機能があります。

  • P3166R0 Static Exception Specifications
    • throw(auto)の指定によって、関数内のすべての文が例外を送出するかをチェックできる
  • P2806R2 do expressions
    • do式内の文全体が例外を送出するかをnoexcept演算子によってチェックできる(式になるので)

違反ハンドラを応用する提案がいくつも持ち上がっており、加えて文の例外検査を可能にする提案もあり、契約注釈と例外機構の相互作用について曖昧なままにしておくことはできず、明確な設計方針を示す必要があります。この提案は、それを促すためのものです。

提案では、この回答の候補として

  1. 任意の未定義動作および契約違反は例外を送出する可能性があり、例外を送出するかどうかはプログラマが制御可能
  2. 契約注釈に関する構成要素はすべて例外を送出しない
    • 未定義動作を制御された例外に変えることは不可能
  3. 契約注釈に関する例外は、なんらかの設定(コンパイラスイッチなど)によって例外を送出する/しないの観測結果が変化する
  4. これらの機能はnoexcept(true)だが、それでも例外を送出する可能性がある

完全にデメリットやコストのかからない選択肢はありません。1は例外安全性の直観に反し、2はバグを例外に変換してより上位でハンドルするという機能への扉を閉ざし、3は正しいプログラムがコンパイラスイッチによって異なる実行パスを取る可能性があり、4は環境(コンパイラnoexcept演算子の結果に依存するライブラリ)を信頼できる方法で例外を処理できなくします。

一方で、オプション1/2は他の機能への扉を閉ざすものの、明確で単純で教えやすいモデルを提供します。

P3542R0 Abolish the term "converting constructor"

converting constructorという規格用語を削除する提案。

変換コンストラクタ(converting constructor)と聞くと、クラス型のコンストラクタのうちその型と異なる型を1つ受け取ってその型のオブジェクトを構築するコンストラクタ、の様な意味であると認識すると思います。しかし、標準で使用されるconverting constructorという言葉は単に、非explictコンストラクタとして定義されています。

それによって、標準における変換コンストラクタには非explicitなコピー/ムーブコンストラクタやデフォルトコンストラクタ、複数引数を取るコンストラクタなどが含まれています。また、explictなコンストラクタであっても明示的変換の際には考慮されますが、標準の定義ではexplictなコンストラクタは変換コンストラクタではありません。

このように標準内での言葉としても問題がありますが、提案ではより問題として深刻なのはコミュニティで使用される言葉と意味が異なる点であると指摘しています。C++標準で使用されている用語は、C++コミュニティで使用される用語に明らかに大きな影響与えていますが、標準内でのconverting constructorという言葉の使用頻度の低さとその言葉から連想されるものとの乖離によって、2つの定義が競合しており、しかも標準の定義の方が有用性が低く分かりにくいためです。

converting constructorという言葉の自明さによって標準の定義を知らない人はそれに気づかずに使用し続けるでしょうし、知っている人は他者が使用している場合にどちらの定義を採用しているのか確認しないといけなくなり、自分が使用する場合もどちらの定義を採用しているのか明確にする必要に迫られます。

これらの理由から、この提案は converting constructor という言葉を non-explicit constructor という定義そのままの言葉に置き換えることを提案しています。

この提案は大きな変更なく、2025年2月の会議で採択され、C++26WDに適用されています。

P3543R0 Response to Core Safety Profiles (P3081)

P3081への応答文書。

P3081はプロファイル機能の一環として、安全性に関するプロファイルの具体的な動作についてを提案しています。そこでは既存のC++のコードをプロファイルを有効化して再コンパイルするだけで安全性を向上させる事に主眼を置いた機能が提案されていました。

P3081の詳細については以前の記事を参照

この提案は、P3081のいくつかのカテゴリごとに、懸念点や推奨事項などを提示しています。

  1. 安全ではない言語構造の拒否
    • これはP3081の提案の中で最も議論の余地の少ない部分
    • ただし、懸念点がいくつかある
      • 適用可能なエンティティとスコープの選択の詳細な検討
      • コードの一部に対してのプロファイル無効化方法の導入
      • よりきめ細かいレベルで制約を制御する方法の必要性
        • プロファイル内の特定のルール単位での無効化/有効化
        • 重複するルールを持つ異なるプロファイル同士の相互作用
  2. 実行時チェックの暗黙的な挿入
    • これはP3081の中で最も未発達で物議を醸す部分
    • P2900のContractsの実行時チェックと互換性が無い方法を導入しようとしている
      • 違反ハンドラのシグネチャ変更や評価セマンティクスの強制など
    • 安全性プロファイルによる新しい実行時チェックの導入は、Contractsの機能のみを使用するか、そのような機能を提供する将来の拡張と前方互換性のある方法で行う必要がある
      • この領域はSG21の所掌範囲内であり、SG21での承認が必要
      • P3100の仕事と重複があり、それと比較すると検討が足りない
    • 実行時チェックに関しては次の3つのいずれかの方針を取る事を提案する
      1. 新しい実行時チェックを導入する部分をすべて削除する
      2. P2900 Contractsを活用し、その範囲を超えた制約を課さない
      3. 実行時チェックはプロファイルによってオンにするのではなく、デフォルトとする
        • 宣伝効果が絶大
        • この方針採用する場合、問題は1の方針(における問題点)のみになる
    • また、一般的なコンテナ型に対する実行時チェックの導入には問題がある
      • ダックタイピングによってコンテナ型を識別するため、コンテナ型でないようなものや、特殊なコンテナ型に対して間違ったチェックを導入してしまう
      • ライブラリ実装がすでに提供しているチェックと重複する(事についての検討が必要
  3. 実行時動作の静かな変更
    • 例えば、P3081ではキャスト式の実行時動作のサイレント変更が提案されている
    • どの安全性プロファイルが有効であるかによって、プログラムのwell-definedな動作を意図的に変更するのは危険
    • そのようなコードが安全ではないのなら、そのコードを安全になるように修正するべき
    • 安全性がコンパイルを制限する以外の意味に影響を与えないことが重要
  4. コードの現代化提案
    • これはQoIと呼ばれる領域に踏み込んでいる
    • 問題領域を制限せずに無制限の静的解析をコンパイラに求めるべきではない
      • あらゆるケースにおいて上限のある時間計算量をもつソリューションに厳密に制限する必要がある
    • より詳細なプロファイルが導入されるにつれて、現代化の方法は1つに定まらなくなるかもしれない
      • その場合、実装間で変更内容が異なりうる
    • P3081の提案の一部は単にスタイルの好みが反映されている
    • 少なくとも一人の実装者は、この部分について深刻な懸念を表明している
      • 実装されない(できない)機能について時間を費やすべきか?

これらのことからこの提案では、C++26の期限内で合意を得るためには、1の安全ではない言語構造の拒否以外の部分を削除することを提案しています。特に

  • 全ての実行時チェックについては次のどちらか
    • より成熟した提案(SG21との共同作業と承認によるもの)が提出されるまで延期する
    • P2900をP3100と同様の方法で活用した実行時チェックであり、かつ期待される動作に対する新しい前方非互換な制限がない場合に限り、導入する
  • コンパイラが開発者の選択したキャストをサイレントに修正するような"Fix"のすべてを削除する
  • Modernize提案はすべて削除する

また、特定のC++機能・構文・キーワードによる二者択一の選択、を超えたよりニュアンスのある構文でユーザーが一般的な設計ルールやコーディング標準を表現するような方法を組み込むことについても、より熟考が必要としています。

おわり

この記事のMarkdownソース




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

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