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

文書の一覧

全部で40本あります。

もくじ

P0843R9 inplace_vector

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

以前の記事を参照

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

  • sz < capacityの事前条件は、unchecked_系関数を除いてstd::bad_alloc例外をスローするようにした
  • try_系の挿入関数は、コンテナ容量が満杯の場合に入力引数の右辺値を消費しないようにした
  • Tのコピー/ムーブコンストラクタがトリビアルなら、コンテナの対応するコンストラクタもトリビアルになるようにした
  • メンバswap()は、サイズが0か要素型のムーブコンストラクタがnoexceptならnoexceptになるようにした
  • リサイズの計算量を線形にした
  • 文言の範囲外の計算(以下と未満)を修正
  • emplace系関数に対する制約を修正
  • デフォルト挿入可能性を要求するサイズのみを引数にとる単項コンストラクタを修正
  • <inplace_vector>で山かっこが抜けていたのを修正し、アルファベット順にソート
  • シーケンスコンテナの用件でカバーされている事前条件の重複を削除
  • メンバswap()と特殊化されたアルゴリズムの不要な指定を削除
  • 幾つかの記述スタイルの修正

などです。

この提案はLEWGのレビューを通過し、LWGへ転送されています。

P1068R9 Vector API for random number generation

<random>にある既存の分布生成器にイテレータ範囲を乱数で初期化するAPIを追加する提案。

以前の記事を参照

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

  • ベクトル化で安全ではない要素アクセス操作の使用に関する注釈を追記
  • 機能テストマクロ
  • タイポの修正

などです。

この提案はLEWGのレビューを通過し、LWGへ転送されています。

P1255R10 A view of 0 or 1 elements: views::maybe

任意のオブジェクトやstd::optional等のmaybeモナドな対象を要素数0か1のシーケンスに変換するRangeアダプタviews::maybe/views::nullableの提案。

以前の記事を参照

このリビジョンでの変更は、views::maybe/views::nullableに分割した後のborrowed_range性を修正、安全上の懸念を明確化したことなどです。

P2264R5 Make assert() macro user friendly for C and C++

assertマクロをC++の構文に馴染むように置き換える提案。

このリビジョンでの変更は、渡された式を暗黙的にstatic_cast<bool>することについての議論を追加したことです。

P2542R4 views::concat

P2542R5 views::concat

同じ要素型を持つ異なる型の範囲を連結するRangeファクトリ、views::concatの提案。

R4での変更はconcat_expertを追加したことです。

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

  • concat_expertの削除(R3へ戻す)
  • static_cast<difference_type>を適切に使用
  • cartesian_product_viewを再利用して、concat-is-bidirectionalを定義

などです。

P2686R2 constexpr structured bindings and references to constexpr variables

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

以前の記事を参照

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

  • 記号的なアドレス指定(Symbolic addressing)のための文言を追加
  • constinit構造化束縛を許可

などです。

P2748R2 Disallow Binding a Returned Glvalue to a Temporary

glvalueが暗黙変換によって一時オブジェクトとして参照に束縛される場合をコンパイルエラーとする提案。

以前の記事を参照

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

  • ライブラリの文言を削除し、コア言語の文言を追加
  • 実装経験について追記

などです。

筆者の方は、この提案の変更をパッチしたclang16.0.6をビルドし、clangそのものをビルドすることに成功したとのことです。その際、13件のテストが失敗しましたが、この変更の意図に反したものではなかったようです。その後、BloombergのBDEやchromium等をビルドすることもできたとのことです。この結果は、この提案の変更後に既存の出荷済みのコードにおいてエラーが発生する可能性が低いことを示しています。

P2755R0 A Bold Plan for a Complete Contracts Facility

C++契約プログラミング機能について、将来的な完成形のための計画を練る提案。

現在、C++26に向けて契約プログラミング機能を導入すべく作業が続けられていますが、それは本来の契約プログラミング機能の合意がとれたサブセットです。本来想定される契約プログラミング機能はより広いものですが、そこにはまだ合意がとれていないものや議論が紛糾しているもの、実装経験が乏しいものなどが含まれており、それらを全て導入しようとすればさらに長い時間が必要となります。

この提案は、そのような最終的なC++契約プログラミング機能がどのようなニーズの元に構成され、何を備えているべきかを説明し、提案するものです。必然的に、C++26に導入されようとしている機能を超えた部分についての提案になっています。

1章を除いた残りの部分は

  • 2章 : 契約機能のC++におけるユースケースについての説明
  • 3章 : 提案する個々の機能についての解説や提案
  • 4章 : 3章を踏まえた、より大規模で現実的なサンプルの提示
  • 5章 : 最終的に対処する必要がある、言語と契約機能についての他の(3章に含まれない)側面についての議論

のような構成となっています。

P2760R0 A Plan for C++26 Ranges

C++26に向けての、<ranges>ライブラリ関連作業の予定表。

C++23では<ranges>ライブラリ関連作業のまとめをP2214で行っていました。最終的に、そこで提案されていた優先度1のほとんどのものと優先度2の一部のものをC++23に導入することができました。

この提案は、C++26でも同様に作業を優先付けして分類し、追加の考慮が必要なものについてまとめておくためのものです。

この提案で優先度1とされているものは次のものです

  • Rangeアダプタ
    • views::concat
    • take/dropのファミリ
      • views::drop_lastviews::take_last
      • views::drop_last_whileviews::take_last_while
      • views::drop_exactlyviews::take_exactly
      • views::slice
    • 単純な合成アダプタ
      • views::transform_join
      • views::replaceviews::replace_if
      • views::removeviews::remove_if
      • views::upto
    • views::as_input
    • views::cache_last
    • views::chunk_on
    • views::cycle
    • views::delimitviews::c_str
    • より複雑な状況における、より多くのアダプタのborrowed_range対応
    • ジェネレータ
      • views::scan
      • views::generateviews::generate_n
  • Rangeアルゴリズム
    • ranges::reduce
    • ranges::sum
    • ranges::product

他にも優先度2と3の分類がありますが、C++26に向けてはこの優先度1のものについての作業を優先的に行う予定です。

P2762R1 Sender/Receiver Interface For Networking

現在のNetworking TSにP2300のsender/receieverサポートを入れ込む提案。

以前の記事を参照

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

  • 決定を求めるために投票する項目をまとめたセクションを追加
  • Networking TSをターゲットとする理由を明確化
  • receiverからschedulerを取得することが完全に明白な選択ではない理由を追加
  • 提起されたトピックを把握し、それに対する回答や変更を纏めたセクションを追加
  • ネットワークアルゴリズムに関するセクションを追加
  • Networking sendersenderアダプタである必要がある理由を説明するセクションを追加
  • 提案するNetworking senderとしてasync_waitを追加

などです。

P2833R2 Freestanding Library: inout expected span

C++23のライブラリ機能の一部をFreestanding指定する提案。

以前の記事を参照

このリビジョンでの変更は、shared_ptrの前方宣言を削除した事です。

P2846R1 size_hint: Eagerly reserving memory for not-quite-sized lazy ranges

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

以前の記事を参照

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

  • 動機の説明の修正
  • ベンチマークの追加
  • sized_rangeapproximately_sized_rangeを包摂するように変更
  • 命名に関するセクションを追加

などです。

P2865R3 Remove Deprecated Array Comparisons from C++26

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

以前の記事を参照

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

  • nullptr定数と配列の同値比較についての問題を追記
  • CWGのレビューを受けての文言の修正
  • 提案する文言の変更

などです。

P2866R1 Remove Deprecated Volatile Features From C++26

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

以前の記事を参照

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

  • 対象のグループとしてSG22を追加
  • 欠けていたライブラリについての分析を追加
  • ライブラリ内に残っている構造化束縛のvolatile依存についての分析を追加
  • 提案する文言の更新

などです。

P2867R1 Remove Deprecated strstreams From C++26

長く非推奨となっていた、std::strstreamを削除する提案。

以前の記事を参照

このリビジョンでの変更は、提案する文言の全体を追加したことなどです。

P2868R2 Remove Deprecated std::allocator Typedef From C++26

std::allocatorにある非推奨化された入れ子型定義を削除する提案。

以前の記事を参照

このリビジョンでの変更は、提案する文言の更新などです。

P2869R2 Remove Deprecated shared_ptr Atomic Access APIs From C++26

C++20で非推奨とされた、std::shared_ptrのアトミックフリー関数を削除する提案。

以前の記事を参照

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

  • std::atomic<std::shared_ptr>に切り替えた場合のヘッダの問題についての新しいオプションを追加
  • 提案する文言の更新

などです。

アトミックフリー関数を使っていたstd::shared_ptrstd::atomic<std::shared_ptr>に変更すると、自動的に<atomic>の同名フリー関数が使用されますが、それは<memory>ではなく<atomic>にあるため、追加のヘッダインクルードが必要となります。この提案ではその問題のために

  1. ユーザー任せ(何も対処しない)
  2. std::atomic<std::shared_ptr>のためのフリー関数を<memory>に追加
  3. 最小のstd::atomic<T>フリー関数を<memory>に追加
  4. 全てのstd::atomic<T>フリー関数を<memory>に追加
  5. <memory><atomic>をインクルードするようにする

の5つのオプションを提示しています(まだ未決定)。

P2870R2 Remove basic_string::reserve() From C++26

C++20で非推奨とされたstd::string::reserve()C++26に向けて削除する提案。

以前の記事を参照

このリビジョンでの変更は提案する文言の更新などです。

P2871R2 Remove Deprecated Unicode Conversion Facets From C++26

C++17で非推奨とされた<codecvt>ヘッダをC++26で削除する提案。

以前の記事を参照

このリビジョンでの変更は、提案する文言の修正などです。

この機能の代替が無いまま削除することへの懸念がありますが、LEWGのレビューと投票の際にはそれを認識しながらも削除することは正しい選択だと考えている人が多数だったようです。

P2872R2 Remove wstring_convert From C++26

C++17で非推奨とされたwstring_convertC++26で削除する提案。

以前の記事を参照

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

  • 提案する文言の更新
  • P2874R2に関連する文言衝突の懸念についてを削除
  • /W3を使用してMSVCを再テスト

などです。

P2875R2 Undeprecate polymorphic_allocator::destroy For C++26

C++20で非推奨とされたpolymorphic_allocator::destroyの非推奨化を解除する提案。

以前の記事を参照

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

  • allocator_traitsのメンバ型を削除した場合の影響との比較を追加
  • 提案する文言の更新

などです。

P2885R2 Requirements for a Contracts syntax

C++契約プログラミング機能の構文の選択のための評価基準や要件をまとめる文書。

以前の記事を参照

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

  • 投票結果と議論の追記
  • 投票結果を受けてintroductionとsummaryを変更
  • セクションの相互参照の修正

などです。

P2909R1 Fix formatting of code units as integers (Dude, where's my char?)

P2909R2 Fix formatting of code units as integers (Dude, where's my char?)

std::format()charを整数値としてフォーマットする際の挙動を改善する提案。

以前の記事を参照

R1での変更は

  • タイトルの変更
  • 影響を受けるフォーマットオプションを明確化
  • printfとの比較を追加
  • SG16の投票結果を追加
  • charwchar_tとしてフォーマットする場合の処理を修正

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

  • __cpp_lib_formatバンプするようにした
  • charwchar_tとしてフォーマットする場合に欠けていたキャストを追加

などです。

この提案はすでにLEWGのレビューをパスしてLWGに転送されています。

P2932R0 A Principled Approach to Open Design Questions for Contracts

契約機能に関する未解決の問題についての設計原則に基づく解決策の提案。

C++26に向けて契約プログラミング機能を導入するべく、SG21での作業は順調に進行しています。今年に入ってから、主に契約注釈の意味論についての議論が交わされ、合意の下でいくつかの提案がMVPにマージされました。

MVP仕様(P2521R4)に対してこれらをマージすれば、C++の言語機能としての契約プログラミング機能がすぐにでも得られますが、いくつかのエッジケースや設計ポイントにはまだ対処すべき問題が残っています。

契約プログラミングに関する議論が紆余曲折を経ていることもあって、C++における契約プログラミング機能の設計に関するドキュメントは存在していません。この提案は、C++26の契約プログラミング機能のための残りの作業の完遂のために、C++における契約プログラミング機能の設計意図を体系化する基本原則をまとめ、それらをもとに現在残っているいくつかの問題への回答を示すものです。

提案されている原則とは次の4つです

  1. 契約注釈は平易な言語(非プログラミング言語)による契約をチェックする
    • 関数の宣言に付加された契約注釈は、それが評価されたときにその関数の平易な言語による契約の違反を特定しなければならない
  2. プログラムのセマンティクスは(実装によって)選択された契約注釈のセマンティクスから独立している
    • 契約注釈のセマンティクスは、それがなされている地点を取り巻く近接した部分のコンパイル時のセマンティクスに影響を与えてはならない
  3. 無視される契約述語(つまり、条件が評価されない場合)はゼロオーバーヘッドである
    • 無視される契約注釈付近のコードの動作は、その注釈がコメントアウトされているかのようになる
    • ただし、無視される場合でも構文チェックは継続される(エンティティがODR-usedであるかは変わらない)
  4. 未定義動作はill-formed(コンパイルエラー)とする
    • 現在の契約機能ではサポートされていないユースケースに対応するために、未定義(安全でない)や実装定義(ポータブルでない)ではなく、未解決の振る舞いをill-formedとすることで拡張性を柔軟に保つことを好む

これらの原則をベースとして、この提案では次の問題についての解決が提案されています

  1. トリビアルな特殊メンバ関数と契約
    • 契約注釈は関数のトリビアル性に影響を与えない
    • そのような契約注釈は評価されない可能性がある
  2. 暗黙のラムダキャプチャ
    1. ラムダ式における契約注釈を許可する
    2. 契約注釈中のODR-usedは暗黙的にキャプチャしない
      • 契約注釈(assertを含む)はラムダ式がキャプチャしていないものをキャプチャしない
      • それがなされている場合はill-formed
  3. 定数式における契約チェック
    1. コンパイル時の契約違反は診断を発行する
      • 契約違反プロセスがコンパイル時に評価されると(observeもしくはenforceセマンティクスでコンパイル時に評価される場合に契約違反が発生すると)診断が発行される
        • enforceセマンティクスによって評価されている場合、プログラムはill-formed
    2. セマンティクスはコンパイル時に選択される可能性がある
      • 契約注釈を評価する際の実装定義の評価セマンティクスの選択は、コア定数式の一部として評価される可能性がある
      • つまり、契約注釈の評価の一部としてそのセマンティクスを選択しても、それを含む式はコア定数式ではなくならない
    3. コンパイル時に契約条件が評価される場合に、定数式で実行不可能な式を使用するのはill-formed
      • コア定数式として有効な式において、コア定数式として有効ではない契約条件を評価すると診断が発行される
        • enforceセマンティクスによって評価されている場合、プログラムはill-formed
    4. コンパイル時定数ではないものの、定数式として有効な初期化子内の契約注釈は、実行時に評価される
      • 潜在的コンパイル時定数である非コンパイル時定数変数の初期化式が、その全ての契約注釈がignoreセマンティクスで評価されたときにコア定数式とならない場合、その初期化式はコア定数式ではない
      • 従って、異なるセマンティクスを持つ可能性のある契約条件を使用してその式を再度定数評価しようとすることは行われない
    5. 定数式における契約注釈の評価について、まとめ
      • 式が定数式として有効であるかを判断するときは、まずignoreセマンティクスで契約注釈とその式を評価することで、式が定数式として有効であるかを調べる
        • 式は定数式として有効である、もしくは、定数式として有効ではないがmanifestly constant-evaluatedなコンテキストにある場合
          • 実装定義の方法で選択されたセマンティクスに基づいて、契約注釈を評価しながら式を再評価する
            • observeセマンティクスの元で契約違反が発生した場合、診断を発行し実行(コンパイル)を継続
            • enforceセマンティクスの元で契約違反が発生した場合、プログラムはill-formed、診断を発行する
            • 式が定数式で実行できない場合、プログラムはill-formed
        • それ以外の場合、式は定数式ではない
  4. 仮想関数 : 次のどちらか
    1. 契約注釈は継承されない
      • 宣言に事前条件・事後条件を持たない仮想関数は、事前条件・事後条件を持たない
      • オーバーライドされる関数の契約条件を継承しない
    2. 仮想関数には契約注釈を行えない
      • 事前条件・事後条件をvirtualとマークされた関数もしくはそれをオーバライドする関数に行うのはill-formed
  5. コルーチン
    • コルーチンには契約注釈を行えない
      • コルーチンである関数に事前条件・事後条件を指定することはill-formed
      • コルーチン本体内でのassertは許可
  6. 最初の宣言の契約
    1. 契約注釈は最初の宣言でのみ行える
      • 関数の宣言がその最初の宣言ではない場合、事前条件・事後条件を持つことはできない
    2. 関数の契約注釈のリストは一貫している必要がある
      • ある関数について、全ての翻訳単位における最初の宣言における契約注釈リストは同じである必要がある。ただし、診断は不要

この提案が採択されることで、P2896R0で収集されている全ての未解決の問題は解決されます。そして、それによって(構文の選択は必要ですが)契約機能の提案はC++言語に統合するための準備ができている状態になります。

P2935R1 An Attribute-Like Syntax for Contracts

P2935R2 An Attribute-Like Syntax for Contracts

C++契約プログラミングのための構文として属性構文を推奨する提案。

以前の記事を参照

R1での変更は、文書の改善などです。

R2での変更は

  • 大体区切り文字(非属性構文)に関する投票結果を追記、それを受けてそれを使用する提案を削除
  • 宣言終了構文、構文拡張およびその議論についてのアンケート結果の追記
  • この提案としての具体的な構文の提案を追加

などです。

P2944R2 Comparisons for reference_wrapper

reference_wrapperに比較演算子を追加する提案。

以前の記事を参照

このリビジョンでの変更は、曖昧さに関するセクションを追加し、それに応じた提案する文言の更新です。

R1では、reference_wrapperに次のような比較演算子を追加することを提案していました

template<class T>
class reference_wrapper {
  friend constexpr bool operator==(reference_wrapper, reference_wrapper);
}

しかし、これだと現在有効な一部の比較が曖昧になるケースがありました。

auto check(int i, std::reference_wrapper<int> r) -> bool {
  return i == r;  // 現在はok、このR1の内容だと曖昧になる
}

現在、この比較はrint&に暗黙変換(reference_wrapper::operator int&による)されることで組み込みの演算子によって比較が行われます。R1の変更後、追加されたreference_wrapperoperator==(上記のもの)も有効な候補として認識され、その結果2つの候補の間でオーバーロード解決に失敗するようになります。これはint以外の型でも同様に起こります。

このことのチェックは、非constTに対して次のようなコンセプトを用いて行うことができます。

template <class T>
concept ref_equality_comparable = requires (T a, T const ca, Ref<T> r, Ref<T const> cr) {
  // the usual T is equality-comparable with itself
  a == a;
  a == ca;
  ca == ca;

  // Ref<T> is equality-comparable with itself
  r == r;
  r == cr;
  cr == cr;

  // T and Ref<T> are equality-comparable
  a == r;
  a == cr;
  ca == r;
  ca == cr;
};

R1の提案では、全ての型でこの比較は失敗します。

前述のように、最後のグループに問題があり、ca == r以外の比較が失敗します。const T&reference_wrapper<T>に変換できないため曖昧になっておらず、それ以外の3つについて対処する演算子を追加する必要があります。

また、このテストによってr == crにも問題があることがわかるため、これを行う演算子も追加する必要があります。

それによって、追加を提案する比較演算子は3つになります。

template<class T>
class reference_wrapper {
  friend constexpr bool operator==(reference_wrapper, reference_wrapper);
  friend constexpr bool operator==(reference_wrapper, T const&);
  friend constexpr bool operator==(reference_wrapper, reference_wrapper<T const>); // 非constなTについてのみ有効
};

これにより、前述のテストを任意の型Tについてパスすることができるようになります。

P2951R3 Shadowing is good for safety

変数のシャドウィングを活用した、安全性向上のための言語機能の提案。

以前の記事を参照

このリビジョンでの変更は、内容の軽微な修正などです。

P2955R1 Safer Range Access

std::vector等コンテナ向けの安全な要素アクセス関数の提案。

以前の記事を参照

このリビジョンでの変更は、内容の軽微な修正のみです。

P2961R0 A natural syntax for Contracts

契約プログラミング機能のための構文の提案。

C++26に向けた契約プログラミングの議論において、あとは構文の選択が大きな議題として残っています。現在のところ構文の候補として有力なのは属性like構文と呼ばれるもので、これはC++20の頃の構文を踏襲し新しい文法やキーワードを発明する必要がなく、属性の無視という性質を自然に利用でき、さらにGCCによる実装経験があります。

属性like構文にもいくつか欠点があります

  1. 契約注釈の区切りのトーク[[ ... ]]が構文として重い
    • 一部のユーザーからは醜いと認識されている
  2. 契約構文は属性と同様の記法を利用するが属性ではないため、混乱が生じる
    • 契約構文は違反ハンドラを通じるなどして、関数から新しいコードパスを作成できるが、標準属性はこのようなことを行うように設計されていない
  3. 契約注釈を置ける構文上の位置は関数宣言の自然な読み取り順序に反している
    • 属性の置ける位置を再利用するため、後置戻り値の前(overriderequires節の前)に事前条件と事後条件がくる
  4. assertは式ではないため、Cのassertの完全な代替となり得ない
  5. 3と4を属性構文のまま解決しようとすると、属性構文の利点が失われる
    • 現在それらが可能なように標準属性はできていない、そのため実装経験もない
  6. 属性構文では、その内部の述語の前に:がくる場合に、それより前の内容に区切りを導入しない
    • 視覚的な情報の区別(契約種別や戻り値の名前、ラベルなどの見分け)が難しくなり、将来的に構文解析の曖昧さを生じさせる
  7. 契約注釈自体に属性を付加する場合、属性内の属性という文法を導入させなければならない

属性like構文の代替としていくつか構文の提案があります

P2373は契約注釈のためにcontract-kind ( predicate )という新しい文法を発明しました。そしてそれに加えて幾つかの設計上の選択を提案しました

  • assertionをinconditionにリネーム
  • pre, post, assertの代わりに、precond, postcond, incondを使用
  • precond, postcond, incondを文脈依存ではない完全なキーワードとして追加
  • 事後条件では、戻り値をresultという固定的な名前で参照する

これらのことがSG21では受け入れられなかったようで、この提案の方向性も問題を抱えています。

この提案は、P2461R1とP2373R0で提示されたアイデアを流用しながら、それら2つと属性like構文にある問題を解決しつつP2885で示された契約構文に対する要件を満たすような、契約プログラミング機能のためのより自然な構文を提案するものです。

この提案の目指す設計ゴールは次のようなものです

  • 構文は既存のC++に自然に馴染む。
    • 契約機能に慣れていないユーザーでも混乱を招くことなく直感的に理解できるものである必要がある
  • 契約注釈構文は、属性やラムダ式など既存のC++の構成要素に似ていてはならない
    • ぱっと見で認識可能な独自の設計空間に置かれているべき
  • 構文はエレガントかつ軽量である
    • 必要以上にトークンや文字を使用するべきではない
  • 読みやすくするために、一次情報とニ次情報を構文的に分離する
    • 一次情報(条件種別、契約条件式、戻り値名、キャプチャなど)をそれ以外のニ次情報(ラベルなど)よりも視覚的に強調する

これらの目標は、現在の構文候補が満たしていないものでもあります。

この提案による構文は、P2461R1とP2373R0で提案されたアイデアをベースとしており、関数の一番最後の位置にpre(...)post(...)によって事前条件と事後条件を指定します。

float sqrt(float x)
  pre (x >= 0);     // 事前条件

int f(int x)
  post (r: r > x);  // 事後条件

pre(...)post(...)のように()の内部に述語(契約条件)を指定することは、if(expr)while(expr)などの既存のC++構文と一貫しており、非常に自然なものです。また、事後条件で戻り値を使用する場合は、条件式の前に:を置いて、その前で任意の名前を使用できます。

pre, postはどちらも文脈依存なキーワードであり、関数の一番最後(requiresの後)にくることもあって、関数の他の部分でpre, postを任意のエンティティの名前として使用することができます。

template <typename T>
auto f(T x) -> bool
  requires std::integral<T>
  post (x > 0);

文法の詳細は提案の4.1 Grammarに記載されています。少し大きめなのと差分をわかりやすく表示できないためここには転記しません。

assertも事前・事後条件と同様の構文によるのですが、この場合既存のCのassertマクロと衝突してしまうので、それを回避する必要があります。そのために、キーワードを少し変更しています

void f() {
  int i = get_i();

  // C assertと衝突する
  assert(i >= 0);

  // この提案によるassert
  assrt(i >= 0);
}

ここではassrtとしていますが、他の選択肢(ass, assertion, co_assertなど)でも良いとしています。コルーチンのキーワードであるco_yieldなどと同様に、ユーザーはすぐに慣れるとしています。

assrt(expr)は式であるため、現在のassertマクロと同様に式として使用できます。

class X {
  int* _p;
public:
  X(int* p)
   : _p((assrt(p), p))  // ok
  {}
};

これによって、単純な文字列置換によってassertマクロからassrt式へ移行することができます。

この提案ではさらに、P2885で示されているC++26契約の後の機能拡張のアイデアを考慮しています。

例えばキャプチャ

void vector::push_back(const T& v)
    post [old_size = size()] ( size() == old_size + 1 ); // 初期化キャプチャ、関数の実行によって変更される前の値を保存する

ここでは、キャプチャ関連に限ってのみラムダ式のキャプチャ構文を流用しますが、契約種別のワード(post)が先行し契約条件が()内で記述されることでラムダ式との混同を回避しています。

他の例として、契約注釈に対するrequires

template <typename T>
void f(T x)
  pre (x > 0) requires std::integral<T>;

// もしくは
template <typename T>
void f(T x)
  pre requires std::integral<T> (x > 0);

契約注釈に対するrequires節が述語の前後どちらに来るべきかはまだ議論されていませんが、この提案による構文はどちらの場合でも拡張可能です。

契約注釈に対する属性指定

template <typename T>
void f(T x)
  pre (x > 0) [[deprecated]];

これも属性がどこに来るかはまだ決まっていませんが、やはりどちらも受け止めることができます。属性like構文の場合のように属性内属性を考慮する必要もありません。

ラベルの指定

ラベルとは、契約注釈に対するメタ注釈となるものです。現在想定されているのは、契約注釈のセマンティクスを指定するものです。

void f(int x)
  pre (x > 0) [audit]; // or <audit>, or {audit}, or [{audit}], or @audit ...

前のものと同様にこれも位置について異論がありますが、どちらでも受け止めることができます。

戻り値の分解

構造化束縛に近い構文によって、事後条件で参照する戻り値を分解して参照する拡張が考えられます

std::tuple<int, int, int> f()
  post ([x, y, z] : x != y && y != z);

これらの構文はまた、P2885で示された契約構文に対する要件の多く(将来の拡張も含めて)を考慮し、満たすように設計されています。

P2461(closure-based syntax)の著者の方はこの提案がP2461のアイデアを包含し改善するものであるとしてこの提案を支持し、P2461の追及を停止することにしたそうです。そのため、契約機能の構文提案としては、属性like構文とP2737R0の条件中心構文、及びこの提案の3つがアクティブとなっています。

P2963R0 Ordering of constraints involving fold expressions

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

現在のコンセプトの制約の半順序ルールでは、畳み込み式はそれ全体で1つの原子制約式と扱われます。それによって、プログラマの意図と異なる制約順序付けが行われ、特に可変長テンプレートにおける適切な制約を難しくしています。

template <class T>
concept A = std::is_move_constructible_v<T>;

template <class T>
concept B = std::is_copy_constructible_v<T>;

template <class T>
concept C = A<T> && B<T>;


template <class... Ts>
  requires (A<Ts> && ...)
void g(T...);

template <class... Ts>
  requires (C<Ts> && ...)
void g(T...);

この例のコンセプトCは型に対してコピー構築可能性とムーブ構築可能性を要求しています。同じ型のシーケンスTsに対して、C<Ts> && ...A<Ts> && ...を包含しているため、制約の順序づけもそれを反映したものとなることが期待されます。しかし、現在は畳み込み式はその全体で1つの原子制約式として扱われて順序づけが行われてしまうため、同じ型のシーケンスTsに対してC<Ts> && ...A<Ts> && ...の間には包摂関係が成立せず順序づけ不可能となります。

その結果、上記の例ではABを満たす型Tを1つ以上g()に渡すと、2つのオーバーロードの間で優先順位が付かないため呼び出しは曖昧になり、コンパイルエラーとなります。

この提案は、畳み込み式においてはその全体ではなく、含まれる個別の制約式を原子制約式として扱うようにして、この問題を解決しようとするものです。

提案では次のような手順によって畳み込み式の半順序を規定しようとしています

  1. 畳み込み式を正規化して、二項畳み込み式を単行畳み込み式に変換する
    • (init && ... && Pack)もしくは(Pack && ... && init)を、(Pack && ...)initを正規化したものを&&で繋げた形に変換
    • ||も同様
  2. (... op Pack)(Pack op ...)に変換し、以降区別しない
    • op&& ||のどちらか
  3. 残った(Pack && ...)もしくは(Pack || ...)について、比較する2つの畳み込み式のパラメータパックが同じサイズならば、通常の原子制約式の包摂のルールに従って包摂関係が判定される

この提案の一部はclangにおいて実装されているようで、そこでは実装においてもコンパイル時間においても大きな影響はなかったとのことです。また、この提案は現在曖昧になるオーバーロードを意図通りに順序づけするだけで、既存のコードの動作を変えるものではありません。

P2966R0 Making C++ Better for Game Developers -- Progress Report

P2966R1 Making C++ Better for Game Developers -- Progress Report

ゲーム開発者にとってより使いやすいものへC++を進化させるための作業についての報告書。

この文書は、SG14のゲーム開発の経験を有するメンバーが中心となってゲーム開発者の視点からC++を改善するために行っている作業のついての進捗方向をおこなうものです。

取り組みは2019年12月頃から開始され、最初は主に情報(要望当)の収集を行っていたようです。そこで得られた情報はSG14に持ち込まれ、議論・分類・選択が行われました。

この議論及びプロセスの目的はゲーム開発にとって最も効果的な機能のサブセットを特定し、個別の提案を提出することでさらに議論を進めていくためのものでした。この文書はそのような取り組みの現時点での成果報告を行うものです。

そのプロセスにおいては、次のような原則の下で議論が行われました

  • C++をよりシンプルにすることは良い事
  • C++をより教えやすくすることは良い事
  • パフォーマンスへの悪影響を避ける
  • デバッグは重要

この作業の結果として追求しようとする要求(機能)は必ずしもこの原則のすべてを満たすものではありませんが、すくなくともこの原則に違反しないものとなっています。

作業においては要求を次のようなカテゴリで分類しています

  • Compile-Time Computing
  • Memory Allocation and Deterministic Behavior
  • Attributes
  • Move Semantics
  • Handling Disappointment
  • Pattern Matching
  • Tooling and Ease-of-Coding
  • Networking
  • Parallel and Concurrent Computing
  • Logging and I/O
  • Numeric Computing
  • Miscellaneous

そのうえで、それぞれの要求についてSG14として次のような基準でガイダンスを行っています

  • この機能はSG14が望むものか?
  • この機能を提案として追求する場合、SG14単独で追求すべきか、関連グループと強調すべきか?
  • この機能は既存の言語機能で代替できるものか?その場合、この機能を追求する価値はあるか?
  • 望ましい代替のアプローチは存在するか?

現在のリビジョン(R1)では、これらの事をベースとして現在補足されている要求がリストアップされています。

SG14のメンバーは、ここで挙げられているいくつかのものについて個別の提案を書く予定のようです。

P2968R0 Make std.ignore a first-class object

std::ignoreは主にstd::tieでtuple-likeなオブジェクトを分解しながら受ける際に、一部の要素を無視するためのものです。これは純粋なライブラリオブジェクトであり、あらゆる型を受けながら何もしない代入演算子を定義することで実装されています。

// std::ignoreの実装例
struct ignore_type {

  template<typename T>
  void operator=(T&&) const {}
};

inline constexpr ignore_type ignore;


auto f() -> int {
  return 10;
}

auto g() -> std::string {
  return "str";
}

int main() {
  ignore = 1.0; // ok
  ignore = f(); // ok
  ignore = g(); // ok
}

このため、関数の実行結果を捨てていることを明示する、とりわけ[[nodiscard]]関数の結果を明示的に捨てる(ことで警告を抑制する)ためにもよく使用されています。

[[nodiscard]]
int f() {
  return 10;
}

int main() {
  f();                    // 警告が発せられる
  std::ignore = f();      // 警告されない、厳密には標準で保証された動作ではない
  (void)f();              // 警告されない、合法
  static_cast<void>(f()); // 警告されない、合法
}

このことはC++コアガイドラインなどでも推奨されていますが、std::ignoreの規定はstd::tieで使用することしか想定しておらず、その実装について何も指定していない(実装例のような代入演算子を持つかどうか指定されていない)ため、実際にはこの振る舞いは標準が保証するものではありません。とはいえ、主要なC++標準ライブラリ実装は全てテンプレート代入演算子によってこれを実装しています。

現在のところ完全に合法的に同じことを行う方法は上記例の下2つの明示的キャストによるものだけですが、CスタイルキャストC++では推奨されない場合が多く教育可能性の問題があり、static_castは冗長です。コアガイドラインやcppreferenceのサンプル、在野のライブラリドキュメントなど、多くの場所でこのイディオムは好んで使用されており、この使用方法をきちんと標準化する必要があります。

この提案は、std::ignoreの実装がテンプレートであらゆる型のオブジェクトを取り何もしない代入演算子によるものに(上記例のように)なるように指定することで、このイディオムをwell-definedにしようとするものです。

この提案によるメリットは次のようなものです

  • コードの意図を伝えるより適切な自己文書化コードを促進
  • 安全性が重要な環境におけるC++初心者プログラマへの教えやすさの向上

また、同時に、std::ignoreの型がconstexprコンストラクタを持つことを指定することで、LWG Issue 2933を解決することも含んでいます。

P2971R0 Implication for C++

記号論理における含意記号の振る舞いをする=>演算子の提案。

ここで提案されている=>演算子記号論理における含意記号と同じ振る舞いをするもので、p => q!p || qと同じ結果となります。

p q p => q !p || q
true true true true
true false false false
false true true true
false false true true

含意p => qは文章の中や日常的な使用において、通常の文として表現されることが一般的です。その場合の表現は通常、「pならばq」や「もしpが成り立つならばqが成り立つ」のように言われます。

英語においては、p => qは、pを前件(antecedent)、qを後件(consequent)として、“If antecedent, then consequent.”のように綴られます。

このため、プログラミングの初学者が、この含意の表現としてのifプログラミング言語の条件付き制御構造としてのifを混同してしまうケースが多々あるようです。

if (cond) {...}においてのifwhenin the event thatの意味で使用され、そこに含意は全く関係がなく、その流れは条件付き制御構造でしかありません。含意は条件付き制御構造とは無関係ですが、if文の流れを含意だと思ってしまう場合があるようです。

ifと含意が相互作用することがあるのは、if (p => q) {...}のようにif文の条件式(の一部)を含意が構成する場合です。この場合、含意の表現とそのコードの表現が一致し、読みやすさや教えやすさが向上します。

また、含意の表現による条件の指定は、規格書中(特にライブラリ機能の規定)においてよく使用されているようです。

  • [unique.ptr.single.general]/2:
    • If the deleter’s type D is not a reference type, D shall meet the Cpp17Destructible requirements.

    • static_assert( nonreference_type<D> => destructible_type<D> );
  • [optional.relops]/2:
    • Returns: If x.has_value() != y.has_value(), false; otherwise if x.has_value() == false, true; otherwise *x == *y.

    • return (x.has_value() == y.has_value()) and (x.has_value() => *x == *y);
  • [out.ptr.t]/3:
    • If Smart is a specialization of shared_ptr and sizeof...(Args) == 0, the program is ill-formed.

    • static_assert( shared_ptr_type<Smart> => sizeof...(Args) > 0uz ) );

提案にはこれ以外にも3つの例が示されています。

このような含意は、演算子オーバーロードも含めたユーザ定義の関数によって書くことができません。なぜなら、短絡評価を実現できないからです。短絡評価も含めて実現しようとするとマクロに頼るほかありません。

また、言語組み込みの=>演算子requires式やコンセプト定義等制約の文脈でも条件の表現や読みやすさの改善に役立つ可能性があります。

これらの理由から、この提案は含意記号と同等の意味論を持つ論理演算子=>を組み込みの新しい演算子として追加することを提案しています。

提案では、p => q!p || qと同じセマンティクスを持つように設計されています

  • =>演算子の優先順位は||と同じ
  • =>演算子は左結合
  • =>演算子は短絡評価される
    • p => qにおいて、pfalseならばqを評価しない

この提案ではまた、標準ライブラリの条件指定をこの演算子によって表現するようにすることも提案しています。

P2972R0 2023-09 Library Evolution Polls

2023年9月に行われる予定のLEWGの投票の予定表。

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

全てC++26に向てけのライブラリ機能の提案ですが、一部C++23のDRなものがあります。

P2973R0 Erroneous behaviour for missing return from assignment

代入演算子の定義時にreturnを忘れた場合を、UBからEB(erroneous behaviour)として*thisを返すように変更する提案。

クラス型の代入演算子を変なオーバーロードではなく適切に定義しようとする場合、代入操作を記述するので満足してreturn *this;を書き忘れるのは比較的よくあることです。

struct Foo {
  Foo& operator=(const Foo& rhs) {
    x = rhs.x;
    y = rhs.y;
    // error: forgot "return *this;"!
  }

  int x, y;
};

残念ながら、通常の関数同様に非void戻り値型関数でreturnを忘れてもエラーにはならず警告止まりです。これを修正せずに実行してしまうと、未定義動作となります。

P2795では、未初期変数の読み取りに伴う未定義動作を修正するために、erroneous behaviour(EB)という新しい動作状態を定義しようとしています。EBはその名の通り間違った動作(エラー)ではありますがUBではなくWell-definedな動作であり、その動作は未定義ではなく規定された、あるいは実装定義の特定の動作をすることが求められます。たとえば、P2795では未初期化変数を特定の値(通常は0、コンパイルオプションで任意の値を指定可能)に初期化しておくことで、少なくとも読み取りを未定義ではなくすとともに読んでしまった場合の動作を推測可能(デバッグで気付きやすい)なようにしようとしています。

EBはP2795で導入されようとしている新しい概念でありまだ標準には導入されていませんが、これは未初期化変数読み取り以外の現在UBとされているものに対して適用可能である可能性があります。この代入演算子return忘れという問題はそこで提示されている原則を満たしています

  • 危害の可能性
    • 一般的な最適化では、returnが欠落した代入演算子を実行すると、予期しないコードが継続して実行される可能性がある
  • 検出可能性
    • returnの欠落はコンパイル時に検出可能であるべき。現在のほとんどのコンパイラはこれについて警告を発することができている
  • オプトアウト可能性
    • std::unreachable()を挿入することで、実行されてないことがユーザーにわかっているブランチなどで現状を復帰(UBに)することができる

これらのことに基づいて、この提案はreturn文が無いコピー/ムーブ代入演算子の末尾での動作を、UBではなくEBとしてreturn *this;と等価である、と規定しようとするものです。

P2976R0 Freestanding Library: algorithm, numeric, and random

<algorithm>, <numeric>, <random>にある一部の機能をフリースタンディング指定する提案。

この提案は、ヒープ割り当てやシステムコールを使用せず、例外を必要としない標準ライブラリをフリースタンディング指定していく一連の作業の一環です。フリースタンディング指定された機能は、OSの無い環境やそのサポートの受けられない環境においても使用可能であることが規定されます。

この提案では、<algorithm>, <numeric>, <random>にあるフリースタンディングとなりうるライブラリ機能の内、浮動小数点数ExecutionPolicyに依存しないものについてフリースタンディングとすることを目指すものです。

浮動小数点数型については、OSのカーネル環境において浮動小数点数を使用してしまうと、ユーザーモード浮動小数点数状態を変化させ壊してしまうため、フリースタンディング機能として指定することを避けています。このため、<random>の乱数生成周りの機能は整数の計算のみで実装可能なものだけをフリースタンディングとして提案しています。

おわり

この記事のMarkdownソース