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

文書の一覧

全部で113本あります。

一部の記事は次の方に手伝っていただいています、ありがとうございました

もくじ

N4984 WG21 June 2024 Admin Minutes of Meeting

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

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

N4985 WG21 2024-06 St Louis Minutes of Meeting

2024年6月にSt Louisで行われたWG21全体会議の議事録

N4986 Working Draft, Programming Languages -- C++

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

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

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

P0260R10 C++ Concurrent Queues

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

以前の記事を参照

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

  • St. Louis会議におけるSG1からのフィードバックを実装
  • コンセプトを3つに分割
  • try_*はロックフリーであることを要求(エラーコードbusyと共に
  • is_always_lock_freeを削除
  • capacity()を削除
  • discussion points forSG1、TS ship vehicleを削除
  • 設計部分の一般的なクリーンアップ

などです。

P0472R1 Put std::monostate in <utility>

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

std::monostatestd::variantにおいて空の状態を表現するための型です。その値はデフォルト構築状態しか取ることができず、メンバ関数はありません。しかし、std::variantの諸特性(コピー/ムーブ/比較など)を妨げないようにするために一通りのそれらの操作が可能になっており、regularコンセプトを満たす型になっています。

そのため、std::monostatestd::variantに限らず、情報が無い・状態を持たない・ある種の無効状態などの状態を表現する型として使用でき、特にテンプレートパラメータで型を指定する場合にそのような特別な状態を表現するのにvoid型の代わりに使用することができます。そのような場所では、voidの特殊性(値を取れない、regularでないなど)によって実装が面倒になる場合が多かったのですが、std::monostateはその代わりに使用可能な丁度便利な型になっています。

template<typename ExtraInformation = std::monostate>
class Data {
  ...

  // voidだとエラーになる
  ExtraInformation m_extraInformation;
};

このような用途により汎用的に使用できるように、std::monostate<utility>からでも利用できるようにしようとする提案です。

提案より、ユースケースの例

  • コンテナ型のテスト
    • カスタム(オリジナル)のvectorsetが要素型に対して不要な仮定を行っていないかどうかをチェックするのに使用可能
    • std::monostateはその要素型として使用可能な最もシンプルな型であり、std::monostateで動作することを確かめれば要素型に対する不用意な要求を行っていないことを確認できる
  • 特別な状態・場合を表すテンプレートパラメータとして
    • 例えば、std::futureはそれが同期するタイミング以外の情報が無いことを表すためにstd::future<void>特殊化が利用できる
    • ただ、voidの性質の悪さによりそれを検出して特別扱いするTMPコードが必要になる
  • 例外をラップして返すcallableラッパ
    • 渡されたcallableを呼び出し、その際に送出される例外をキャッチして戻り値に重畳して返す呼び出しラッパ実装を考える
    • この場合、その戻り値はstd::optional/std::expcetedになるが、ラップ対象のcallableの戻り値型がvoidだった場合のサポートが厄介
    • std::monostateを使用すると、戻り値によらずコードを共通化できるようになる

ただし、後方互換のために<variant>から削除することは提案していません。あくまで両方のヘッダで利用できるようにしようとするものです。

P0843R13 inplace_vector

P0843R14 inplace_vector

静的な最大キャパシティを持ちヒープ領域を使用しないstd::vectorであるinplace_vectorの提案。

以前の記事を参照

R13での変更は、条件付きconstexprの条件をis_trivial_t<T>trueの場合に変更したことです。R14での変更はよくわかりません。

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

P0876R17 fiber_context - fibers without scheduler

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

以前の記事を参照

このリビジョンでの変更も多いですが、概ね文言レベルの調整です。

P0963R3 Structured binding declaration as a condition

構造化束縛宣言を条件式を書くところで書けるようにする提案。

以前の記事を参照

このリビジョンでの変更は、提案する文言の修正と、この機能を有効化する場合のコーナーケースをcondition構文要素の外側でケアしたことなどです。

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

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

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

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

以前の記事を参照

R10での変更は

  • “the indicated operator”ではなく“op”を使用
  • 右辺オペランドsimd-size-typeがあるシフト演算子の制約を修正
  • P3275で削除されたnon-const operator[]に関する記述を削除
  • intrinsics conversionを推奨プラクティスにする
  • simd_flagsテンプレートパラメータを説明専用にする
  • simd_alignmentを実装定義にしない
  • “supported”を“enabled or disabled”に言い換え
  • [simd.overview]から[simd.mask.overview]へ改善された文言を適用
  • ブロードキャストコンストラクタに関するLWGコメントを追記
  • ブロードキャスト制約を使用しないようにジェネレータコンストラクタを規定しなおす
  • 隣接イテレータではto_addressを使用するようにする
    • 要素ごとに範囲をイテレーションするのではなく、範囲全体をmemcpyすることを許可する意図を明確にしている

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

  • C++26をターゲットとして、SG1とLEWG向けに対応
  • (改善および調整された)TS仕様をISにマージするように求める
  • TSの経験の結果としてABIタグを削除したことについての議論を追加し、現状を変更するために投票を求める
  • テンプレートパラメータTsimd_abi::fixed_sizeに追加
  • simd_abi::compatibleを削除
  • simd_abi::abi_stableを追加(ただし削除を要求する
  • GCCリリースでのTS実装について言及
  • 関連提案への参照を追加
  • [numbers]の節番号を最新のドラフトに調整
  • 未解決の質問を追加
    • [simd]の正しい節はどこ?
    • rangesとの統合
  • simd_maskジェネレータコンストラクタを追加
  • 見出しに一貫してsimdsimd_maskを追加
  • experimentalparallelism_v2名前空間を削除
  • N4808 (Parallelism TS 2)に対するdiff有無で文言を二回提示する
  • デフォルトのロード/ストアフラグをelement_alignedに設定
  • 条件付きexplicitコンストラクタによるキャストの一般化
  • 名前付きキャスト関数を削除

などです。

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

P2075R6 Philox as an extension of the C++ RNG engines

<random>にPhiloxというアルゴリズムベースの新しい疑似乱数生成エンジンを追加する提案。

以前の記事を参照

このリビジョンでの変更は、LWGのフィードバックを受けて提案する文言を修正したことです。

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

P2300R10 std::execution

P0443R14のExecutor提案を置き換える、任意の実行コンテキストで任意の非同期処理を構成・実行するためのフレームワークおよび非同期処理モデルの提案。

以前の記事を参照

R10での変更は

  • 修正
    • connectget_completion_signaturestransform_senderを使用するように修正
      • P3303R0を適用
    • ensure_startedstart_detachedexecuteexecute_may_block_callerを削除
      • P3187R1を適用
    • splitの仕様で、receiverが2回完了する可能性があったのを修正
    • stopped_as_optionalを子のsenderが2つ以上の値で完了する場合に対応できるように修正
    • queryablestoppable_sourcestoppable_callback_forのコンセプトを説明専用にした
  • 機能の強化
    • operation_stateコンセプトでは、操作状態モデルがqueryableである必要がなくなった
    • get_delegatee_schedulerクエリをget_delegation_schedulerに変更
    • 環境のreadread_envに変更
    • read_envsenderインスタンスを返すクエリのnullary形式を削除
      • get_scheduler()read_env(get_scheduler)の別記ではなくなった(他のクエリも同様)
    • 機能テストマクロ__cpp_lib_sendersを追加
    • transfercontinues_onに、onstarts_onに変更され、starts_oncontinues_onを組み合わせた新しいonアルゴリズムを追加
      • P3175R3を適用
    • ライブラリの概要にsimple-allocatorコンセプトが追加され、get_allocator()クエリの仕様がされに基づいて指定するようになった
    • 新しいonアルゴリズムで使用するために、説明専用のsenderアダプタwrite-envを追加

などです。

この提案は2024年6月の全体会議で承認され、C++26 WDに導入されています。

P2319R0 Prevent path presentation problems

filesystem::path.string()メンバ関数を非推奨にして、代わりのメンバ関数を追加する提案。

filesystem::path.string()メンバ関数はパス文字列をネイティブエンコーディングに変換したstd::stringを返します。ネイティブエンコーディングとは「OS依存のパス名の現在のエンコーディング」です。

これによって、Windowsでは次のようなコードにおいて

std::filesystem::path p(L"Выявы");

std::print("{}\n", p);
std::print("{}\n", p.string());

全てのコードページとローカライズ設定がベラルーシ語に設定されていて、ソースコードエンコーディングリテラルエンコーディングの両方がUTF-8である場合でも、.string()メンバ関数の結果は文字化けを起こします。

Выявы
�����

ネイティブエンコーディングとはリテラルエンコーディングでもロケールエンコーディングでもなく、この変換は通常何らかの損失を伴います。例えば、同じ条件のシステムで、次のようなコードはstd::runtime_error例外を送出します

std::filesystem::path p(L"Obrázky");
std::string s = p.string();

Windowsにおけるネイティブエンコーディングとは、コンソールに設定されたコードページとは別のアクティブコードページと呼ばれるエンコーディングであり、これは通常ASCII互換の非UTF-8エンコーディングになっています。一方で、Windowsにおけるpathオブジェクトはユニコードwchar_t文字列)で文字を保持しているため、.string()の呼び出しではユニコード文字列からの変換が入り、必ずしもすべての文字が変換できるわけではありません。

上記例外を投げる例は突き詰めるとWideCharToMultiByte()関数でエラーが起きていると思われますが、なぜエラーが起きるのかは不明です(おそらくERROR_NO_UNICODE_TRANSLATIONエラーだと思われる)。

アクティブコードページは実行時のコマンドやAPI呼び出しで変更されるほかPCのローカライズ設定によっても変化するため、.string()は実行時のそれらの設定を受けて結果が変わり、テスト環境ではエラーが起きず本番環境ではエラーが起こるということが容易に起こります。

まとめると、filesystem::path::string()には次のような問題があります

  • iostream、std::formatstd::printなどのほぼすべての標準テキスト処理およびI/O機能と互換性のないエンコーディングを使用する
  • エラーが非常に発生しやすく、プログラムが異なる環境にデプロイされた後や実行時構成変更後に発生する可能性のある文字列変換の問題が見過ごされやすい
  • POSIX環境では上記のような問題はないものの、追加のメモリ確保と文字列コピーを行う非効率なnative()でしかないため、移植性のあるプログラムにおいて使用が難しい

これらの事は、非英語圏C++ユーザーに不釣り合いな悪影響を及ぼし、C++言語を国際化対応されローカライズされたプログラムを記述するための言語としての魅力を削いでいます。

この提案では、filesystem::path::string()を非推奨にするとともに、その役割を分割して担う2つの関数を追加することを提案しています。追加する関数は次の2つです

また、同様の問題がある.generic_string()POSIXのパス形式にしたうえでパス文字列を返す)関数もgeneric_system_string()generic_display_string()の2つに分割します。

これにより、先程の例は基本的にdisplay_string()を使うことで改善されます

std::filesystem::path p(L"Выявы");

std::print("{}\n", p);
std::print("{}\n", p.display_string());
Выявы
Выявы
std::filesystem::path p(L"Obrázky");
std::string s = p.display_string(); // 例外を投げない

P2389R2 dextents Index Type Parameter

std::dextentsから、整数型の指定を省略する提案。

以前の記事を参照

このリビジョンでの変更は、提案する文言のコード中の斜体の表記を修正したことです。

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

P2422R1 Remove nodiscard annotations from the standard library specification

規格書における標準ライブラリの関数から、[[nodiscard]]を取り除く提案。

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

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

P2642R6 Padded mdspan layouts

std::mdspanpadding strideをサポートするためのレイアウト指定クラスを追加する提案。

以前の記事を参照

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

  • 提案している2つのレイアウトマッピングクラスのデフォルトコンストラクタのインライン定義を削除
    • 既にその後のテキストで指定されていたため
  • [mdspan.layout.general]の更新を修正
  • 保存された追加ストライドを初期化するためのコンストラクタの文言と事前条件を修正
  • LWGレビューからのフィードバックに対応

などです。

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

P2656R3 C++ Ecosystem International Standard

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

以前の記事を参照

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

  • parallel processを削除(現時点では関連性が無いため
  • レビュープロセスの明確な手順を指定
    • EWGの設計承認とCWGの文言承認の定期的なレビューに準ずる
  • 目標の条件と順序を微調整して、ドラフトの概要を反映する
  • 現在および近い将来の見通しを反映させるため、タイムラインを更新

などです。

P2664R7 Proposal to extend std::simd with permutation API

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

以前の記事を参照

このリビジョンでの変更は、P3299R0と一致するように、配列外参照するgather/scatter操作のデフォルトの動作をUBにしたことです。

P2686R4 constexpr structured bindings and references to constexpr variables

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

以前の記事を参照

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

  • ”constituent values”と”constituent references”の定義がわずかに変更された(意味は変わらない
  • 不正確だった”at a point [...] namespace scope”という表現は、名前空間スコープの最も近い次のポイントを参照するように変更された
  • どの変数がconstexpr-referenceableなのかを示す文言の例は、どの変数がconstexpr-referenceableではないのかを明確にするために更新
  • constexpr-referenceableの定義を明確にするために新しい用語を導入
  • ”constant initialized”とconstexpr変数宣言の要件の間の重複した指定を減らすために、新しい用語を導入
  • requires式によって導入された関数パラメータスコープは無視されるようになった
  • 定数評価中の構造化束縛のセマンティクスを表す追加の例を追加
  • ユースケースが無かったため、機能テストマクロを削除
    • 代わりに、__cpp_constexpr__cpp_structured_bindingsの両方をバンプ

などです。

この提案はCWGのレビューを完了していますが、実装経験が上がってくるのを待機しているようです。

P2761R2 Slides: Evaluating structured binding as a condition (P0963R2 presentation)

P2761R3 Slides: Slides: Structured binding declaration as a condition (P0963R2 presentation)

P0963R1の紹介スライド。

このリビジョンでの変更は明示的ではありませんが、スライドの説明を調整しているようです(なぜかピコ太郎氏が登場しています)。

P2769R2 get_element customization point object

tuple-likeなオブジェクトから特定のインデックスの要素を抜き出すCPOの提案。

以前の記事を参照

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

  • 標準でユーザー定義のタプル型をサポートする追加のモチベーションを追加
  • tuple-likeコンセプトに変更を提案

などです。

ここで提案されているget_elementCPOがユーザー定義のタプル型をサポートすべきモチベーションとは、現在の標準にそれを受け止める物が無いことです。

現在の標準ライブラリには5つのタプル互換な型が存在しており、タプルをサポートするライブラリはそれらの型をサポートしています。これらの型はタプルプロトコルを実装することでタプル互換を達成しているためユーザー定義型でも同様に達成できそうですが、std::getがカスタマイズポイントではないことによってそれは妨げられています。

一方、言語ではユーザー定義タプル型がきちんとサポートされている場所があります。例えば構造化束縛ではget()関数を適切に見つけるために特別なルールを追加しています。ただしこのような扱いは場所によって異なっており、elements_viewでは標準ライブラリの型以外をサポートしていなかったりします。

ここで提案しているget_elementCPOをstd::getに変わるタプルの要素取得のためのカスタマイゼーションポイントとしておくことで、これを使用してタプルプロトコルをユーザー定義型に対して完全にオープンにすることができるようになります。

P2848R1 std::is_uniqued

範囲内に重複する隣接要素がないかを調べる std::is_uniqued, std::ranges::is_uniqued<algorithm> に追加する提案。

以前の記事を参照

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

  • 機能テストマクロの追加
  • 命名に関する議論の追加

などです。

P2863R6 Review Annex D for C++26

P2863R7 Review Annex D for C++26

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

以前の記事を参照

R6およびこのリビジョンでの変更は主に追跡中の提案のステータス更新です。R7では追加でレビュー済みの提案に関する会議メモを記録しています。

P2865R5 Remove Deprecated Array Comparisons from C++26

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

以前の記事を参照

このリビジョンでの変更は、最新のWDに追随したことのみです。

P2866R3 Remove Deprecated Volatile Features From C++26

P2866R4 Remove Deprecated Volatile Features From C++26

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

以前の記事を参照

R3での変更は

  • すべてのワーキンググループにおけるC++26のレビューの概要を記録
  • std::atomicクラステンプレートの現在非推奨ではないものに対する変更を延期
  • 最新のWDに追随
  • Annex Cに欠けていた根拠を追加

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

  • St Louis会議でのCWGレビューを記録
  • 微妙な変更を説明するためにコアの文言計画を更新
  • Cとの互換性に関する懸念を収集するために、SG22に送付
  • volatile修飾された関数引数の削除に関するEWGの懸念を提起
  • コア言語とライブラリの文言更新

などです。

P2873R2 Remove Deprecated locale category facets for Unicode from C++26

C++20で非推奨とされたロケールカテゴリファセットをC++26で削除する提案。

以前の記事を参照

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

  • iostreamsに精通していない人向けにロケールカテゴリファセットに関する背景情報を追加
  • “locale dependent” という用語を “locale-specific” に置き換え
  • Wdeprecatedを使用してもGCCが非推奨警告を出力しないことを確認
  • 文言の更新

などです。

P2897R2 aligned_accessor: An mdspan accessor expressing pointer overalignment

P2897R3 aligned_accessor: An mdspan accessor expressing pointer overalignment

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

以前の記事を参照

R2での変更は

  • is_sufficiently_alignedからconstexprを削除
  • R1のLEWGレビューでのオプションの提案についての議論の追加
    • explicit変換コンストラクタと名前付きキャストに関して
    • detectably_invalidに関して
    • LWGによるR2のレビューが同時に進行する間、aligned_accessorメンバ関数の代わりにis_sufficiently_aligned<bit>の非メンバ関数とする設計を検討するように依頼
  • P2389R2が先にWDに入ったため、例におけるdextentsの使用をdimsに置き換え
  • aligned_accessorに小さなアライメントから大きなアライメントへの明示的変換コンストラクタが無い理由を説明するセクションを追加
  • 完全な実装とデモを含むCompiler Explorerリンクを追加

このリビジョンでの変更は、David Sankel氏からのレビューを適用したことです。

P2963R3 Ordering of constraints involving fold expressions

コンセプトの制約式として畳み込み式を使用した場合に、意図通りの順序付を行うようにする提案。

以前の記事を参照

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

  • CWGのレビューを受けての文言の改善
  • 両方の制約が等価なテンプレートパラメータを持つことを要求することで、空のパックがある場合の矛盾から保護する
    • これは、サイズが同じであることを保証する
  • foldから展開された制約の分解により、atomic制約が非bool型となる可能性があるためAnnex Cのエントリを追加

などです。

この提案は2024年6月の全体会議で採択され、C++26WDにマージされています。

P2989R2 A Simple Approach to Universal Template Parameters

より限定されたユニバーサルテンプレートパラメータの提案。

以前の記事を参照

このリビジョンでの変更は、ABIとinjected-class-namesに関するセクションを追加したことです。

P2996R4 Reflection for C++26

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

以前の記事を参照

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

  • Unicodeとの親和性を高めるためにname_of()関数ファミリを変更し、u8name_of(), u8qualified_name_of(), u8display_name_of()を追加
  • reflect_resultreflect_value(), reflect_object(), reflect_function()の3つの関数に分離
  • エイリアスのリフレクション(鏡像)の比較およびリンケージのルールを厳格化
  • is_noexcept()をより広いタイプのエンティティに適用可能なように変更
  • アクセス可能なクラスメンバをリフレクションするためのAPIを再構築
  • test_type, test_typestest_traitに変更
  • has_module_linkage()を追加
  • 変数とそのオブジェクトのリフレクションの違いを明確化
  • object_of()を追加
  • 文言の追加

などです。

P3006R1 Launder less

バイト配列上に構築した別の型のオブジェクトへのポインタをより直感的に取得できる様にする提案。

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

  • 「Impact on the optimizers」の説を追加
    • std::launderの使用は最適化を阻害するのみ、とのこと
  • 提案する文言を追加

などです。

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

P3037R2 constexpr std::shared_ptr

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

以前の記事を参照

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

  • 提案する文言の追加
  • いくつかの関数からconstexprを取り除いた

などです。

除外されたのは、例外やreinterpret_castなどの定数式では実行できない操作を含むものです。

P3044R1 sub-string_view from string

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

以前の記事を参照

このリビジョンでの変更は、関数名と関数に対する参照修飾に関する議論を追加したことです。

P3051R2 Structured Response Files

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

以前の記事を参照

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

  • 1度に1つの引数/オプションフィールドのみ許可
  • 引数とオプションの処理の概念を定義
  • オプションの混乱を避けるため、文言セクション名を"Structured Options"から"Structured Parameters"に変更
  • オプションをJSONオブジェクトに変更し、独自の構造化オプションモデルへ移行
  • 設計上の選択の根拠を追跡するため、"design considerations"セクションを追加
  • flagオプションはbool値としてより自然に表現できるため削除

などです。

P3064R2 How to Avoid OOTA Without Really Trying

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

以前の記事を参照

R1での変更は、アトミックロードのinventing, duplicating, もしくは repurposingが算術の基本法則に違反する可能性がある事を示す、非OOTAの例を追加

このリビジョンでの変更は、St. Louis会議でのフィードバックを取り入れ、OOTAを回避するために何も変更をする必要がないという主張を明確化したことです。

P3068R3 Allowing exception throwing in constant-evaluation

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

以前の記事を参照

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

  • bad_allocbad_array_new_lengthbad_typeidconstexpr
  • 文言からrecommended practiceを削除
  • dynamic_casttypeid空の例外送出を許可するために文言を調整
  • 機能テストマクロの追加

などです。

P3085R3 noexcept policy for SD-9 (throws nothing)

ライブラリ関数にnoexceptを付加する条件についてのポリシーの提案。

以前の記事を参照

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

  • P3313R0について、§ 8.3.3 Bloatに議論を追加
  • P3318R0について、§ 8.5.1 Security dangers of throwing exceptions while in an unknown stateに議論を追加

などです。

P3087R1 Make direct-initialization for enumeration types at least as permissive as direct-list-initializatio

スコープ付き列挙型の値の初期化時に、直接初期化を許可する提案。

以前の記事を参照

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

  • 別の設計について議論
  • 提案している設計の選択根拠を強化

などです。

提示された代替設計とこの提案の比較は次のようになります

設計オプション std::byte b(0) std::byte b(-1) std::byte b(0.f)
現状維持
リスト初期化と同等にする
特定の変換の制限
この提案

この提案では非リスト直接初期化時の一貫しない挙動を修正しようとしており、列挙型の変換を安全に拡張することを目的としていないため、その観点からは他の選択肢は望ましくないとしています。また、実装品質の問題として、この提案で許可される変換で縮小変換が起こる場合に警告を発することもできます。

この提案はEWGのレビューにおいてこれ以上追及しないことが決定されました。

P3094R3 std::basic_fixed_string

NTTPとして使用可能なコンパイル時文字列型であるstd::basic_fixed_stringの提案。

以前の記事を参照

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

  • 文字のリスト({...}ではなく)を取るコンストラクタの動機を追記
  • 容量関連の操作を修正し、std::integral_constantを適切に使用するようになった
  • 非メンバswapを使用する根拠を追記
  • Annex Cエントリを追加
  • その他文言の改善

などです。

P3096R2 Function Parameter Reflection in Reflection for C++26

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

以前の記事を参照

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

  • EWGでの投票結果を追加
  • 提案する文言の改善
  • template forの代わりにexpandを使用する例を追加
  • 実装経験のメモを追加

などです。

P3124R0 2024-02 Library Evolution Poll Outcomes

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

次の5つの提案が投票にかけられ、最初の4つはC++26を目指してLWGに転送されました。

最後のものは作業予定を承認した形になります。

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

P3137R2 views::to_input

入力の範囲をinput_rangeに弱めるRangeアダプタ、views::inputの提案。

以前の記事を参照

このリビジョンでの変更は、機能テストマクロを追加したことです。

P3138R2 views::cache_last

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

以前の記事を参照

このリビジョンでの変更は、SG1からのフィードバックに従いconstメンバ関数のスレッドセーフ性保証の例外の追加をいったん削除したことと、機能テストマクロを追加したことです。

この提案では[res.on.data.races]の条項に対して提案しているviews::cache_lastを例外として追加しようとしていましたが、その文言にはすでに"unless otherwise specified"という文言によって例外が考慮されています。しかし、どのようにしてそれを個別のライブラリ機能(特にテンプレート)に適用すべきかが明確ではなく、これはこの提案ではなくLWGのIssueとして対処されることになりました。

P3144R1 Deprecate Delete of Incomplete Class Type

P3144R2 Deleting a Pointer to an Incomplete Type Should be Ill-formed

不完全型のポインタに対するdeleteを非推奨にする提案。

以前の記事を参照

R1での変更は

  • 2024/05/15のEWGレビューの記録を追加
  • 推奨されるソリューションはill-formedであり、非推奨ではないことを明確化
  • 推奨されるソリューションについての文言を追加
  • C++26をターゲットとしてCWGに転送

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

  • 採用されたソリューションを反映するようにタイトル変更
  • CWGレビューの記録を追加
  • P2795の最終リビジョンへ参照を更新

などです。

このリビジョンで不完全型のポインタに対するdeleteについて最終的に選択されたソリューションは、非推奨やEBとするものではなく、C++26で即座にill-formedとすることです。

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

P3149R4 async_scope -- Creating scopes for non-sequential concurrency

P3149R5 async_scope -- Creating scopes for non-sequential concurrency

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

以前の記事を参照

R4での変更は

  • spawn_future()の呼び出し元は、オプションの環境引数からストップトークンを提供できるようにした
  • [[nodiscard]]を削除
  • simple_counting_scope::token::token()counting_scope::token::token()explicitかつ説明専用にする
  • 冗長なasync_scopeコンセプトを削除
  • let_with_async_scopeの最後の名残を取り除いた
  • 新しいSpecificationセクションにいくつか文言を追加

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

  • ネストしたsenderoperation stateはスコープの参照カウントを減らす前に、子のoperation stateはを破棄する必要がある事を明確化
  • 命名に関する議論を追加
  • メモリアロケータの生存期間に関する懸念と、それを解決するためのいくつかのオプションについて説明を追加

などです。

P3161R2 Unified integer overflow arithmetic

オーバーフローを処理可能な整数演算の提案。

以前の記事を参照

このリビジョンでの変更は、would_cast_modify()を削除して既存のin_range()に変更し、いくつかの例を更新したことです。

P3164R1 Improving diagnostics for sender expressions

P3164R2 Improving diagnostics for sender expressions

提案中のExecutorライブラリにおいて、senderチェーンのエラーを早期に報告するようにする提案。

以前の記事を参照

R1での変更は

  • transform_completion_signaturesの仕様を変更して、completion_signatures<>の特殊化ではない型を伝播する
  • カスタマイゼーションポイントlet_value, let_error, let_stoppedについては、呼び出し可能オブジェクトの可能な戻り値型が全てsenderであることを必須にする
  • senderを積極的にconnectするアルゴリズムでは、RequiresをMandatesに変更

などです。

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

  • completion_signatures_of_t<Sndr, Env...>sender_in<Sndr, Env...>制約を削除
  • 最後の手段として、get_completion_signatures(sndr, env)get_completion_signatures(sndr)へディスパッチするように指定
  • 実装者がsenderアダプタアルゴリズムの完了シグネチャを利用して型エラーを伝播することを奨励する
  • get_completion_signaturesから返される型がcompletion_signatures<>の特殊化ではない場合はエラーを表すものと推論する設計の決定に関する議論を追加

などです。

P3168R2 Give std::optional Range Support

std::optionalrangeにする提案。

以前の記事を参照

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

  • LEWG Feedback#P3168R1 サブセクションにLEWG投票結果を記載
  • Implementation experience#Beman.Optional26 サブセクションに実装経験について追記

などです。

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

P3175R3 Reconsidering the std::execution::on algorithm

P2300のstd::execution::onアルゴリズム命名について再考する提案。

以前の記事を参照

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

  • onアルゴリズムに標準タグ型を再度付加
  • start_oncontinue_onの名前をそれぞれ、starts_oncontinues_onに変更
  • onの2引数の場合の仕様のバグを修正
    • get_scheduler(rcvr)get_scheduler(get_env(rcvr))に置き換え
  • [exec.on]/p1のonの2つの形式の説明を改善
  • 説明専用型none-suchnot-a-schedulerに変更
  • LEWGがonのカスタマイズに関する意味論制約を別の方法で指定することを望んでいるという編集メモを追加

などです。

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

P3178R0 Retrieval of Exception Information

P3178R1 Retrieval of Exception Information

現在投げられている例外もしくはexception_ptrから例外オブジェクトの情報を取得する関数の提案。

catch(...)の形のcatch節においては、例外オブジェクトの型は全く分からず、そのtype_infoを得る方法がありません。また、exception_ptrを取得したとしても、そのexception_ptrが参照している例外オブジェクトの型情報を得ることはできません。さらにどちらの場合も、例外オブジェクトのアドレスを取得することもできません。

例えば、C++のプログラムは共有ライブラリと動的にリンクされて利用されることがありますが、そのような動的ライブラリではしばしば、その例外仕様についてきちんと記述されていないことがあります。さらには、LoadLibrarydlopenなどによって事前情報のないライブラリと動的にリンクされることもあります。

このような場合に、共有ライブラリの関数を呼び出すメインのプログラムでは、共有ライブラリ内からの例外送出に対応するために、例えば次のように例外を処理します

#include <exception>  // exception, exception_ptr
#include <new>        // bad_alloc
#include <typeinfo>   // typeid, type_info

extern void SomeLibraryFunction(void) noexcept(false);

int main(void) {
  try
  {
    SomeLibraryFunction();
  }
  catch(std::bad_alloc const&)
  {
    // メモリが足りない場合
  }
  catch(std::exception const &e)
  {
    std::type_info const &ti = typeid(e);
    // std::exceptionはポリモルフィックなクラスなので、そのtypeid()は派生先の型情報を提供する
  }
  catch(...)
  {
    std::exception_ptr const p = std::current_exception();
    // 例外オブジェクトへのポインタは取得できるものの、何が送出されてきたのか分からない・・・
  }
}

この提案はまず、このような場合に現在送出されている例外オブジェクトの型情報(std::type_info)を取得するライブラリ関数を提供することを提案しています。

catch(...)
{
  std::type_info const &ti = std::exception_typeid();
  // 送出されている例外オブジェクトの型情報を得る

  // あるいは、exception_ptrから取得する
  std::exception_ptr const p = std::current_exception();
  
  std::type_info const &ti = std::exception_typeid(p);
}

type_infoから取得できる型名は名前マングリングされているものではあるものの、特定の関数やクラス名はそのまま含まれているため、文字列検索などでエラーについての手掛かりを得ることができます。

また、共有ライブラリの別のユースケースとして、サードパーティプラグインのサポートがあります。このような場合、予め共有ライブラリが提供する関数に制約が課されており、そのうちの一つとしてプラグイン(の共有ライブラリ)から送出されうる例外のtype_info配列を返す次のPlugin_GetExceptions()のような関数があります

std::type_info const *const *Plugin_GetExceptions();

プラグインをサポートするメインのプログラムでは、プラグインのロード時にこの配列を取得しておき、プラグイン処理から例外が送出されてきた場合にこの配列を参照することで、プログラムが同じようにエクスポートしている例外ハンドラを呼び出すなど、例外に特化したエラーハンドリングが可能になります。

catch(...)
{
  // 送出されてきた例外のtype_infoを取得
  std::type_info const &ti = std::exception_typeid();

  // 候補例外リストを検索し、適切なハンドラを呼び出す
  if ( nullptr != plugin_exceptions.find(&ti) ) {
    void (*const handler)(void*) = handlers[ std::type_index(ti) ];

    // プラグインの提供するハンドラに例外オブジェクトへのアドレスを渡す
    handler( std::exception_object() );
  }
}

このような場合に、送出されてきた例外オブジェクトのアドレスは、メインのプログラム側では有効に使用できないかもしれませんが、プラグイン内では有効活用できる可能性があり、それを取得してハンドラにコールバックするとより便利です。しかし現在のところ、このように現在送出されている例外オブジェクトのアドレスを取得するポータブルで安全な方法はありません。

この提案ではそれを、std::exception_object()によって取得可能にします。

この提案で提供される関数の宣言は次のようになります

// <exception>内
namespace std {
  type_info const &exception_typeid() noexcept;
  type_info const &exception_typeid(exception_ptr const &p) noexcept;

  void *exception_object() noexcept;
  void *exception_object(exception_ptr const &p) noexcept;
}

exception_typeid()は例外オブジェクトの型情報(type_info)を取得するもので、exception_object()は例外オブジェクトのアドレスを取得するものです。どちらも、引数無しのものはcatch(...)節内で処理中の例外について取得し、exception_ptrを引数に取るものはそのexception_ptrが参照する例外オブジェクトについて取得します。

この提案ではさらに、プラットフォーム固有の例外事情(WindowsのSEHやPOSIXのForced unwindingなど)にもこれらの機能を対応可能にすることを提案しています。

SEHは通常C++の例外ハンドラではハンドルできませんが、/EHaオプションを指定することでcatch(...)節でハンドル可能になります。このときcurrent_exception()は有効なexception_ptrを返すものの、そのtype_infoは利用できません。この場合、exception_typeid()の戻り値をtypeid(void)typeid(_s__se_exception)のような特別な型情報を返し、SEH例外オブジェクトの実体はunsigned intなのでexception_object()の戻り値はそのアドレスを返すことを推奨しています。

例外が送出されていないにもかかわらずスタックの巻き戻しが起こるforced unwindingの場合でもcatch(...)節でハンドルすることができますが、この場合、exception_typeid()の戻り値をtypeid(void)typeid(abi::__forced_unwind)のような特別な型情報を返し、またexception_object()の戻り値をnullptrにすることを推奨しています。

P3179R2 C++ parallel range algorithms

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

以前の記事を参照

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

  • シリアルRangeアルゴリズムと非Rangeの並列アルゴリズムの提案されている違いをまとめた
  • 並列Rangeアルゴリズムで複数の範囲を入力に取るものは、入力のうち1つだけが境界あり(sized_range)であればよいように緩和
    • 残りの入力はsizedである必要は無くなった
  • 出力範囲を取る既存のRangeアルゴリズムの一覧を追加
  • 出力に範囲を使用するための引数と緩和策を更新
  • random_access_rangeをサポートする引数を追加
  • 提案された設計と一致するように例示しているfor_eachシグネチャを更新

などです。

P3182R1 Add container pop methods that return the popped value

標準ライブラリのシーケンスコンテナとコンテナアダプタに、値を取り出して返す関数を追加する提案。

以前の記事を参照

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

  • コンテナアダプタが内部で使用するシーケンスコンテナに対して変更を行うようにした
  • 例外安全性に関する説明を拡張
  • その他の設計上の決定に関する説名を追加
  • この提案による恩恵を受けうるコードの量に関する見積もりを追記
  • 関数名の変更

などです。

この提案では、提案する関数名が.pop_front_value().pop_back_value()の2つになり、これらの関数はコンテナアダプタだけではなくシーケンスコンテナ(std::vector, std::deque, std::list, std::forward_liststd::string)にも追加(片方しかないものもある)され、コンテナアダプタの関数は内部コンテナの関数を呼び出して処理を行うようになりました。

P3212R0 The contract of sort()

std::ranges::sortの関数契約をP2900の契約プログラミング機能で記述してみた実験の報告書。

使用したstd::ranges::sortイテレータペアを受け取るオーバーロードで、次のような宣言となるものです

template<random_access_iterator I, 
         sentinel_for<I> S, 
         class Comp = ranges::less,
         class Proj = identity>
  requires sortable<I, Comp, Proj>
constexpr I
  ranges::sort(I first, S last, Comp comp = {}, Proj proj = {});

まず現在でも、コンセプトによって関数契約の一部が表現されて指定されています。コンセプトには構文要件と意味論要件があり、構文要件はコンパイラによってチェックされていますが、意味論要件は呼び出し側が満たすべきものです。

また、sortableコンセプトは次の概ね3つのコンセプトからなる少し複雑なコンセプトです

  • regular_invocable
  • strict_weak_order
  • permutable

これらは、比較関数compに対して範囲[first, last)が等しさを保持することを要求しています。

次に、範囲に対する一般的な要件として、[first, last)が有効な範囲である必要があります。さらに、sortの実行中にこの範囲が別の所から変更されないことも呼び出し側が保証する必要があります。

最後に、sort固有の要件(事後条件)があります

  • 結果の範囲はcompprojに関してソートされる
  • 出力範囲は入力範囲の順列となる
  • N = last - firstとして、compprojの呼び出し回数はO(N log N)

これらの要件をP2900の機能によってコードにエンコードすることを考えます。

しかしまず、特定の型があるコンセプトのモデルとなっているかを確認することは必ずしも可能ではありません。ムーブによって値が実際にムーブされるかどうかをチェックすることも型がコピーや比較を実装していないとできませんが、sort()は必ずしもそれを求めておらず、契約チェックのために新しいコンセプトを追加するのは正しい行いではないでしょう。

提供された述語がstrict_weak_orderコンセプトを満たすかどうかはO(N^2)ではあるものの評価可能です。ただし、このチェックを追加するとsort()の計算量要件が破られます。

このような要件を標準で指定するためには次の2つの選択肢が考えられます

  • 標準ライブラリの規定で関数に契約注釈が指定されていても、実装は必ずしもそれに倣う必要が無いことを許可する
  • 実行時に評価されないアサーションを表すラベルを導入する
    • 以前これはauditと呼ばれていた

auditで指定するとすると次のようになります

template<random_access_iterator I, sentinel_for<I> S, class Comp, class Proj>
  requires sortable<I, Comp, Proj>
constexpr I ranges::sort(I first, S last, Comp comp = {}, Proj proj = {})
  pre audit(is_strict_weak_order(first, last, comp, proj));  

同様に、等しさの保持をチェックすることもできません。各引数のペアについてcompを何回か呼び出して同じ値を返すかチェックすることはできますが、前述のようにここではoperator==が必ずしも使用可能ではありません。また、何回チェックしたとしても、それが常に同じ値を返すことの保証にはなりません。

operator==が使用可能な場合にのみ事前条件がチェックされる、という構文をpre() requiresであらわすと、この要件は次のように書けます

pre (maybe_is_equality_preserving(first, last, comp, proj)) 
  requires equality_comparable<typename iterator_traits<I>value_type>;  

入力の範囲が有効であるかどうかも、厳密にチェックすることはできません。ただし、形式的に記述することはできる(範囲を一回何もせずにイテレーションする)ので、そのような述語を使用して契約を記述することはできます。

このような形式的にしか表現できない(実質的なチェックを行えない)述語を使用して契約条件を記述することには一定の価値があります。例えば、ユーザーとIDE等ツールはそれを読み取ることができます。そして、ツールはその情報を各種分析に使用できます。

このような種類の契約アサーションを表現するためには契約注釈もしくは関数宣言に対してそれを表明する新しい宣言を追加する必要があります

template<random_access_iterator I, sentinel_for<I> S>
  axiom is_valid_range(I first, S last);   // 宣言のみで定義されない
  
template<random_access_iterator I, sentinel_for<I> S, class Comp, class Proj>
  requires sortable<I, Comp, Proj>
constexpr I ranges::sort(I first, S last, Comp comp = {}, Proj proj = {})
  pre axiom(is_valid_range(first, last));  // axiom事前条件、評価されない

最後にsortの事後条件特に「出力範囲は入力範囲の順列となる」という条件のチェックを行うためには、入力範囲をコピーして保存しておいて、関数の終了までそれを保持する必要があります。これを行える構文は今は無いので、例えばラムダキャプチャのような構文を使用してそれを行うことにすると、次のように記述できます

template<random_access_iterator I, sentinel_for<I> S, class Comp, class Proj>
  requires sortable<I, Comp, Proj>
constexpr I ranges::sort(const I first, const S last, Comp comp = {}, Proj proj = {})
  post audit [in = vector(first, last)] (is_permutation(first, last, in.begin(), in.end())); 

ここで範囲をキャプチャするのにstd::vectorを選択しましたがこれによってsort()が特定のコンテナに依存してしまうことになります。さらに、これはstd::vectorの構築という元の文章で指定された契約以上のものを表しています。

これはまた副作用を伴う術語にもなっており、この事後条件はメモリを確保し、O(N)でコピーし、例外送出の可能性もあります。さらに、キャプチャ動作は関数開始時に発生し、このチェックはO(N^2)の計算量となります。このような事後条件は静的分析で活用されるのは想像しづらいものがあります。

個別に挙げてきたアサーションを全て組み合わせると次のようになります

template<random_access_iterator I, sentinel_for<I> S, class Comp, class Proj>
  requires sortable<I, Comp, Proj>
constexpr I ranges::sort(const I first, const S last, Comp comp = {}, Proj proj = {})
  pre axiom (is_valid_range(first, last))
  pre audit (is_strict_weak_order(first, last, comp, proj))
  pre audit (maybe_is_equality_preserving(first, last, comp, proj)) 
    requires equality_comparable<typename iterator_traits<I>value_type>
  post (ranges::is_sorted(first, last, comp, proj))
  post audit [in = vector(first, last)] (is_permutation(first, last, in.begin(), in.end()));

これらのアサーションのうち、現在のP2900で表現可能なのは1つだけです。

この経験から、標準ライブラリ機能に契約アサーションを追加する場合に、その目標は何なのか?という問いを行う必要がある事がわかります。答えは次の2つのどちらかでしょう

  1. ツールに契約違反検出に有効なあらゆるヒントを提供する
  2. 型・コンセプト・指定子が表現できていない契約の部分をコードで記述する

先程の宣言は1の目標を満たしていますが、2の目標のためには少し過剰です。既存コンセプトが表現している意味論要件のチェックを取り除くと、幾分宣言がスリムになります

template<random_access_iterator I, sentinel_for<I> S, class Comp, class Proj>
  requires sortable<I, Comp, Proj>
constexpr I ranges::sort(const I first, const S last, Comp comp = {}, Proj proj = {})
  pre axiom (is_valid_range(first, last))
  post (ranges::is_sorted(first, last, comp, proj))
  post audit [in = vector(first, last)] (is_permutation(first, last, in.begin(), in.end())); 

なお、この文書は実験報告書であり何らかの提案をするものではありません。

P3223R1 Making std::istream::ignore less surprising

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

以前の記事を参照

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

  • to_int_typeに対する暗黙の仮定を避けるための議論を拡張
  • 提案している新しいオーバーロードにConstraintsを追加
  • std::basic_istreamではなくstd::istreamに特化したものになるようにタイトルを調整

などです。

P3235R1 std::print more types faster with less memory

P3235R2 std::print more types faster with less memory

P3235R3 std::print more types faster with less memory

std::printの効率的な実装をより拡大して適用する提案。

以前の記事を参照

R1での変更は

R2での変更は

  • LEWGからのフィードバックに従い、rangeのフォーマッタを対象にしないようにした

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

  • 機能テストマクロ__cpp_lib_printを更新するための手順を追加
  • <chrono>のdration型のRepテンプレートパラメータはユーザー定義の算術型likeな型になる可能性があるため、Repがカスタマイズされているかに応じて条件付きで(<chrono>のdration型の)オプトインを有効化するようにした

などです。

この提案C++23へのDRとして、2024年6月の全体会議で採択されています。

P3245R1 Allow [[nodiscard]] in type alias declarations

[[nodiscard]] 属性を型エイリアス宣言で使用できるようにする提案。

以前の記事を参照

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

  • 2024年6月の全体会議でのEWGIレビューにおける投票結果を追加
  • モチベーションを拡張
  • コーナーケースの例を追加

などです。

P3248R1 Require [u]intptr_t

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

以前の記事を参照

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

  • "Design"セクションにCとC++間のヘッダファイルの不一致に関するセクションを追加
  • [u]intptr_tを要求するC言語の取り組みに関するコンテキストを追加
  • Memory Taggingに関する説明を追加
  • [u]intptr_tのC23仕様について追記
  • 準拠実装と非準拠実装への影響分析を追加

などです。

P3255R1 Expose whether atomic notifying operations are lock-free

std::atomicの通知・待機系関数がロックフリーとは限らない場合がある問題について修正する提案。

以前の記事を参照

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

  • wait_is_signal_safewait_is_always_signal_safestd::atomic_is_signal_safeを追加
  • その他文言の修正

などです。

このリビジョンでは、以前の3つの提案に加えて

  • std::atomic_flagstd::atomicstd::atomic_refメンバ関数.wait_is_signal_safe()とメンバ定数notify_is_always_signal_safeを追加し、std::atomic_notify_is_signal_safe()フリー関数を追加
    • これらの関数および定数の値は、対応するアトミック型の待機操作がUBなしでシグナルハンドラ内で使用できるかを表すbool

が追加されました。

P3265R2 Ship Contracts in a TS

P3265R3 Ship Contracts in a TS

契約プログラミング機能をまずTSとして出荷すべき、とする提案。

以前の記事を参照

変更点が明確化されていないのでこのリビジョンでの変更はよくわかりませんが、DG's questions from P4000というセクションが追加されており、そこではTSを発行するにあたって収集すべき質問リストが提示されているようです。

などです。

P3288R1 std::elide

P3288R2 std::elide

P3288R3 std::elide

コピーもムーブできないクラス型のprvalueの生成を遅延するライブラリ機能の提案。

以前の記事を参照

R1での変更は

  • std::elideクラスをfinal指定しない
  • コンストラクタに1引数しかない場合にテンプレートのインスタンス化が失敗する
  • テンプレートのインスタンス化はtrueと評価されるtypedefタグの存在によって失敗する

R2での変更は

  • optional/varinat.emplace_invoke()を追加する代替案を追記
  • std::elideは定数式で使用可能
  • std::construct_at()は配置newの代替として追加されており、定数式で使用できる
  • Tはコピー構築可能である必要があるため、std::anyの例を削除
  • boost::static_vectorの例を追加
  • このクラスを検出する入れ子型名をtag_elideに変更

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

  • 関数ポインタと非常に小さなトリビアルな関数の最適化により、参照tupleではなくラムダを使用するように実装を変更
  • 1引数コンストラクタテンプレートでstd::elideを推論したい場合のために、std::elide_c1を使用する
  • eliderの連鎖はoperator()で可能になった
  • tag_elidetag_tempfail_ctor_soleparamにリネーム
  • std::elide_c1によって不要になったため、std::elideから派生する例を削除

などです。

素直なstd::elideの実装だと、次のようにコンストラクタテンプレート(特に一引数のもの)において変換が起こらずにstd::elideが直接観測されてしまいます

class AwkwardClass {
  std::mutex m;  // cannot move, cannot copy
public:
  template<typename T>
  AwkwardClass(T &&arg) {
    cout << "In constructor for AwkwardClass, \n"
            "type of T = " << typeid(T).name() << endl;
  }
};

AwkwardClass ReturnAwkwardClass(int const arg) {
  return AwkwardClass(arg);
}

int main(int const argc, char **const argv) {
  std::optional<AwkwardClass> var;
  var.emplace( std::elide(ReturnAwkwardClass, -1) );
}

この例は次のような結果を出力します

In constructor for AwkwardClass,
type of T = std::elide< AwkwardClass, AwkwardClass (&)(int), int&& >

理想的にはここでT = intになればいいのですがこれは困難なので、tag_tempfail_ctor_soleparamという入れ子型を使用して次のように回避することを提案しています

template<typename... Params>
  requires (
    !(
      (1u==sizeof...(Params)) &&
      (std::has_tag_tempfail_ctor_soleparam_true<Params> || ...)
    )
  )
AwkwardClass(Params&&... arg) {
    ( std::cout << "In constructor for AwkwardClass, type of T = " << typeid(Params).name() << std::endl, ... );
}

既存の標準ライブラリ型にこれを追加して回るのは大変なので、この提案ではコア言語を変更してどのクラスのコンストラクタもstd::elideの特殊化をその唯一の引数として持てないようにすることを提案しています。実装的には上記のような制約を書くコンストラクタに追加することになります。

P3290R1 Integrating Existing Assertions With Contracts

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

以前の記事を参照

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

  • ライブラリAPIsource_locationオーバーロードを追加
    • 違反ハンドラの直接呼び出しを行うAPIに対して追加
  • assertマクロの動作を変更するための制御マクロを追加
  • partial_contract_assertの提案を削除

などです。

P3294R1 Code Injection with Token Sequences

トークンシーケンスを用いてコード注入によるコンパイル時コード生成機能の提案。

以前の記事を参照

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

  • トークンシーケンスを導入する構文を^{...}に変更(以前は@tokens{...}
  • トークンシーケンス作成時の外部環境のキャプチャ構文(interpolator)を変更
    • $eval(e) -> \(e)
    • $eval(e) -> \tokens(e)
      • eトークンシーケンスをリフレクションしたmeta::infoである場合、対応するトークンに置き換えられる
    • $id(e, ...) -> \id(e)
  • declare [: e :]を削除
  • inject()queue_injection()に変更
  • 提案の多くを実装し、例を実行したリンクを追記
  • 衛生的マクロの引数型をstd::meta::infoに変更

などです。

以前のサンプルコードをこのリビジョンで改めて書き直すと次のようになります

std::tupleのストレージを定義する例

template <class... Ts>
struct Tuple {
    consteval {
        // パラメータパックTsから個別のmeta::info型を取得し保存
        std::meta::info types[] = {^Ts...};

        // Tsの型ごとにメンバ変数を宣言する
        for (size_t i = 0; i != sizeof...(Ts); ++i) {
            queue_injection(^{
              [[no_unique_address]]
              [:\(types[i]):] \id("_", i); 
            });
        }
        /* Tsの型名をTi(iは数値)とすると
        [[no_unique_address]]
        Ti _i;
        のようなメンバ宣言がTupleクラスの定義に注入される
        */
    }
};

std::enable_ifを定義する例

template <bool B, class T=void>
struct enable_if {
    consteval {
      // Bがtrueの場合にのみ、`using type = T;`を注入する
      if (B) {
          queue_injection(^{ using type = T; });
      }
    }
};

簡単なプロパティ機能を注入する関数を定義する例

consteval auto property(std::meta::info type, std::string_view name) -> void {
  // メンバ変数名のトークンシーケンスを作成
  auto member = ^{ \id("m_"sv, name) };

  // メンバ変数宣言を注入
  // 型名をTとすると
  // T m_name;
  // のような宣言が注入される
  queue_injection(^{ [:\(type):] \tokens(member); });

  // ゲッター関数宣言を注入
  // 型名をTとすると
  // auto get_name() -> T const& {
  //   return m_name;
  // }
  // のような宣言が注入される
  queue_injection(^{
      auto \id("get_"sv, name)() -> [:\(type):] const& {
          return \tokens(member);
      }
  });

  // ゲッター関数宣言を注入
  // 型名をTとすると
  // auto set_name(T const& x) -> void {
  //   m_name = x;
  // }
  // のような宣言が注入される
  queue_injection(^{
      auto \id("set_"sv, name)(typename [:\(type):] const& x)
          -> void {
          \tokens(member) = x;
      }
  });
}


// 使用例
struct Book {
    consteval {
        property(^std::string, "title");
        property(^std::string, "author");
    }
};

int main() {
  Book b:

  b.set_author("太宰治");
  b.set_title("人間失格");

  std::string author = b.get_author();
  std::string title = b.get_title();
}

アサーションマクロの例

consteval auto assert_eq(@tokens a,
                         @tokens b) -> info {
  return @tokens {
    do {
      // aの式文字列と式の評価結果を取得
      auto sa = $eval(stringify(a));
      auto va = $eval(a);

      // bの式文字列と式の評価結果を取得
      auto sb = $eval(stringify(b));
      auto vb = $eval(b);

      if (not (va == vb)) {
        // アサートが失敗した場合、それぞれの式の評価結果と場所を出力して終了させる
        std::println(
            stderr,
            "{} ({}) == {} ({}) failed at {}",
            sa, va,
            sb, vb,
            $eval(source_location_of(a)));

        std::abort();
      }
    } while (false);
  };
}
consteval auto assert_eq(meta::info a, meta::info b) -> meta::info {
  return ^{
    do {
      // aの式文字列と式の評価結果を取得
      auto sa = \(stringify(a));
      auto va = \tokens(a);

      // bの式文字列と式の評価結果を取得
      auto sb = \(stringify(b));
      auto vb = \tokens(b);

      if (not (va == vb)) {
        // アサートが失敗した場合、それぞれの式の評価結果と場所を出力して終了させる
        std::println(
            stderr,
            "{} ({}) == {} ({}) failed at {}",
            sa, va,
            sb, vb,
            \(source_location_of(a)));
        std::abort();
      }
    } while (false);
  };
}
// こう書くと
assert_eq!(42, factorial(3));

// こう展開される
do {
  auto sa = "42";
  auto va = 42;

  auto sb = "factorial(3)";
  auto vb = factorial(3);

  if (not (va == vb)) {
    std::println(
        stderr,
        "{} ({}) == {} ({}) failed at {}",
        sa, va,
        sb, vb,
        /* some source location */);

    std::abort();
  }
} while(false);

P3296R1 let_with_async_scope

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

以前の記事を参照

このリビジョンでの変更は明確ではないですが、P3149R3の変更を適用してサンプルコードや一部の文書を修正したことの様です。

P3297R1 C++26 Needs Contract Checking

C++26で契約プログラミング機能を出荷すべきとする提案。

以前の記事を参照

このリビジョンでの変更は明確ではないですが、一部の文章が修正・加筆されているほか、"Who Watches the Watcher?"セクションが追加され、そこでは契約条件式にUBが含まれている場合の影響について論じています。

"Who Watches the Watcher?"セクションでは、契約条件式にUBが含まれてしまったとしても現状より悪くなることはなく、安全性とはシステム全体の性質であるためそこの責任はWG21にはなく、全ての契約条件式からUBを取り除く実用的な方法を待つ機会費用が早期に導入された場合のメリットを大きく上回るとしています。

SG23におけるこの提案のレビューおよび投票においては、C++26で何もしないよりはここで提案されているようにP2900をさらに弱めたものを導入したほうが良い、という方向性に合意がされています(ただし反対票が無いわけではありませんが)。

P3303R1 Fixing Lazy Sender Algorithm Customization

P2999の提案の欠けていた部分を埋める提案。

以前の記事を参照

このリビジョンでの変更は、transform_senderに渡されるドメインの型を計算するときにsndr式が評価されないようにconnectget_completion_signaturesの仕様を調整したことです。

この提案は2024年7月に行われた全体会議で承認され、C++26WDに適用されています。

P3309R1 constexpr atomic and atomic_ref

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

以前の記事を参照

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

  • SG1の要求により、wait()と通知系関数をconstexpr化し、それにより文言を修正
  • 実装リンクの更新

などです。

P3310R1 Solving partial ordering issues introduced by P0522R0

P3310R2 Solving partial ordering issues introduced by P0522R0

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

以前の記事を参照

R1での変更は

  • 問題1
    • クラステンプレートのデフォルト引数を推論することの結果をさらに調査し、一貫して動作するようにルールを調整
    • パックのデフォルト引数を推論することの結果をさらに調査
    • 一貫性のない推論の例をさらに調査
  • 問題2
    • 現在の振る舞いの意味や価値と、この提案のものを混在させた場合の影響に関する説明を追加

このリビジョンでの変更は、問題1に関して解決策の表記と説明を改善したことです。

P3314R0 2024-07 Library Evolution Polls

2024年7月に行われる予定の、LEWGにおける投票の予定。

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

これはC++26に向けてLWGに転送するための投票です。

P3319R1 Add an iota object for simd (and more)

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

以前の記事を参照

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

  • 動機付けの説明に例を追加
  • “Generalization”セクションを拡張して、概要を説明するのではなく機能を詳細に説明する
  • 初期値とステップに関する説明を追加
  • 既存のiotaアルゴリズム/viewを使用しても、std::simdユースケースでは機能しないか十分ではない理由を説明
  • iota_vが適切な名前である理由を説明

などです。

P3321R0 Contracts Interaction With Tooling

P2900の契約プログラミング機能の実装定義とされている部分について、その範囲や意義を説明する文書。

P2900で提案されている現在のC++ Contracts仕様は一見すると、多くの部分が実装定義とされており標準では何も指定していないように見えます。この文書はP2900の仕様が実装定義としている部分をリストアップしそれが意図している自由度を説明するとともに、推奨する実装ベースラインを定義しようとするものです。

P2900が実際に実装定義としているのは次の2か所です

  1. 契約注釈が評価されるかどうか、およびいつ・どのように評価されるのか、について
    • 評価は繰り返されることがある
    • 結果が確定的である場合は評価を省略できる
    • 評価は、ignore, observe, enforce, あるいはquick_enforceセマンティクスによって起こる
    • ここの実装定義はユーザーによって制御されるもの
  2. observeもしくはenforceセマンティクスによって評価された契約注釈で違反が検出されると、ユーザー定義の違反ハンドラが呼び出される
    • このハンドラは条件付きで置換可能で、プラットフォームによってデフォルトの実装が提供される
    • プラットフォームはenforceセマンティクスで評価された場合にプログラムを終了する方法などのセマンティクスの他の部分に加えて、contract_violationオブジェクトの作成と設定も担う
    • ここの実装定義はプラットフォームの実装自由度を確保するためのもの

P2900の仕様はC++ Contractsの最初のバージョンから、予想される実装アプローチのすべてが準拠した実装になるようにすることを最終目標として設計されています。

この実装定義とされている仕様に対して、実装戦略の自由度の範囲は次のようにグループ化されます

  • 契約アサーションの評価場所とタイミング
    • コンパイラは、契約アサーションの評価を実行する命令をいつどこで生成するかを決定する必要がある
    • 関数契約アサーション(事前条件と事後条件)の場合、評価は呼び出し側と呼び出し先の境界で行われるため、より複雑になる
    • ABI、間接関数呼び出し、および繰り返し評価などの考慮事項が、コード生成の場所に影響を与える
  • 契約アサーションの評価セマンティクスの選択方法
    • 各契約アサーションの評価には、ignore、observe、enforce、またはquick_enforceのいずれかのセマンティクスが適用される
    • 実装は、コンパイラの構成、ソースコードのプロパティ、構成ファイル、モジュール構成、およびリンク時または実行時の構成に基づいてセマンティクスを選択するためのアルゴリズムを提供できる
    • この柔軟性により、ユーザーはさまざまなレベルで契約アサーションの動作を制御できる
  • 契約違反ハンドラの呼び出し方法
    • 契約違反ハンドラを呼び出すには、生成されたコードがstd::contracts::contract_violationオブジェクトを作成するための情報を収集する必要がある
    • プラットフォームの共有ABIは、このプロセスを処理するための関数を提供する必要がある
    • Itanium ABIでは、_Contract_Violationデータ型と、関連情報にアクセスするためのアクセサー関数を提供することが提案されている
  • 契約違反ハンドラの置換可能性
    • 実装は、契約違反ハンドラが置換可能かどうかを決定する必要がある
    • 置換可能なハンドラは、ユーザーが環境での違反を軽減する方法に柔軟性を提供する
    • 置換不可能なハンドラは、プログラムの動作に対するより厳格な保証を提供する
  • 契約違反オブジェクトに配置される文字列
    • contract_violationオブジェクトのプロパティの具体的な内容は、明示的に指定されていない
    • この柔軟性により、実装は、デバッグ情報が削除された実行可能ファイル、機密情報の除外、実行可能ファイルのサイズの削減など、さまざまなユースケースに対応できる
  • デフォルトの契約違反ハンドラの動作
    • デフォルトのハンドラは、診断出力を生成し、通常は復帰する必要がある
    • 堅牢な実装では、破損した状態での安定性、過剰なロギングの防止、例外情報の組み込みなどの考慮事項を考慮する必要がある
  • プログラムの終了方法
    • quick_enforceセマンティクスまたはenforceセマンティクスを使用した契約アサーションの評価中にプログラムが終了する方法は実装定義
    • 一般的には、std::abortを呼び出すか、それに相当する動作が想定される
    • ただし、特定のシナリオでは、より効率的なメカニズムを使用できる
  • 述語を省略できる場合
    • 述語の省略は、主に実装の品質の問題であり、必須ではない
    • コンパイラは、副作用のない述語を省略できる
    • 将来のハードウェアおよび評価コンテキストでは、トランザクション状態での評価などの高度な手法を探求できる

これらの実装戦略の自由度の範囲は移植性と柔軟性のバランスをとるように設計されており、P2900で実装定義とされていることによって実装間で許容される唯一の違いとは、個々の契約アサーションを制御できる粒度と柔軟性の程度であり、それ以上ではありません。

この様な実装戦略の自由度は、C++ Contractsが幅広いプラットフォームとユースケースに適応できることを保証します。 開発者は、すべての準拠実装で動作するソフトウェアを安全に記述でき、ベンダーは単純な実装から始めて、必要に応じてより堅牢なニーズをサポートするように進化できます。

そして、この文書ではこれらの実装自由度毎に実装の推奨ベースラインを提案しています

  • 契約アサーションの評価場所とタイミング
    • 呼び出し元側のチェックは各呼び出し元側で行い、呼び出し先のチェックは関数本体で行うことを推奨
      • 呼び出し元と呼び出し先のチェックが同じ(通常の関数呼び出しの)場合は、呼び出し元側のチェックはデフォルトでignoreセマンティクスで評価されるべき
    • 呼び出し元側と呼び出し先のチェックが異なる可能性のある仮想ディスパッチの場合は、呼び出し元側と関数本体の両方で設定されたセマンティクスで評価を行うことを推奨
    • 通常の関数呼び出しにおける呼び出し先でのチェックを呼び出し元側に切り替える設定パラメータを提供することも推奨
  • 契約アサーションの評価セマンティクスの選択方法
    • プラットフォームは、翻訳単位ごとおよび契約アサーションごとの契約アサーションの設定を可能にする標準のフラグセットをサポートすることを推奨
      • 翻訳単位ごとの設定には、少なくとも明示的な評価セマンティクスの指定を含める必要があるが、機密コードや信頼できないコードなど、より意味のある設定オプションに拡張できる必要がある
        • 個々の契約アサーションの設定は、グローバルに設定できるのと同じパラメータを設定できる、共通で定義された外部ファイル形式を介して実行する必要がある
    • リンカは、翻訳単位の設定方法に基づいてインライン関数定義を選択する設定をサポートするように更新する必要がある
      • リンカに明示的に何も指定されていない場合、リンカは最も厳格なチェックを行う定義を選択する必要がある
      • これは、ユーザーが特定の翻訳単位でenforceセマンティクスによる評価を要求した場合に、関数がインライン化されなかったためにリンカがより緩やかな定義を選択したという理由だけで、契約アサーションがenforceにならない事を回避するため
      • 一部のビルドでは、アプリケーションのホットパスにオーバーヘッドが発生することを避けることを強く望むため、オーバーヘッドの少ないセマンティクスを選択するようにリンカに指示する設定も提供する必要がある
    • リンク時のセマンティクスの選択は、複数の異なる契約構成で使用できる単一のバイナリライブラリの配布を可能にするために利用可能なオプションである必要がある
    • コンパイル時およびリンク時の構成は、コンパイル時構成に適用されるすべての考慮事項が、サードパーティから提供されたバイナリを統合する場合にも同様に適用される可能性があるため、一貫性を持たせる必要がある
    • 実行時のセマンティクスの選択は、選択された場合、何らかの形式のユーザー設定可能な関数を通過する必要があるが、これはコンパイラ固有のものである可能性がある
  • 契約違反ハンドラの呼び出し方法
    • 共有ABI(Itanium ABIなど)の場合、置換可能な契約違反ハンドラのサポートを推奨し、共有ABIを修正して、拡張機能を採用することを推奨する
      • 拡張機能とは、contract_violationの将来の拡張を考慮して追加の情報を保持できるようにしておく実装と、違反ハンドラのラインライムのエントリポイントを間接化すること(これにより、C++以外からの違反ハンドラを受け入れ可能になる)です
    • 大規模な共有ABIでは、多くのユーザーにとってその柔軟性が契約アサーションの使用から利益を得るための重要な要素であるため、ユーザーが独自の契約違反ハンドラを提供できるようにすることも義務付ける必要がある
  • 契約違反オブジェクトに配置される文字列
    • コンパイラは、実行可能ファイルから、contact_violationオブジェクトのlocationまたはcommentプロパティを設定するために使用される可能性のある情報を含む、不要な情報や機密性の高い情報をすべて削除することを要求する単一のフラグを提供する必要がある
    • 他の場合、可能な限り、契約アサーション評価が生成される場所をlocationに使用して呼び出し側の場所をキャプチャし、他の状況では関数本体の開始をキャプチャする必要があります。
  • デフォルトの契約違反ハンドラの動作
    • 推奨事項なし
  • プログラムの終了方法
    • 推奨事項なし
  • 述語を省略できる場合
    • 推奨事項なし

これらのことは必須ではなくあくまで推奨ですが、C++ Contracts実装におけるベースラインの動作となることを意図しています。

P3323R0 cv-qualified types in atomic and atomic_ref

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

CWG Issue 2094の解決によって、トリビアルコピー可能な型のCV修飾されている型もトリビアルコピー可能であると判定されるようになりました(以前は常にそうならなかった)。

一方で、std::atomic<T>Tとして使用可能な条件の一つにis_trivially_copyable_v<T>trueであることがあり(他の条件はこれを満たしていれば多くの場合満たすことになる)、CWG Issue 2094解決後にstd::atomic<T>TにCV修飾された型を指定した場合にどうなるかが問題となりました。

例えば、std::atomic<volatile int>std::atomic<const std::size_t>などは整数型のための部分特殊化が使用されない(CV非修飾のTに対してのみ特殊化されているため)という問題があり、浮動小数点数型についても同様です。同様に、std::atomic/std::atomic_refの多くの部分がTがCV非修飾であることを前提にしているため、どう動作すべきかが不明瞭になっています。

また、std::atomic_ref<const T>std::atomci_ref<volatile T>などには有用性がある(std::atomicとして構築されていないオブジェクトをアトミックアクセスし、なおかつ読み取り専用やvolatileで使用する)可能性があるものの、こちらもTがCV修飾を考慮していないことでどう動作するかが不透明です。

この提案は、CV修飾されたTについて、std::atomicでは使用を禁止し、std::atomic_refでは使用可能なように規定しなおす提案です。

std::atomic<T>の場合、Tに対する制約としてsame_as<T, remove_cv_t<T>>trueであることを要求するようになります。volatileの場合はvolatile std::atomic<T>でより良くサポートされているためそれも禁止されます。

std::atomic_ref<T>の場合はCV修飾を受け入れるもののstd::atomic_ref<volatile T>特殊化がサポートされるのはロックフリーの場合のみとし、std::atomic_ref<const T>特殊化においては可能な操作を読み取りのみに制限します。

P3325R0 A Utility for Creating Execution Environments

P3325R1 A Utility for Creating Execution Environments

P3325R2 A Utility for Creating Execution Environments

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

Execution Environments(実行環境)とはsenderチェーンによる非同期処理に依存関係を注入する方法です。例えば、アロケータやstop_tokenscheduler等をカスタマイズする場合は実行環境を通してやり取りします。

実行環境はreceiverに関連付けられており、senderアルゴリズムは接続されたreceiverから実行環境を取得して利用します。実行環境そのものは単なるkey/valueストアであり、特定の取得関数をkeyとして実行環境から値を取り出します。

// receiverとしてrcvrが得られている時
auto st = get_stop_token(get_env(rcvr));  // rcvrから実行環境を取得し、stop_tokenを実行環境から取得

しかし、これまでのところ実行環境をユーザーがカスタマイズする方法はなく、これはsenderアルゴリズムの実装詳細でした。しかし、senderアルゴリズムそのものはユーザが自由に実装することができ、その場合実行環境をカスタマイズすることはその際の作業の一部になります。

また、P3284R0ではユーザーが指定した実行環境とreceiverの実行環境を統合するwrite_envというユーティリティ(senderアダプタ)が提案されていますが、これを有効活用するにはユーザーが実行環境を任意に作成できる必要があります(現在その方法はありません)。

さらに、P3149R3ではspawnspawn_futureという新しいsenderアルゴリズムが提案されており、これらの関数にオプションの引数として実行環境を渡せるようにすることでカスタマイズ性を向上させることができます。そしてこのアプローチは、sync_waitstart_detachedなど現在カスタマイズ性の皆無な必須部品に対しても適用でき、実行環境を引数を通して渡すことでそこで使用されるデフォルトのアロケータやstop_token等をカスタマイズすることができるようになります。

これらの理由に加えて、実行環境を引数に取るようなAPIは増えていくことが予想されているため、ユーザーが実行環境を作成するためのユーティリティが必要です。

この提案はそのようなユーティリティを追加しようとするものです。ここで提案しているのは次の3点です

  1. execution::propというクラステンプレートを追加する
    • propはクエリQを値Vに関連付け、E.query(Q)Vを返すクエリ可能オブジェクトEを作成する
  2. execution::envというクラステンプレートを追加する
    • envは複数の環境を1つに集約し、字句順で優先度を付ける
  3. empty_envenv<>に置き換え
    • これはオプションの提案

これらの名前は決定されたものではなく、まだ仮のものです。

1がクエリと値のペアから実行環境を作成するもので、2はそのように作成された実行環境同士を1つの実行環境に統合するものです。

namespace std::execution {
  // propの宣言例
  template<class Query, class Value>
  struct prop {
    Query query;    // exposition only
    Value value;    // exposition only

    static_assert(/*...*/);

    constexpr const Value& query(Query) const noexcept {
      return value;
    }
  };

  template<class Query, class Value>
  prop(Query, Value) -> prop<Query, unwrap_reference_t<Value>>;
}

これはprop(key, value)のように構築することで実行環境を取得でき、その値eに対してe.query(key)とすることで登録したvalueを取得できます。なお、このような呼び出しは標準のクエリCPOがデフォルトで行う呼び出しでもあります。

using ex = std::execution;

template<sender Sndr>
ex::sender auto parameterize_work(Sndr sndr) {
  // ex::get_allocator(env)がmy_alloc{}を返す実行環境を作成
  // ex::get_allocatorは実行環境からアロケータを取得する標準のCPO
  auto e = ex::prop(ex::get_allocator, my_alloc{});

  // 入力`sender`(sndr)をパラメータ化してカスタムの実行環境を使用するようにする
  return ex::write_env(sndr, e);
}
namespace std::execution {
  template<class Env, class Query>
    concept has-query =                   // exposition only
      requires (const Env& env) {
        env.query(Query());
      };

  // encの宣言例
  template<queryable... Envs>
  struct env {
    Envs0 envs0;       // exposition only
    Envs1 envs1;       // exposition only
      ...
    Envsn-1 envsn-1;   // exposition only

    template<class Query>
    constexpr decltype(auto) get-first() const noexcept {  // exposition only
      constexpr bool flags[] = {has-query<Envs, Query>..., false};
      constexpr size_t idx = ranges::find(flags, true) - flags;
      return (envsidx);
    }

    template<class Query>
      requires (has-query<Env, Query> ||...)
    constexpr decltype(auto) query(Query q) const noexcept(noexcept(get-first<Query>().query(q))) {
      return get-first<Query>().query(q);
    }
  };

  template<class... Envs>
    env(Envs...) -> env<unwrap_reference_t<Envs>...>;
}

これはenv(env1, env2, ...)のように使用して、渡した実行環境を統合した1つの実行環境を作成するものです。作成された実行環境ee.query(key)のように(先程と同様に)使用して値をクエリすることができます。この時の実行環境へのクエリは内部に保存された複数のクエリに対して順番に行われますが、この順序は字句順となります。おそらくこのようなクラスはリフレクション機能を用いないと作成できないでしょう。

using ex = std::execution;

template<sender Sndr>
ex::sender auto parameterize_work(Sndr sndr) {
  // get_allocator(env) がmy_alloc{}のコピーの参照を返すような実行環境と
  // get_scheduler(env) がmy_sched{}のコピーの参照を返すような実行環境を統合した実行環境を作成
  auto e = ex::env{ex::prop(ex::get_allocator, my_alloc{}),
                   ex::prop(ex::get_scheduler, my_sched{})};

  // 入力`sender`(sndr)をパラメータ化してカスタムの実行環境を使用するようにする
  return write_env(sndr, e);
}

P3326R0 favor ease of use

std::optional<T&>において、安全な場合に一時オブジェクトを使用できるようにする提案。

std::optional<T&>はP2988で提案されており、安全性のために右辺値からの構築が一切禁止されています。

optional<int&> dangler(optional<const int&> other,
                       optional<int&> left,
                       optional<int&> right)
{
  if(random_bool())
  {
    return left;
  }
  else
  {
    return right;
  }
}

int main() {
  int i = 42;
  optional<int&> oi1{i};
  optional<int&> oi2 = dangler(oi1, oi1, oi1);  // ok
  optional<int&> oi3 = dangler(42/* unnecessary error */, oi1, oi1);  //ng
}

このdangler()では、第一引数の使用(使用されていませんが)はこの関数内で完結しており、その参照はこの関数の呼び出し後に破棄されます。したがって、この場合第一引数に右辺値を渡してそれを参照したとしても実際には問題ありません。しかし、std::optional<T&>のコンストラクタの直接の文脈からそれを判定することはできないため、現在一律に禁止されています。

この提案は、このような場合にプログラマの知識をstd::optionalに伝達する方法を用意することで、このような利用を可能にしようとするものです。

この提案では、favorsという列挙型を用意して

enum class favors {
  safety,
  ease
};

この列挙型の値をstd::optional<T&>に指定できるようにします

template <class T, favors favor = favors::safety>
class optional<T&, favor> {
  ...
};

なお、この変更を行うためにはstd::optional<T>でもこの2つ目のテンプレートパラメータを受け取るようにする必要があります(ただし、std::optional<T>では使用しません)。

コンストラクタではこの列挙値を使用して制限を緩和します

  // 任意の参照を受け取るコンストラクタ
  template <class U = T>
      requires(!detail::is_optional<std::decay_t<U>>::value)
  constexpr explicit(!std::is_convertible_v<U, T>) optional(U&& u) noexcept requires (favor == favors::safety)
      : value_(std::addressof(u)) {
      static_assert(
          std::is_constructible_v<std::add_lvalue_reference_t<T>, U>
          & favor == favors::safety,
          "Must be able to bind U to T&");
      static_assert(std::is_lvalue_reference<U>::value
          & favor == favors::safety,
          "U must be an lvalue");
  }

  // T&を受け取るコンストラクタ
  constexpr optional(T& t) noexcept requires (favor == favors::ease)
      : value_{std::addressof(t)} {}

デフォルトはfavors::safetyであり、これは安全性を重視することを表すものです。主に

  • ローカル変数
  • 引数で使用してそのままリターンするもの
  • コンストラクタ引数に依存するメンバ変数
  • 関数引数を通じて値を返す場合の関数引数

等の場所で使用します。対して、favors::easeはより危険な場所での利用を認めるもので

  • voidを返す関数の入力引数
  • 値(not参照)を返す関数の入力引数
  • その全体も一部もその関数から外に漏れることのない引数

等に使用することを意図しています。

同様の問題は他の参照セマンティクスな既存の型(std::spanstd::function_refstd::reference_wrapper)にも言えて、この提案ではこれらの型に対する変更を提案していないものの問題点について指摘しています。

式の値カテゴリの情報は必ずしもその変数の生存期間と一致しているわけではなく、デフォルトの安全性を重視するあまりにstd::optional<T&>を不必要に使い辛くするのは標準ライブラリ機能の明快さを損ねる、としています。

P3328R0 Observable Checkpoints During Contract Evaluation

P1494で提案されているUBによる影響の遡求を防止する機能を、契約アサーションに導入する提案。

契約プログラミング機能における問題点の一つとして、契約アサーション内での未定義動作の問題があります。契約アサーション内ではほぼ通常のC++と同じセマンティクスの下で契約条件式を記述できるため、そこには容易に未定義動作が混入しえます。

特に問題なのは、UBに伴って発生するタイムトラベル最適化です。契約プログラミング機能において問題になるのは次の2つのパターンです

契約アサーション内のUBによってタイムトラベル最適化が引き起こされる

int i = 0;
void f(int *p) {
  if (p != nullptr) // #1
  {
    ++i;
  }

  contract_assert( *p >= 0 ); // p == nullptrの場合未定義動作
}

#1の後のcontract_assertが無視ではないセマンティクスで評価される場合、その条件式は未定義動作となりコンパイラはUBが起こらないものと仮定してpは常にnullptrではないとみなすことで、#1のチェックが削除される可能性があります。この場合、f(nullptr)を渡していてenforceセマンティクスで評価されていてもiのインクリメントが観察される可能性があります。

契約アサーションの後のUBによるタイムトラベル最適化によって、契約アサーションが削除される

void g(int *p) {
  contract_assert( p != nullptr ); // #2

  ++*p; // p == nullptrの場合未定義動作
}

#2contract_assertobserveセマンティクス(チェックあり、デフォルト違反ハンドラ呼び出し、終了無し)で評価されていて、コンパイラが契約違反処理プロセスに関する知識を持っており、違反ハンドラが常に正常にリターンすることを認識している場合、後続のpでは未定義動作を起こらないものとして最適化を行うとpは常にnullptrではないため#2のチェックは自明であり削除することができます。

この場合、#2アサーションenforcequick_enforceセマンティクスで評価されている場合は#2以降に継続しないためそのような最適化は不可能です。

P1494R3ではstd::observable()という特殊な関数を追加し、この関数の呼び出しが観測可能なチェックポイント(observable checkpoint)となることを規定するとともに、観測可能なチェックポイントの後続のUBがこの点を超えてプログラムをUBにしないようにすることを規定しています。

P1494は一瞬C++23に入りかけたものの、その有効性や使用法について合意を得ることができずに立ち止まっています。

この提案は、P1494で提案されている観測可能なチェックポイントという概念を利用し、契約アサーションを観測可能なチェックポイントとして規定することで前述のような契約アサーションとUBの相互作用によるタイムトラベル最適化の影響を低減することを提案するものです。ここでは次の2つの変更を提案しています

  1. 契約アサーション内での未定義動作によるタイムトラベル最適化の発生を防止するために、無視されないセマンティクスを持つ契約アサーションの評価の開始を観測可能なチェックポイントとする
  2. 契約アサーションの後の未定義動作によるタイムトラベル最適化によって契約アサーションもしくは違反ハンドラ呼び出しが削除されるのを防止するために、observeセマンティクスの評価によって呼ばれる違反ハンドラの正常なリターンを観測可能なチェックポイントとする

この2つの変更によって、前述の2パターンの契約アサーションとUBの問題を解消することができます。

この提案はSG21のレビューにおいてP2900への導入が採択されています。2つの変更点は両方とも了承されているものの、P1494の採択が条件になっています。

P3330R0 User-defined Atomic Read-Modify-Write Operations

std::atomicにユーザー定義のRMW操作を行うためのAPIを追加する提案。

std::atomicのRMW(read-modify-write)操作は、算術型の特殊化に対して一部の演算についてのみ専用のメンバ関数が提供されていますが、ユーザー定義型の特殊化についてはそのようなAPIはありません。サポートされていない操作やユーザー定義型についてのRMW操作はユーザーが自前で定義する必要があります。

#include <atomic>
std::atomic<float> a = 0.f;
    
int main() {
  float old = a.load(), next;

  // float値のアトミックな指数関数RMW操作
  do {
    next = std::expf(old);
  } while(a.compare_exchange_strong(old, next));

  return 0;    
}

ユーザー定義でRMW操作を定義しようとすると、このように通常CAS(Compare-and-Swap)ループを使用して実装することになりますが、これにはいくつかの難点があります

  • 進行保証(forward progress)QoIを損なうことなく実装するのが困難
  • コンパイラの最適化が困難になる
    • アトミックfetch_add()を単にadd()に最適化するコンパイラは存在しない
  • CASループはイテレーション毎にロックを複数回取得・解放するため、ロックフリーではないstd::atomicの場合パフォーマンスが低下する

これらの問題点はRMW操作そのものというよりは、その実装のために使用するCASループに伴う問題点です。この提案では、RMW操作実現のための実装の大半(CASループなど)を実装側に移動しておくことで、ユーザーが独自のRMW操作を実装する場合に上記のような問題の解消を目指すものです。

すなわち、先程の例だと

do {
  next = user_defined_operation(old);
} while(a.compare_exchange_strong(old, next));

このuser_defined_operation()以外の部分の実装は共通であり、問題が起こりやすいのはこの部分です。user_defined_operation()はユーザーが指定するもののその他の部分について実装済みのAPIを提供することで、RMW操作はより定義しやすくなります。

この提案では、std::atomicstd::atomic_refのユーザー定義型の特殊化に対して次の2つのメンバ関数を追加することを提案しています

template <typename T, typename UnaryOp>
bool fetch_update(T& old, UnaryOp uop, 
                  memory_order order = memory_order::seq_cst) const noexcept;

template <typename T, typename UnaryOp>
bool fetch_update(T& old, UnaryOp uop, 
                  memory_order success = memory_order::seq_cst,
                  memory_order failure = memory_order::seq_cst) const noexcept;

単項演算uopの結果はアトミックアクセス対象の値をアトミックに更新するために使用され、Tもしくはstd::optional<T>の値を返す必要があります。

uop(o)の値がT型の値を返す場合は、その戻り値はそのままアトミックアクセス対象の値に代入され、fetch_update()trueを返します。uop(o)の値がstd::optional<T>型の値を返す場合は、その.has_value() == trueの場合はTの値を返す場合と同様ですが、.has_value() == falseの場合は代入を行わずにfalseを返します。ただしどちらの場合でも、内部で読み取られてuopに渡された値はoldに書き込まれます。

このAPIを使用すると、先程の指数関数RMW操作は次のように書くことができます

#include <atomic>
atomic<float> a = 0.f;
    
int main() {
  float old;

  // float値のアトミックな指数関数RMW操作
  a.fetch_update(old, [](float o) {
    return std::expf(o);
  });
  
  return 0;    
}

std::pairの値の一方に応じて更新を行い、条件が満たされない場合は更新せず読み取りのみを行う例

std::atomic<std::pair<char, short>> atom;
    
std::pair<char, short> old;

bool success 
  = atom.fetch_update(old, [](std::pair<char, short> p) -> std::optional<std::par<char, short>> {
      // char側の値が42より大きい場合、更新を行わない
      if (p.first > 42) {
        return std::nullopt;
      } else {
        return std::make_pair(p.first+1, p.second+2)};
      }
    );

// oldにはpの値が読みだされている
assert((success && old.first <= 42) || (!success && old.first > 42));

fetch_update()の実装戦略としては次のものをサポートすることを意図しています

  • CASループベース実装
  • ロックベース実装
    • ロックベースCASループなど複数回ロックを取得する
    • 一回だけロックを取得する
  • LL/SCベース
  • ハードウェアトランザクショナルメモリ

これらの実装戦略をサポートするために、UnaryOpの呼び出し演算子およびuop(o)呼び出しには次の要件が課せられます

  • regular_invocableであること
  • implicit-lifetime typeであること
  • noexcept(declval<UnaryOp>()(declval<T>()))trueであること
  • そのオペランドまたは非静的メンバにのみアクセスする
  • 標準ライブラリのI/O関数呼び出し、同期操作、アトミック操作を実行しない
  • 最終的にリターンする

この要件は、uop(o)の副作用はその非静的メンバを変更するのみであり、かつ安定(同じ入力に対して同じ出力を返す)であることを要求しています。

実装の要件は

  • fetch_update()の効果はuop(o)を一回だけ呼び出す
  • uop(o)を複数回呼び出す実装はuop(o)が一回だけ呼び出されたかのように動作する場合にのみ許可される

例えば、CASループベースの実装ではuopオブジェクトをバックアップしておき、イテレーション毎にそのバックアップのコピーに対してuop(o)呼び出しを行うことでuop(o)の副作用を破棄する必要があります。

P3331R0 Accessing The First and Last Elements in Associative Containers

※この部分は@Sakky4869さんに執筆して頂きました!

連想コンテナに対して、2つの関数front()back()を追加する提案です。

StackOverflowでは以下の2つの質問が多く見受けられます。

  • mapコンテナで最初の要素を取得する方法
  • std::mapの最後の要素のkeyを取得する方法

このような課題に対して、最初の要素を取得する場合は、以下のような方法を提案しています。

現在 提案
*m.begin() m.front()
(*m.begin())->first m.front().first
(*m.begin())->second m.front().second

また、最後の要素を取得する場合は以下のような方法を提案しています。

現在 提案
*m.rbegin()
*prev(m.end())
*--m.end()
m.back()
(*m.rbegin())->first
*prev(m.end())->first
(*--m.end())->first
m.back().first
(*m.rbegin())->second
*prev(m.end())->second
(*--m.end())->second
m.back().second

類似した提案について、C++20で追加された連想コンテナのcontains()が挙げられています。

関数の命名は、front() / back()の他に、first() / last(), min() / max()が提案されていました。

P3332R0 A simpler notation for PM

P2688R1のパターンマッチング提案に対して、名前を導入するパターン構文の改善提案。

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

P2688では、パターンにマッチしたものに名前を付けて参照するのにletを使用します。

std::tuple<double double> p = ...;

p match {
  [0, 0] => std::print("on origin");
  [0, let y] => std::print("on y-axis at {}", y);
  [let x, 0] => std::print("on x-axis at {}", x);
  let [x, y] => std::print("at {}, {}", x, y);
};

この提案ではこの構文が煩雑であるとして、代わりの構文を提案しています。

ここで提案されているのは、単にletを無くしてpattern name => actionの形で記述するようにした構文です。先程の例を書き直すと次のようになります

p match {
  [0, 0] => std::print("on origin");
  [0, _ y] => std::print("on y-axis at {}", y);
  [_ x, 0] => std::print("on x-axis at {}", x);
  _ [x, y] => std::print("at {}, {}", x, y);
};

最後のパターンのように、_を任意のものにマッチする構文として一貫的に使用しながらマッチしたものに名前を付けられるようになります。

letによる名前導入の例

let x // x is new
[a, let y] // a is old, y is new
[let x, b] // x is new, b is old
let [x, y] // x and y are both new
let [x, [y, z]] // x, y, z are all new

この提案による例

_ x // x is new
[a, _ y] // a is old, y is new
[_ x, b] // x is new, b is old
_ [x, y] // x and y are both new
_ [x, [y, z]] // x, y, z are all new

ただし、最後の例に関してはネストした場合の特別ルールとして[_ x, _[y, z]]と書けるようにすることを提案しています。

std::variantの例

P2688R1 この提案
std::variant<int, bool, std::string> parse(std::string_view);

parse(some_input) match {
  int: let i => // ...
  bool: let b => // ...
  std::string: let s => // ...
};
parse(some_input) match {
  int i => // ...
  bool b => // ...
  std::string s => // ...
};

ポインタの例

P2688R1 この提案
void f(int* p) {
  p match {
    ? let i => // ...
    nullptr => // ...
  };
}
void f(int* p) {
  p match {
    ? _ i => // ...
    nullptr => // ...
  };
}

提案文書にはもう少し対応例があります。

この提案では純粋に構文の変更のみを提案しており、意味論の変更は提案していないため、変更後のコードは変更前のものと同じ意味を持ちます。また、現在パターンマッチング提案としてはもう一つP2392がありますが、この提案はどちらの提案を推すものでもありません。

P3335R0 Structured Core Options

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

ビルドシステムをはじめとするC++ツールはC++コンパイラフロントエンドを呼び出すために異なるオプションをコンパイラに合わせて使い分ける必要があります。現在のところそれは実装できていますが、この先増大する複雑さを低減するためには、コンパイラ間で共通して使用可能なオプション言語ともいえるものが必要です。

この提案は、そのために構造化されたオプションの共通セットを提案するものです。この提案によって、例えば次のようなことが可能になります

  • コンパイラフロントエンドとやり取りするツールによる実装の再利用
  • 消費者or提供者として、共通オプションを利用するツールの採用拡大
  • 初学者にとって学ぶべきことが減ることで、参入障壁が低くなる

例えば、Hello worldのプログラムをコンパイルするコマンドはgccとMSVCで次のようになります

"g++" -O0 -fno-inline -Wall -g -static "hello.cpp" -o "hello"
"cl" "hello.cpp" /Fehello -TP /EHs /GR /Z7 /Od /Ob0 /W3 /Op /MLd /DEBUG
  /subsystem:console

これをこの提案の共通オプションを用いて記述すると次のようになります

{
  "source": "hello.cpp",
  "output": {
    "hello": "exec"
  },
  "optimization": {
    "compile": "off",
    "inline": false
  },
  "warnings": {
    "enable": "all"
  },
  "debug": true,
  "runtime": {
    "multithread": false,
    "debug": true,
    "static": true
  },
  "vendor": {
    "msvc": {
      "subsystem": "console"
    }
  }
}

これはコマンドラインで直接記述して渡すようなものではなく、ファイルに格納して渡すものです。そのファイル形式やオプションについてはP3051で提案されています。ここで提案しているのは、各C++コンパイラがほぼ同じ意味論で用意していて良く使用されているオプションについて、P3051のファイル形式で記述可能なように共通化したオプション指定方法についてです。

ソースコードのように複数の値を指定するオプションでは、JSONの配列が使用できます

{
  "source": [
    "bindjam.cpp",
    "builtins.cpp",
    ...
  ],
  
  ...
}

-Dによって指定される事前定義マクロを指定する例

{
  "define": {
    "BOOST_ALL_NO_LIB": 1,
    "_WIN32_WINNT": "0x0600",
    "_GNU_SOURCE": true,
    "U_USING_ICU_NAMESPACE": false,
    "NOMINMAX": null
  }
}

最適化オプションの例

{
  "optimization": "minimal"
}
{
  "optimization": "speed"
}

提案では

  • ソースファイルの指定
  • 出力名とその種類の指定
  • インクルードディレクトリの指定
  • リンクするライブラリの指定
  • マクロ定義の指定
  • マクロundefの指定
  • 言語の指定
  • 最適化・デバッグの指定
  • ベンダ固有オプションの指定

について、既存コンパイラ(MSVC、GCC)とツール(CMake、B2)におけるオプション構文を比較するとともに、提案する共通オプションの構文とセマンティクスを詳細に検討しています。

P3336R0 Usage Experience for Contracts with BDE

Bloomberg社内において使用されていた契約チェック機能をP2900で置き換えた実験の報告書。

この文書で報告されている実験の目的は、P2900の契約プログラミング機能の従来のアサーションと異なる点である暗黙const化と評価回数の規定なし(複数回評価されうる)の影響について調べることです。

実験は、Bloomberg社内のBDEライブラリというライブラリで使用されているbsls_assertというアサーションcontract_assertを使うように書き換えて行われています。また、BDEのほかに規模は少し小さくなるものの4つの内部ライブラリでテストしています。

実験の一つ目は、暗黙const化の影響について調べることです。これは、GCCの実験ブランチで実装中のP2900R7ベースの契約プログラミング機能の実装を利用して対象のライブラリをコンパイルすることで行われました。

テスト対象のコードベースは次のようなものです

ライブラリ コンポーネント 行数 テスト行数 アサーション テストアサーション 問題点
BDE 3330 1.32M (70.52%) 2.14M (76.02%) 7749 (0.83%) 4743 (0.29%) 7
Library #1 1814 368.30K (62.71%) 986.45K (79.18%) 6307 (2.73%) 981 (0.13%) 4
Library #2 165 45.49K (74.68%) 64.77K (75.86%) 231 (0.68%) 240 (0.49%) 0
Library #3 1084 352.58K (79.90%) 116.20K (88.73%) 1844 (0.65%) 138 (0.13%) 2
Library #4 240 86.61K (60.09%) 17.79K (85.48%) 1156 (2.22%) 5 (0.03%) 1

各列の意味:

  • コンポーネント: .hと.cppのペアの数、および関連するテストドライバ
  • 行数とテスト行数: コードの行数。空行やコメントを除いた行数の割合をパーセントで示し、ライブラリのサイズのおおよその尺度としています。本番コード (.hと.cppファイル) とテストドライバコードに分かれています
  • アサーションとテストアサーション: bsls_assertマクロの使用回数。空行やコメントを除いたコード行数に対するアサーションの割合をパーセントで示し、これも本番コードとテストドライバコードに分かれています。この割合は、個々のライブラリでアサーションがどれだけ一貫して使用されているかを示しています
  • 問題点: bsls_assertの実装でcontract_assertを使用するように切り替えたために対処する必要があった、異なる問題の数

そして、1つ目の実験の結果は次のようになりました

ライブラリ constサポートの不足 変更しない使用 意図的な失敗 破壊的な述語 バグ 合計
BDE 1 4 1 1 0 7
Library #1 3 1 0 0 0 4
Library #2 0 0 0 0 0 0
Library #3 1 0 0 0 6 7
Library #4 0 0 0 0 1 1

各列の意味:

  • constサポートの不足: const修飾が正しく行われていなかっAPIの使用による問題
  • 値を変更しない非constでの使用: 値を変更しないが、非constの変数を使用する必要があることによる問題
  • 意図的な失敗: テストのために意図的に失敗するように設計されたアサーションによる問題
  • 破壊的な述語: 同じセマンティクスで一貫して評価されない場合、正しい結果を生成しなくなる述語による問題
  • バグ: 代入演算子(=)を等価比較演算子(==)の代わりに誤って使用することによる問題。

全体として、bsls_assertからcontract_assertへ以降するための労力はほぼ皆無だったと、としています。

2つ目は、契約述語が繰り返し評価される場合の影響を見る実験で、これはアサーションの述語を64回繰り返して評価するようにして、BDEライブラリを普段ビルドしている製品版のコンパイラでビルドすることで行われました。

実験の結果、次のことが明らかになりました

  • 実行時の動作品質を向上させ、一度報告された違反が再度報告されないようにするために、違反が検出された場合(たとえそれがobserveセマンティクスであっても)、繰り返し評価を停止する、ことにした
    • この決定は、テストや既存のコードには影響を与えないもののこの機能を提供するコンパイラでは考慮すべき点
  • 2つのテストドライバは、テスト対象のコードによって実行された操作をカウントしていた
    • これは主に、特定の同期プリミティブが要求された通りの動作のみを行うことを確認するため
      • カウントを容易にするために、計測を組み込んだ実装が使用されていた
    • どちらの場合も、既存のコードはアサーションマクロ内で評価を行い、期待される操作に期待される繰り返しの回数を乗算する必要があった
    • このレベルの詳細を必要とするテスト戦略、または契約アサーション述語が評価されたかどうかに依存するテスト戦略は、テストコードが契約アサーション述語の評価回数を決定できる適切なロジックを実装できる場合にのみ適用する必要がある
  • ファズテストユーティリティのテストの一環として意図的に失敗するように設計されたアサーションは、最初の繰り返しで依然として失敗し、繰り返し評価戦略では問題として検出されなかった
  • ローカルカウンタを変更していたアサーションは、繰り返し評価によって見事に失敗したため、修正される予定

とはいえ、これらの問題を解決するための量力はBDEライブラリ全体を数回ビルドする時間に比べると僅かなものだったとのことです。

文書では、これらの実験から次のような教訓を得られたとしています

  • アサーションの大部分は変更を必要とせずに正しく機能する
  • テストしたライブラリではconst化によってオーバーロード解決結果が変更されてバグるアサーションは一つもなかった
  • この実験だけでも、見つかっていなかったバグや疑わしい設計がいくつか発見されたことで、ライブラリの品質が向上した

総合的に見ると、P2900の契約プログラミング機能で争点となっている上記2つの問題点の実際の影響は皆無ではないものの軽微であり、それによる恩恵が上回る、という方向性の報告です。

P3338R0 Observe and ignore semantics in constant evaluation

定数式における契約チェックのセマンティクスとして、observeignoreが有効であることを説明する文書。

ここでの主張は、防衛的なチェックと契約チェックを併用している場合に、定数式における契約チェック時にもobserveignoreセマンティクスによる評価が有用となる、ということです。

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

int f(int x)
  pre(x >= 0)
{
  // 防衛的なチェック(事前条件と一部重複する
  if (x < 0) {
    return -1;
  }

  // 関数の主処理
  return x * 2;
}

この関数は事前条件を持つものの、防衛的なチェックを行っていることで事前条件に違反した呼び出しを行っても安全です。このようなコードは既存のコードからの移行によって書かれる可能性があり、この関数を既に使用している呼び出し下では負の数で呼び出している場合があるかもしれませんが安全に動作します。

契約チェックの有効化後にそのようなバグを発見するには、このpre()observeセマンティクスで評価することで行えます。誤った呼び出し元は契約違反となるものの、診断が発行されるとともに引き続き安全に動作します。

このような関数をconstexpr化した後でも、(コンパイル時の契約注釈のセマンティクスとしてobserveが使用できれば)コンパイル時の評価においても同様の事が保証されます。また、その場合にignoreセマンティクスを選択すれば警告すら表示されません。防衛的なチェックによって契約違反は必ずしも致命的ではないので、このような選択が可能になります。

このようなアプローチを効果的に使用するには契約注釈に個別にセマンティクスを指定するラベルのようなものが必要になりますが、そのようなラベルが無くても使用することができるため、コンパイル時の契約チェックのセマンティクスとしてobserveignoreには有用性があります。

現時点のP2900R7でもコンパイル時の契約注釈のセマンティクスとしては実行時と同様に4種類が有効であり、この文書はどうやらその設計根拠に追加の動機付けを与えるもののようです。

P3339R0 C++ Ecosystem IS Open License

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

Ecosystem ISはC++周辺ツールとエコシステムの相互運用性を高めるための共通の基盤を整備するための国際標準規格であり、2025年中の発行を目指して目下作業中です。

C++の周辺ツールとエコシステムは既に多くのものが存在し、日々進化を続けており、そのようなツールやシステムの多くは様々な国や企業の開発者のコミュニティによって開発されています。

Ecosystem ISが効果的であるためには、過剰なオーバーヘッドなしに、コミュニティのアイデアや経験をタイムリーに取り入れることができなければなりません。特に、コミュニティはEcosystem ISに貢献し、その後、できるだけ円滑に自分たちのエコシステムでEcosystem ISを実装できるようにする必要があります。

それを円滑に行うための方法の一つとしてここでは、オープンなライセンスによってEcosystem IS関連の文書を公開しておくことを提案しています。これによって、エコシステムのコミュニティはEcosystem IS関連の文書を自由に共有及び参照することができ、実装やフィードバックのスピードアップを図ることができます。

ここでの提案は次の3点です

  • Ecosystem ISに関連するPnnnn番号の文書(個別の提案)の著作権は著者に帰属し、"Creative Commons Attribution 4.0 International License" (CC-BY-4.0)の下でライセンスされる
  • 作業の結果得られたEcosystem ISのNnnnnドラフト文書と公開された国際標準規格の著作権は全ての著者とISO/IECに帰属する
  • 作業の結果得られたEcosystem ISのNnnnnドラフト文書と公開された国際標準規格は、CC-BY-4.0の下でライセンスされ、必要な帰属表示が含まれる

提案では、ISOの著作権ポリシーに適合しており、正当化されることを説明しています。

P3340R0 A Consistent Grammar for Sequences

C++の文法規則におけるシーケンスの定義を統一する提案。

C++の文法上のシーケンスとは例えば、指定された特定の文字の並びとして何らかの文字列による文法要素(ある種の文字列など)を定義しているものです。その例としては2桁以上の数値や2文字以上の文字列などがあります。これ以外にもC++文法上のシーケンスにはいくつかの種類があります。

2終端シーケンス

これはシーケンスの最も一般的な形式で、単一要素によって終端を指定したもの(長さ1のシーケンス)と、要素が再帰的に続いたうえで単一要素が終端としておかれることによってシーケンスをなすもの、によって構成されるシーケンスです。

例えば次のように定義されます

c-char-sequence :
  c-char
  c-char-sequence c-char

c-char-sequenceは文字リテラルの内部の構文を定義するものです。

同様のシーケンス定義は次の構文要素で使用されています

  • c-char-sequence
  • d-char-sequence
  • h-char-sequence
  • n-char-sequence
  • q-char-sequence
  • r-char-sequence
  • s-char-sequence
  • simple-octal-digit-sequence
  • simple-hexadecimal-digit-sequence
  • balanced-token-seq
  • declaration-seq
  • label-seq
  • lambda-specifier-seq
  • requirement-seq
  • statement-seq
  • virt-specifier-seq
  • elif-groups
  • pp-tokens
  • h-pp-tokens

末尾シーケンスがオプションなもの

これは、先頭の単一要素を終端として、その後ろにオプションで再帰的に自身を置いたものです。例えば次のように定義されます

cv-qualifier-seq :
  cv-qualifier cv-qualifier-seq(opt)

末尾の(opt)は省略可能であることを表します。cv-qualifier-seqはCV修飾指定の文法定義です。

同様のシーケンス定義は次の構文要素で使用されています

  • cv-qualifier-seq
  • handler-seq
  • conversion-declarator
  • member-specification

先頭シーケンスがオプションなもの

これは、先程とは逆に省略可能なリストを先頭に置いたものです。例えば次のように定義されます

attribute-specifier-seq :
  attribute-specifier-seq(opt) attribute-specifier

attribute-specifier-seqは属性リストの文法定義です。この定義はこれでしか使用されていません。

偽装リスト

これは、オプションの区切り文字の挿入を許可しているタイプのシーケンスです

hexadecimal-digit-sequence :
  hexadecimal-digit
  hexadecimal-digit-sequence '(opt) hexadecimal-digit

hexadecimal-digit-sequence0xプリフィックスの後に続く16進数値列の構文定義です。'を区切り文字として使用できるものの区切り文字で開始できないためこのようなシーケンス定義となっています。これは厳密にはシーケンスではなく、リストになっています。

同様の定義は次の構文要素で使用されています

  • digit-sequence
  • hexadecimal-digit-sequence

これらの種類のシーケンスでは元のリスト形式の定義を維持する必要があります。

シーケンスのグループ

これは、他のシーケンスのグループとなっているシーケンスです。これそのものはシーケンスではないためこの提案の対象ではありません。例えば次のように定義されます

escape-sequence :
  simple-escape-sequence
  numeric-escape-sequence
  conditional-escape-sequence

同様のシーケンス定義は次の構文要素で使用されています

  • decl-specifier-seq
  • defining-type-specifier-seq
  • escape-sequence
  • numeric-escape-sequence
  • type-specifier-seq

エスケープシーケンス

これは命名として-sequenceとなっているものの、ここまでのものと異なり要素の繰り返しによるシーケンスではないものです。例えば次のように定義されます

simple-escape-sequence :
  \ simple-escape-sequence-char

これに該当するものは全てエスケープシーケンスです

  • conditional-escape-sequence
  • octal-escape-sequence
  • simple-escape-sequence

これもこの提案の対象ではありません。

この提案は、最後の2つを除いてシーケンスの定義を共通化しようとするものです。

ただし、ここでは名前の一貫性(-seqだったり-sequenceだったり、どちらでもなかったり)を修正することは提案していません。名前を変更すると、その文法要素が参照されているところも変更する必要があり、変更範囲が大きくなるためです。

提案では、末尾シーケンスがオプションなものの文法定義をシーケンスを定義する際の共通定義として採用することを提案しています。

xxx-seq :
  xxx xxx-seq(opt)

2終端ではなく1終端を採用することで、偽装リスト形式と明確に区別できるようになります。

この提案ではさらに、文法要素としてのシーケンスを定義する場合のCWGのポリシーについても提案しています。提案しているポリシーは次の2つです

  1. シーケンス定義のポリシー
    • シーケンスを定義する場合は、1行の再帰表現を使用し、末尾要素として(オプショナルの)再帰をおいたものを使用することを推奨する
      • この提案の内容をポリシーとする
  2. シーケンス命名のポリシー
    • シーケンスを1文字づつパースし、1つのトークンまたは部分的なトークンとして消費する場合は、接尾辞として-sequenceを使用することを推奨
    • シーケンスをトークン毎にパースする場合は、接尾辞-seqを使用することを推奨

この提案は2024年11月の全体会議で承認され、ドラフトに適用されています。

P3341R0 C++ Standard Library Ready Issues to be moved in St Louis, Jun. 2024

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

P3342R0 Working Draft, Standard for C++ Ecosystem

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

これはP2656て作業予定が提案されていた、C++周辺ツールのための共通の枠組みを定義する国際標準規格の最初のドラフトです。

ここではP2717(ツールのEcosystem ISへの準拠度のクエリ)とP3335(コンパイラフロントエンドのオプションの共通構文)の2つの提案の内容が採択されているようです。

P2656で示されていたスケジュールによればこの文書の作成完了は2024年3月予定だったので、少し遅れているかもしれません。次の予定は、2025年2月のCommittee Draft (CD)作成完了です。

P3343R0 Contracts - What are we doing here (EWG Presentation)

P2900の契約プログラミング機能についてEWGのメンバに説明するスライド資料。

特に、P2900が行ってきた重要な設計上の決定事項について説明することを目的としているようです。

P3344R0 Virtual Functions on Contracts (EWG - Presentation for P3097)

P2900の契約プログラミング機能に関して、仮想関数に対する契約に関してEWGのメンバに説明するスライド資料。

P2900の現在の仕様では仮想関数には契約を行えません。それは恒久的なものではなく一時的なものですが、その緩和アプローチとして呼出し元(静的型)と呼び出し先(動的型)の両方で契約チェックを行うことで許可しようとする提案がいくつかあります。

このスライドはそのような提案の一つであるP3097のアプローチを解説するものです。

P3345R0 Core Language Working Group "ready" Issues for the June, 2024 meeting

6月に行われたSt Louis会議でWDに適用されたコア言語に対するIssue報告の一覧。

P3351R0 views::scan

状態を持つ関数を使用可能なviews::transformであるRangeアダプタ、views::scanの提案。

rng | views::transfrom(f)rngの各要素にfを適用した結果からなる範囲を生成するRangeアダプタですが、このfstd::regular_invocableであることが要求されており、このため状態を持つ(かつそれを更新するような)関数の使用はコンセプトの意味論要件違反となり未定義動作となります。

しかし、状態を持つ関数によってviews::transfromしたい場合というのはそれほど珍しいことではありません。例えば、std::inclusive_scan(範囲の部分和を計算し別の範囲に出力するアルゴリズム)のようなことを遅延評価で行いたい場合などが考えられます(これにはviews::transfromを使用できません)。

この提案ではそのために、新しいRangeアダプタであるviews::scanを提案しています。

ここで提案されているviews::scanは状態を持つ関数を使用可能なviews::transformとは少し異なり、regular_invocableな関数を受け取り、内部で状態の初期値として入力範囲の最初の要素を取得して、残りの各要素に対して状態と要素を関数に渡した結果を要素とする範囲を生成します(この時、関数の適用結果で状態を更新します)。

これは遅延評価かつ汎用的なstd::inclusive_scanであり、遅延評価するstd::inclusive_scan相当のものとして(適用する関数をstd::plusに固定したもの)views::partial_sumを、初期値を別に指定するviews::scanとしてviews::prescanを追加で提案しています。

namespace std::ranges {
  // views::scanアダプタの実装view型
  template<input_range V, typename T, move_constructible F>
    requires view<V> && is_object_v<T> && is_object_v<F> &&
             regular_invocable<F&, T&, range_reference_t<V>> &&
             assignable_from<remove_cvref_t<invoke_result_t<F&, T&, range_reference_t<V>>>&, T&> &&
             can-reference<invoke_result_t<F&, T&, range_reference_t<V>>>
  class scan_view; // freestanding

  namespace views {
    // views::scanアダプタのファミリー
    inline constexpr unspecified scan = unspecified;        // freestanding
    inline constexpr unspecified prescan = unspecified;     // freestanding
    inline constexpr unspecified partial_sum = unspecified; // freestanding
  }
}

サンプルコード

import std;

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

  auto scan = vec | std::views::scan(std::plus{});
  auto prescan = vec | std::views::prescan(10, std::plus{});
  auto partial_sum = vec | std::views::partial_sum;

  std::println("scan: {}", scan);
  std::println("prescan: {}", prescan);
  std::println("partial_sum: {}", partial_sum);
}

この出力は次のようになります

scan: [1, 3, 6, 10, 15]
prescan: [10, 11, 13, 16, 20, 25]
partial_sum: [1, 3, 6, 10, 15]

この3つのRangeアダプタは全て、基底の実装としてscan_viewを共有しています。

その他の特性は次のようになります

  • reference
    • invoke_result_t<F&, T&, ranges::range_reference_t<V>>
  • rangeカテゴリ
    • Vforward_rangeの場合: forward_range
    • それ以外の場合: input_range
  • common_range: ×
  • sized_range: Vsized_rangeの場合
  • const-iterable: Vがconst-iterableかつ、const Fが呼び出し可能である場合
  • borrowed_range: ×

value_typereferenceremove_cvrefした型になります。なお、referenceの型の決定はranges::foldと同じになっています。

また、borrowed_rangeに関しては現時点では常に満たさないものの、P3117が採択される場合に条件付きで可能になります(ここでは提案していません)。

P3354R0 Slides for P3233R0

P3233R0の説明スライド。

P3233R0の内容(P2786のトリビアルリロケーション機能に関する問題点の報告)についての内容を説明したスライドです。2024年6月のSt. Louis会議でWG21メンバに向けてプレゼンされたもののようです。

P3355R0 Fix submdspan for C++26

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

この提案では次の2つの修正を提案しています

  1. ユーザー定義型をスライス指定で使用できるようにする
  2. 行優先/列優先(及び~_paddedな)レイアウトに対して、スライスにコンパイル時定数が使用されていて条件を満たす場合に元のレイアウト情報を保持するようにする
    • コンパイル時定数エクステントの指定に対して、隣接性を維持する

ユーザー定義型をスライス指定で使用できるようにする

std::submdspanではmdspanの参照しているレイアウトによる配列から、さらに部分行列をインデックス範囲指定によって取得することができます。それは、std::submdspanのスライス引数に対してstd::pairstd::tupleによって半開区間を指定することで行えます。

int main() {
  int arr[] = {
    1,  2,  3,  4,
    5,  6,  7,  8,
    9, 10, 11, 12
  };

  // 3行4列の行列を参照するmdspan
  std::mdspan<int, std::extents<std::size_t, 3, 4>> mat_43{arr};
  // |1   2   3   4|
  // |5   6   7   8|
  // |9  10  11  12|

  // 上記行列の1行目から2行と0列目から3列の範囲を参照する部分行列を取得
  auto mat_23 = std::submdspan(mat_43, std::pair{1, 3}, std::pair{0, 3});
  // |5   6   7|
  // |9  10  11|
}

このような指定を行う場合に使用できるのはindex-pair-likeという説明専用のコンセプトを満たす型ですが、pair-likeという説明専用コンセプトを含んでいることでget<0>get<1>が使用できることを要求しており、これによって整数のペアを表現するためにはstd::pairのようなtuple-likeな型を使用する必要があります。

この問題点は、例えばintegral_constant2つをメンバに持つ構造体によってインデックス範囲指定が行えない(get<0>get<1>は参照を返す必要があるため)ことです。

// 整数ペアを表す空のクラス
template<std::integral auto Begin, std::integral auto End>
struct index_pair {
  [[no_unique_address]]
  std::integral_constant<std::size_t, Begin> a;
  [[no_unique_address]]
  std::integral_constant<std::size_t, End> b;
};

int main() {
  int arr[] = {
    1,  2,  3,  4,
    5,  6,  7,  8,
    9, 10, 11, 12
  };

  // 3行4列の行列を参照するmdspan
  std::mdspan<int, std::extents<std::size_t, 3, 4>> mat_43{arr};

  // 上記行列の1行目から2行と0列目から3列の範囲を参照する部分行列を取得(したい
  auto mat_23 = std::submdspan(mat_43, index_pair<1, 3>{}, index_pair<0, 3>{}); // ng
}

このindex_pairのような構造体のサイズは最小であるため、submdspanから返されるmdspanのサイズオーバーヘッドを削減することができます。しかしこのような型はstd::get<0>等を実装できないため、現在のsubmdspanではサポートできません。

この提案では、現在の要求にあるpair-likeコンセプトを、構造化束縛が機能すること(かつtuple_sizeが2であること)、という要件に置き換えることでこれをサポートできるようにすることを提案しています。

コンパイル時定数エクステントの指定に対して、隣接性を維持する

std::mdspanstd::aligned_accessorを使用して1次元配列のコピー操作をベクトル化しようとするコードを書こうとする場合(コード例は提案参照)、にコンパイル時エクステントを持つスライス(std::submdspan)を使用すると、現在の規定では2つの問題が発生します

  1. submdspanを取った後でも連続していることが分かっている場合でも、layout_strideが使用される
    • is_always_exhaustive() == falseとなってしまう
    • 例えば代わりにlayout_rightなどの連続性が保証されるレイアウトを使用してほしい
  2. aligned_accessordefault_accessorに変換されてしまう
    • aligned_accessor::offset()std::size_tを引数に取るため
    • オフセット係数が何らかのオーバーアライメント係数で割り切れるかどうかなどコンパイル時の情報が破棄される

この提案では、入力mdspanのレイアウトポリシーがlayout_left, layout_right, layout_left_padded, layout_right_paddedである場合の1の問題を修正することを提案しています。

2の問題は破壊的変更なしでこれを行えないため、解決を提案してはいません。

P3356R0 non_invalidating_vector

要素およびその参照が無効化されない保証のあるstd::vectorのラッパを提供する提案。

std::vectorでは、要素を追加していくと必ずどこかで内部キャパシティを超えてメモリの再確保と要素の再配置が起こり、それ以前に取得された要素への参照やイテレータが無効化されます。std::vectorの特性上これはどうしようもないものではありますが、時としてこの動作は危険なものとなります。回避するにはstd::listをはじめとする要素安定性の保証があるコンテナを使用する必要がありますが、そのようなコンテナはstd::vectorのように要素の連続性(隣接性)がなく、パフォーマンスで劣ります。

この提案では、std::vectorをラップする形で要素無効化が起こらないことを保証する型を導入することでこの問題に対処しようとしています。

次の2つのものを提案しています

  • non_invalidating_vector : std::vectorの参照ラッパ
  • non_invalidating_vector_ref : std::vectorを使用するコンテナアダプタ

どちらの型でも、std::vectorをラップしてメンバ関数は要素を無効化しないもののみを提供することで要素無効化が起こらないようにしています。

non_invalidating_vectorは内部でstd::vectorを保持しており、non_invalidating_vector_refstd::vectorへの参照を保持します。non_invalidating_vector_refはより厳密なconst std::vector<T>&であり、関数引数でstd::vectorを受け取る際に使用することを意図しています。

使用感の例

void non_invalidating(non_invalidating_vector_ref<int> niv, const std::vector<int>& cv) {
  // nivの要素が無効となる心配はない
  // cvの要素が無効になる心配はない

  ...
}

int main() {
  std::vector<int> vs;
  non_invalidating_vector<int> niv; // 要素無効化の心配から解放される
  
  non_invalidating(vs, vs);
  non_invalidating(niv, vs);
  
  [](non_invalidating_vector_ref<int> vs) {
    // 要素が無効化される心配がない

  }(vs);

  for (auto &s : vs) {
    [](non_invalidating_vector_ref<int> vs) {
      // 要素が無効化される心配がない

    }(vs);
  }
}

定義の例

// non_invalidating_vectorの定義例
template<
  class T,
  class Allocator = std::allocator<T>,
  class Container = std::vector<T, Allocator>
>
class non_invalidating_vector {
private:
  actual_type inner;

public:
  // has all of the std::vector constructors
  constexpr non_invalidating_vector() noexcept(noexcept(Allocator()))
      : inner() {}
  constexpr explicit non_invalidating_vector( const Allocator& alloc ) noexcept
      : inner(alloc) {}
  constexpr non_invalidating_vector( size_type count, const T& value, const Allocator& alloc = Allocator() )
      : inner(count, value, alloc) {}
  explicit non_invalidating_vector( size_type count, const Allocator& alloc = Allocator() )
      : inner(count, alloc) {}
  template< class InputIt >
  constexpr non_invalidating_vector( InputIt first, InputIt last, const Allocator& alloc = Allocator() )
      : inner(first, last, alloc) {}
  constexpr non_invalidating_vector( const non_invalidating_vector& other )
      : inner(other) {}
  constexpr non_invalidating_vector( const non_invalidating_vector& other, const Allocator& alloc )
      : inner(other, alloc) {}
  constexpr non_invalidating_vector( non_invalidating_vector&& other ) noexcept
      : inner(other) {}
  constexpr non_invalidating_vector( non_invalidating_vector&& other, const Allocator& alloc )
      : inner(other, alloc) {}
  constexpr non_invalidating_vector( std::initializer_list<T> init, const Allocator& alloc = Allocator() )
      : inner(init, alloc) {}
  template< class R >
  constexpr non_invalidating_vector( std::from_range_t, R&& rg, const Allocator& alloc = Allocator() )
      : inner(std::from_range, rg, alloc) {}
  // all non invalidating methods are proxied
  constexpr reference operator[]( size_type pos )
  {
      return inner[pos];
  }
  constexpr const_reference operator[]( size_type pos ) const
  {
      return inner[pos];
  }
  constexpr size_type size() const
  {
      return inner.size();
  }
  // all invalidating methods are deleted
  constexpr void resize( size_type count ) = delete;
  // compatible with non_invalidating_vector_ref
  operator non_invalidating_vector_ref<T, Allocator>()
  {
      return non_invalidating_vector_ref<T, Allocator>{inner};
  }
  // can narrow scope to just the const members
  operator const actual_type&() const
  {
      return inner;
  }
};
// non_invalidating_vector_refの定義例
template<
  class T,
  class Allocator = std::allocator<T>
>
class non_invalidating_vector_ref {
private:
  actual_type& ref;

public:
  // constructors reflect this being just a pure reference type
  constexpr non_invalidating_vector_ref() = delete;
  constexpr non_invalidating_vector_ref(const non_invalidating_vector_ref&) = default;
  constexpr non_invalidating_vector_ref(non_invalidating_vector_ref&&) = default;
  constexpr non_invalidating_vector_ref operator=(const non_invalidating_vector_ref&) = delete;
  constexpr non_invalidating_vector_ref operator=(const non_invalidating_vector_ref&&) = delete;

  constexpr non_invalidating_vector_ref(actual_type& reference) : ref{reference} {}
  // all non invalidating methods are proxied
  constexpr reference operator[]( size_type pos )
  {
      return ref[pos];
  }
  constexpr const_reference operator[]( size_type pos ) const
  {
      return ref[pos];
  }
  constexpr size_type size() const
  {
      return ref.size();
  }
  // all invalidating methods are deleted
  constexpr void resize( size_type count ) = delete;
  // can narrow scope to just the const members
  operator const actual_type&() const
  {
      return ref;
  }
};

この提案は、P3274で開始された安全性プロファイルにおいて必要性が提示されたことを受けて書かれたものの様です。

P3357R0 NRVO with factory and after_factory

コピーもムーブもできないようなクラスの値を作成するためのファクトリ関数の作成を容易にするための機能の提案。

コピーもムーブもできないようなクラスの値をコード内の任意の場所で展開するために、ファクトリ関数が利用されます。RVOを活用して、関数内で初期化処理を済ませたうえで戻り値で返すことでそのようなクラスの値の可搬性を向上させることができます。

しかしその場合、関数のreturn文で直接的に対象の型の値を構築できれば(return文にprvalueを渡せれば)RVOが保証されるのですが、そうでない場合に行われるNRVOは必須ではないため必ずしも行われません。

struct non_move_and_copy {
  
  non_move_and_copy() {
    ...
  }

  // コピー+ムーブ不可
  non_move_and_copy(const non_move_and_copy&) = delete;
  non_move_and_copy& operator=(const non_move_and_copy&) = delete;

  // 初期化処理を行うなどの追加作業
  void init() {
    ...
  }

};

auto factory() -> non_move_and_copy {
  non_move_and_copy a{};

  // aを構築した後で何か追加の作業を行う必要がある
  a.init();

  // コピー/ムーブどちらかのコンストラクタは利用可能である必要がある
  return a; // ❌コンパイルエラー
}

このような用途に対応するために、NRVOが可能な場合は必須に仕様とする提案はP2025R2として提出されているもののうまく進んでおらず、C++26には間に合いそうにありません。筆者の方はこれはC++29にも間に合わないと考えており、この提案はそれが導入されるまでの間利用可能な過渡的なソリューションを導入しようとするものです。

ここで提案しているのはstd::factorystd::after_factoryの2つのライブラリ関数です

// 宣言の例

template<typename T, typename... Params, typename Setup>
T factory(Params&&... args, Setup &&setup);

template<typename F, typename... Params, typename Setup>
invoke_result_t<F&&,Params&&> after_factory(F &&f, Params&&... args, Setup &&setup);

パラメータパックの制限によりこのような宣言は行えないのですが、ここでは説明のためにテンプレートパラメータを分けています。

std::factoryは対象の型Tを指定し、そのコンストラクタ引数Params...とセットアップ関数Setupを受け取って、Tの値をParamsで構築したうえでSetupに渡した後の値を返す関数です。

int main(void) {
  // 使用例
  int i = std::factory<int>(52, [](auto &a){ ++a;});
}

std::after_factoryは対象の型の値を生成するファクトリ関数Fとその引数Params...とセットアップ関数Setupを受け取って、FParamsを渡して生成した値をSetupに渡してから返す関数です。

binary_semaphore Func(int init) {
  return std::factory<binary_semaphore>(init, [](auto &a){a.release();});
}

int main(void) {
  // 使用例
  binary_semaphore bs = std::after_factory(Func, 0, [](auto &a){ a.acquire(); });
  // Func()では、binary_semaphoreを初期値0で構築後直ぐに`.release()`を読んでから返す
  // std::after_factory()では、その値に対してさらに`.acquire()`を読んでから返す

  bs.release();
  bs.acquire();
}

どちらの関数も、巧妙な実装によってRVOを活用することでコピー/ムーブコンストラクタのチェックを回避しています。非常に複雑ですが、提案には実装例が載っています。

最初のような例はstd::factoryを使用することで正しく書き直すことができます。

// 外側のfactory()はいらないかもしれない(例を対応させるために残している)
auto factory() -> non_move_and_copy {
  return std::factory<non_move_and_copy>([](auto& a) { a.init(); });
}

この提案の内容はP2025R2のソリューション(NRVOの必須化)に比較すると劣ったソリューションではありますが、そちらの解決策がいつ利用可能になるか分からないため、それまでの比較的簡易な解決策として利用されることを意図しています。

P3358R0 SARIF for Structured Diagnostics

C++ツールが出力する診断情報の形式としてSARIFの使用を推奨する提案。

SARIF(Static Analysis Results Interchange Format)はOASIS Open projectで標準化されている、静的解析ツールの出力形式を定めたJSONベースのフォーマットです。

SARIFを利用するメリットは以下の点が挙げられます

  • 診断情報の標準化
    • SARIFは標準化されたフォーマットであるため、異なる静的分析ツールやコンパイラからの出力を統一的に扱うことができる
    • これにより、ツール間での診断情報の交換が容易になり、開発者はさまざまなツールから得られた情報を統合的に分析することができるようになる
  • 診断情報の機械処理
    • SARIFはJSONベースの機械可読フォーマットであるため、ツールによる診断情報のフィルタリングや操作が容易になる
    • 開発者はツールを使用して診断情報を分析し、デバッグや問題の検出に役立てることができる
  • 豊富な情報
    • SARIFは診断の重大度、メッセージ、ソースコードの位置などの情報を提供することができ、修正方法やヒントなどを標準化された方法で提供することもできる
  • 階層型診断
    • SARIFは論理階層をもつ診断メッセージを出力できる
    • これにより、コンセプトのようなネストした構造に対してその構造を保ったまま診断メッセージを保持することができ、コードのネストした構造に対応した診断メッセージを出力することができる
  • 動的な対話
    • SARIFは機械可読なフォーマットであるため、ストリーム的に出力されればコンパイラIDEは診断情報をより動的に処理することができる
  • ツールエコシステム
    • コンパイラ、静的解析ツール、IDE、エディタなど、さまざまなC++周辺ツールにおいてSARIFサポートが進みつつある
    • これにより、上記のメリットを開発者がより受けやすくなる土壌が整いつつある

この文書では、C++ツールとSARIF標準に対して今後取るべき方向性を提示しています

  • SARIF標準
    • 階層的な診断を表現するための標準的な方法を採用する必要がある
  • ビルドシステム
    • ビルドシステムは実行中にコンパイラ等のツールが出力したSARIF診断ファイルを読み取って早い段階でエラーメッセージを出力する
    • そのために、コンパイラIDE等のツールは実行中のビルドからSARIF診断ファイルを取得する方法を提供する必要がある
    • より理想的には、ビルドシステムによって有効化される形で、コンパイラからIDEへのSARIFストリーミングを提供する
      • MSVCはパイプによてMS Buildに直接かつリアルタイムにSARIF診断メッセージを出力している
      • CMake等のビルドツールはコンパイラIDEに依存しない形でこれを有効化できる方法を提供する必要がある
  • コンパイラ
    • clang/gcc/MSVCはそれぞれSARIFをサポートしているものの、その範囲や生成方法が微妙に異なる
    • 3つのコンパイラはすべてSARIF出力方法として、ファイルへの書き込みとSARIF結果オブジェクト(単一JSONオブジェクト)のストリーミングの両方をサポートすべき
    • 3つのコンパイラはすべて、2レベルよりも深い階層的な診断メッセージ出力をサポートすべき
  • IDE

現在のところ、このSARIFの利活用で最も進んでいるのはMSVC(Visual Studio)のようで、2レベルよりも深い階層的診断のサポートや、IDEがそれをストリームで受け取って表示(問題の詳細ウィンドウ)をサポートしています。

この文書は提案というよりはSARIFというフォーマットの周知やC++ツールでのその利用を促すようなもので、Ecosystem ISでこれを標準化したりといったことまでは提案していません。

P3359R0 Slides for P3298R0 - Implicit conversion functions

P3298R0の紹介スライド。

P3298R0では暗黙変換を利用した疑似的なoperator.演算子オーバーロードを可能にするような提案がなされていますが。このスライドは提案の背景や内容について簡単にまとめた紹介(おそらくEWGIのメンバに向けて)するためのものです。

P3360R0 Slides for P3312R0 - Overload Set Types

P3312R0の紹介スライド。

P3312R0の内容を簡単に紹介するとともに、R0の後で判明した問題点とその解決策を報告しています。

報告されている問題は、Overload Set TypeにはADL集合が追加されないことで、ここでは関数名をバッククォートでくくることでADL集合を追加するようにするソリューションを報告しています。

おわり

この記事のMarkdownソース

次月分(2024/08)の記事執筆のお手伝いを募集しています: https://github.com/onihusube/blog/issues/43