[C++]WG21月次提案文書を眺める(2023年08月) - 地面を見下ろす少年の足蹴にされる私

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

文書の一覧

全部で44本あります。

もくじ

N4956 Concurrency TS2 PDTS

Concurrency TS v2の最新のワーキングドラフト

N4958 Working Draft, Programming Languages -- C++

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

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

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

N4960 Business Plan and Convener's Report: ISO/IEC JTC1/SC22/WG21 (C++)

ビジネスユーザ向けのC++およびWG21の現状報告書。

P0124R7 Linux-Kernel Memory Model

P0124R8 Linux-Kernel Memory Model

Linuxカーネルにおけるメモリモデルについて説明する文書。

この文書は、C/C++標準化委員会がメモリモデルに関する既存の慣行・実装としてLinuxカーネルにおけるものを参照する際に役立てることを意図したものです。

P0963R1 Structured binding declaration as a condition

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

構造化束縛宣言は変数宣言の変種であり、ほぼ変数宣言をかけるところなら同様に書くことができます。しかし、if文等の条件式はその例外であり、条件式に直接構造化束縛宣言を書くことはできません。

例えば次のような何かパースを行う関数があったとき

struct parse_window {
  char const *first;
  char const *last;
};

auto parse(std::contiguous_iterator auto begin, std::contiguous_iterator auto end) -> parse_window;

この関数の実行結果を取得し、パースの成否をチェックして継続のアクションを記述するには、例えば次のように書くことができます

if (auto [first, last] = parse(begin(), end()); first != last) {
  // interpret [first, last) into a value
}

この時、構造化束縛宣言を条件式として使用できるとより記述が単純化されます。

// 現在できない
if (auto [first, last] = parse(begin(), end())) {
  // interpret [first, last) into a value
}

この提案は、これをできるようにしようとするものです。

この提案による条件式における構造化束縛はオブジェクトの分解のみを担っており、条件判定に使用されるのは構文上からは隠蔽されている右辺の結果オブジェクトそのものです。従って、先ほどの例ではパース結果を表すparse_window型がパース成否を表現できるようにしておく必要があります。

struct parse_window {
  char const *first;
  char const *last;

  // パース成否テストを追加
  explicit operator bool() const noexcept { return first != last; }
};
// この提案の後、次の2つのif文は同じ意味になる

if (auto [first, last] = parse(begin(), end()); first != last) {
  // interpret [first, last) into a value
}

if (auto [first, last] = parse(begin(), end())) {
  // interpret [first, last) into a value
}

提案より、その他の例。

C++26のstd::to_charsの例

// この提案がない場合の書き方
if (auto result = std::to_chars(p, last, 42)) {
  auto [to, _] = result;
  auto s = std::string(p, to);  // 変換した数字文字列
  ...
} else {
  auto [_, ec] = result;
  std::cout << ec << '\n';
}
// この提案の場合の書き方
if (auto [to, ec] = std::to_chars(p, last, 42)) {
  auto s = std::string(p, to);  // 変換した数字文字列
  ...
} else {
  std::cout << ec << '\n';
}

C++26では、to_chars_resultbool変換演算子が追加されることにより、先ほどの例と同様にこの提案の恩恵を受けることができます。これはstd::from_charsも同様です。

if (int v; auto [ptr, ec] = std::from_chars(p, last, v)) {
  auto s = std::string(ptr, last);  // 変換に使用されなかった残りの部分の文字列
  ...
} else {
  std::cout << ec << '\n';
}

何か数学的な反復ソルバの例。

反復ソルバは主要な処理ステップをループを回して実行し、特定の条件が満たされるまでこのループを継続することで問題を解こうとします。

while (true) {
  auto [Ap, bp, x, y] = solve(...);

  // 最適解が得られていたらループを抜ける
  if (is_optimal(x))  // scan the x vector
  {
    break;
  }

  ...
}

この時、is_optimal(x)は線形かそれより悪いアルゴリズムが使用される(あるいは使用せざるを得ない)場合があります。一方で、ソルバステップ(solve())においては答えが最適かどうかを認識しその情報をキャッシュすることができる場合があります。その場合に戻り値からそれを直接取得できればコードが簡潔かつ効率的になります。

while (true) {
  // 最適解が得られていたらループを抜ける
  if (auto [Ap, bp, x, y] = solve(...))
  {
    break;
  }

  ...
}

これらの例に共通しているのは、ある処理の成否を保持しているのはその戻り値の一部または全部のコンポーネントであり、そのチェックの方法は型によって様々かつ自明ではないということです。この場合に、処理の成否をその処理の結果型そのものに焼き付けることでそのような煩雑なチェックコードを削減することができます。しかし、処理の結果として必要なのが結果オブジェクトのサブコンポーネントである場合、それを行おうとすると今度は余分な中間オブジェクトを導入しなければなりません(上記to_chars()の例のように)。

現状の構文では結果の分解と結果の成否チェックを同時に実行することができませんが、この提案による条件式における構造化束縛宣言を用いるとそれを同時に記述することができるようになります。またこれによって、何か処理結果を表す型を書く際に、その処理の成否のテスト方法を型に埋め込みユーザーはその方法について無知なままで結果を簡易に利用できるようにする(上記例のような)コーディングスタイルを促進します。

P1068R8 Vector API for random number generation

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

以前の記事を参照

このリビジョンでの変更は、std::spanベースのカスタマイズの許可、generate_randomイテレータ版を追加、design-considerationsセクションの拡充などです。

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

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

以前の記事を参照

このリビジョンでの変更は、EWGでの投票結果の追記、CWGレビューに伴う提案する文言の修正などです。

この提案は現在CWGのレビュー中です。

P2407R5 Freestanding Library: Partial Classes

一部の有用な標準ライブラリのクラス型をフリースタンディング処理系で使用可能とする提案。

以前の記事を参照

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

  • 名前空間スコープで定義されないものについて何がフリースタンディング指定されるのかを明確化
  • freestanding.itemの番号をふり直した
  • クラスメンバの全てがフリースタンディングとなる必要がないように要件を修正
  • 言葉として、関数の定義よりもエンティティを使用する
  • フリースタンディングのコメント文法を簡素化
  • フリースタンディングからホストへの移行に関するメモを調整
  • 文言の根拠となるメモなどの追加
  • std::variantの指定において、get_ifの代わりに説明専用のGET関数を使用する
  • this以外のオブジェクトにアクセスする際に、string_viewの説明用インターフェースではなくpublicインターフェースを使用する

などです。

この提案はLWGのレビューを通過し、次の全体会議で投票にかけられることが決まっています。

P2521R5 Contract support -- Record of SG21 consensus

C++26の契約プログラミングのMVP仕様に対する決定などを追跡するための文書。

以前の記事を参照

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

  • オーバーライドする関数は事前条件と事後条件を持てないが、オーバライドされる関数の事前条件と事後条件が適用される
  • 構文の選択に関する議論を短縮
  • 未解決問題のリストを更新

などです。

P2728R6 Unicode in the Library, Part 1: UTF Transcoding

標準ライブラリにユニコード文字列の相互変換サポートを追加する提案。

以前の記事を参照

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

  • null_sentinel_tの比較演算子の複雑な制約を単純化
  • ranges::project_viewを導入し、それによってcharN_viewsを実装するように変更
  • utfN_viewsを個別クラスではなくエイリアスに変更

などです。

P2746R3 Deprecate and Replace Fenv Rounding Modes

浮動小数点環境の丸めモード指定関数std::fesetround()を非推奨化して置き換える提案。

以前の記事を参照

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

  • Varna会議でのコメントを追記
  • Cの予約名との衝突を避けるため、cr_xxx()という関数名をcr::xxx()のようにcr名前空間に配置
  • flush-to-zero問題への対処/回避
  • cr::cast()の指定を修正
  • 実装が複雑になるのを回避するため、一部の関数でconstexprを避ける
  • cr::make()に、欠落していた丸モード引数を追加
  • constevalからconstexprに変更

などです。

P2795R3 Erroneous behaviour for uninitialized reads

未初期化変数の読み取りに関して、Erroneous Behaviourという振る舞いの規定を追加する提案。

以前の記事を参照

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

  • 振る舞いを修正する対象を“object with automatic storage duration”から“non-static local variable”に変更
    • 一時オブジェクトと関数引数を除外することを意図している
  • オプトアウトのための[[indeterminate]]属性を追加

などです。

[[indeterminate]]属性は変数の定義に対して指定するもので、これが指定されたローカル変数が初期化子を持たない場合、現在と同様に初期化されません。

void f(int);
void g() {
  int x [[indeterminate]], y;
  // xは未初期化、yは実装定義の値で初期化

  f(y); // OK、yの読み取りはerroneous behaviour
  f(x); // UB、xの読み取りはundefined behavior
}

P2821R4 span.at()

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

以前の記事を参照

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

この提案はLEWGのレビューを終えて、LWGに転送するためのLEWGでの投票待ちをしています。

P2833R1 Freestanding Library: inout expected span

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

以前の記事を参照

このリビジョンでの変更は、P2407R5の変更を適用、機能テストマクロをフリースタンディング指定、P2821とのコンフリクトについて追記、などです。

この提案はLEWGのレビューを終えて、LWGに転送するためのLEWGでの投票待ちをしています。どうやら、C++23へのDRとなるようです。

P2845R2 Formatting of std::filesystem::path

std::filesystem::pathstd::format()でフォーマット可能にする提案。

以前の記事を参照

このリビジョンでの変更は、デバッグ出力(?オプション)を提供するようにしたことです。

P2863R1 Review Annex D for C++26

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

以前の記事を参照

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

  • D.29(filesystem::u8path)に何もしないことの根拠を追記
  • Issueへのリンクを修正
  • ベースとなるドラフトを更新
  • 進捗状況について追記

などです。

個別の提案に分かれていないものとして、static constexprなクラスのメンバ変数に対する定義の再宣言の非推奨を解除することにEWGで合意がなされ、別の提案で進められることになりました。

P2864R1 Remove Deprecated Arithmetic Conversion on Enumerations From C++26

C++20の一貫比較仕様に伴って非推奨とされた、列挙値から算術型への暗黙変換を削除する提案。

以前の記事を参照

このリビジョンでの変更は、文言の修正や改善などです。

この提案の担当範囲については、EWGのレビューで削除することに合意が取れなかったため作業は中止されるようです。

P2865R2 Remove Deprecated Array Comparisons from C++26

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

以前の記事を参照

このリビジョンでの変更は、文言の修正やCWGレビューについて追記したことなどです。

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

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

以前の記事を参照

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

  • LEWGのレビューで明らかになった懸念について追記
  • ゾンビ名セクションを変更しないことを確認
  • Annex Cの文言を提供

などです。

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

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

以前の記事を参照

このリビジョンでの変更は、SG1やLEWGのレビューを受けての文言の修正などです。

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

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

以前の記事を参照

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

  • 異なるバージョン間でこの関数を使用するリスクについて追記
  • 古いコードからの移行について追記
  • ゾンビ名セクションを変更しないことを確認
  • Annex Cの文言を提供

などです。

P2871R1 Remove Deprecated Unicode Conversion Facets From C++26

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

以前の記事を参照

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

P2875R1 Undeprecate polymorphic_allocator::destroy For C++26

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

以前の記事を参照

このリビジョンでの変更は、ベースとなるWDの更新と文言の調整などです。

P2878R5 Reference checking

プログラマが明示的に関数の戻り値に関するライフタイム注釈を行えるようにする提案。

以前の記事を参照

このリビジョンでの変更は、do式とパターンマッチングにおけるダングリングの問題に対する解決策の修正、std::thread/std::jthreadに対する6種目のチェックの提案、などです。

P2885R1 Requirements for a Contracts syntax

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

以前の記事を参照

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

  • [func.mix]と[future.abbrev]に新しい要件を追加
  • "Accessibility" [basic.access]を"Teachability" [basic.teach]へ変更
  • [basic.lang]と[compat.cpp]の要件を単一の[basic.cpp]にマージ
  • [func.retval]を満たすために提案された2つのアプローチを、個別のより具体的な要件である[func.retval.predef]と[func.retval.userdef]の2つに分解
  • [func.pos.prepost]と[func.pos.assert]から、位置と名前探索に関する基本要件を抽出し[func.pos]にまとめた
  • 要件[func.pos.prepost]と[func.pos.assert]をより具体的にした
    • 事前/事後条件はパラメータ宣言の後に置く必要があり、アサーションはCのアサートがかける場所ならどこでも書けるようにする
  • [compat.back], [compat.impl]と[future.reuse]にそれぞれ根拠を追記
  • よりバランスの取れた表現となるように、[basic.aesthetic]と[basic.brief]を修正
  • [compat.c]の意図を明確化
  • [func.pos]において、属性構文の事前/事後条件の位置についての間違いを修正
  • [func.pos.prepost]に、配列ポインタを返す関数の曖昧な位置と名前探索の例を追加
  • [func.pos.prepost]と[func.mix]に違反する構文のアイデアとして、ピアノ構文についての言及を追記
  • [future.prim]に違反する属性構文の例を追加
  • [future.captures]を少し書き直して、仮引数名のキャプチャとそれ以外のもののキャプチャの違い、及び初期化キャプチャの許可とラムダのようなあらゆる種類のキャプチャ許可の違いを説明
  • P2935R0で説明されている属性構文の様々な拡張および設計の代替案について、文書全体に追記
  • 一次情報と二次情報について説明する段落を概要に追加
  • 将来の進化に対する要求の妥当性の違いについて言及
  • 要求の優先順位を決定し、それらを客観的/主観的なものとして分類するために、SG21で実施予定の電子投票に言及した方法論のセクションを書き直し、個々の要件の説明からそのような優先順位や分類を削除した

などです。

P2890R0 Contracts on lambdas

ラムダ式に対する契約条件指定ができるようにする提案。

現在進行中の契約プログラミングに関する議論においては、通常の関数に対して契約を行うことを主眼に議論されています。同様にラムダ式に対しても行えることが期待されますが、現在のところラムダでに対してどのように動作するのかはあまり考慮されていないようです。

この提案は、契約条件指定がラムダ式においても通常の関数と同じように動作し、なおかつキャプチャをどのように処理するかを提案するものです。

現在のContracts MVP仕様(P2388R4)においてはラムダ式における契約条件指定はその名前解決の問題から未解決の問題とされ、P2036にその解決を委ねMVPとしてはそれを待ってはいません。しかし、P2036はR3が既にC++23に採択されているため、その問題は解決済みでした。

この提案では、ラムダ式における契約条件式内の名前探索はP2036で提案されている後置戻り値型におけるそれと同様にすることを提案しています。すなわち、そのコンテキストで表れている名前はまずキャプチャ名が考慮されます。

int i = 0;
double j = 42.0;

...

auto counter = [j=i]() mutable [[pre: j >= 0]] {
//                                    ^
  return j++;
};

(ここでが契約構文として属性構文を使用していますが、これは仮のもので、この提案の内容は構文とは独立しています)

この例における事前条件式内のjは外側で宣言されているdouble型の変数jではなく、そのラムダにおいてキャプチャされている変数jint型)として扱われます。

この名前解決以外のところでは通常関数に対する契約条件指定と同じ振る舞いとなります。特に、現れる名前が常にODR-usedとなるという点も現在の契約仕様及び言語の他の部分([[assume]]など)と同様とすることを提案しています。

P2894R0 Constant evaluation of Contracts

定数式においても契約チェックを有効化する提案。

現在進行中の契約プログラミングに関する議論においては、実行時の関数に対して契約を行うことを主眼に議論されています。そのため、定数式において契約条件チェックするかどうか、それがどのようなセマンティクスを持つのか、などはあまり議論されておらず、方向性や一致した見解は確立されていません。

現在のC++コンパイル時プログラミングがかなり強力かつ一般的なものとなっており、実行時に動作するコードの多くはコンパイル時にも動作し、契約プログラミングに関しても同様であることが自然と期待されます。この提案はそのために現在の契約プログラミング仕様の下で定数式における契約条件チェック周りのセマンティクスについて議論し提案するものです。

現在の契約プログラミング仕様は、P2877R0の採択によってビルドモードの概念が削除され、契約アノテーションのセマンティクスは実装定義となりコンパイル時には認識できないようにされています。そのため、C++のコードの一部がill-formedであるかを契約プログラミングのセマンティクス(実行時にチェックされるかどうか)に依存させることはできなくなっています。したがって、定数評価中はP2877R0で示されている3つのセマンティクス(ignore, observe, enforce)のいずれかによって実行されているかを選択することができません。

契約アノテーションの定数評価時のセマンティクスについて指定が必要なのは次のどちらかの場合についてです

  1. 契約条件式はコア定数式ではない
  2. 契約条件式はコア定数式だが、falseに評価された(契約が破られた)

この2つの場合についてそれぞれ、セマンティクスを指定する必要があります。

1つ目のケースについてはまず、P2448R2がC++23に適用されたため、constexpr/consteval関数がコンパイル時に呼び出されなければそこに指定されている契約条件について何かをする必要はありません。

int pred(); // constexprではない述語

constexpr int f() [[ pre: pred() ]]; // OK; コンパイル時に呼ばれない
consteval int g() [[ pre: pred() ]]; // OK: 使用されない

int main() {
  return f(); // f()はコンパイル時に呼び出されない
}

考慮すべきは、この場合にf()/g()が定数式で呼ばれ、契約条件式を評価しようとしてpred()の呼び出しで何が起こるかです。

int pred(); // constexprではない述語

int f() constexpr [[ pre: pred() ]]; // OK; コンパイル時に呼ばれない

int main() {
  std::array<int, f()> a; // f()が定数式で呼び出される
  
  ...
}

この場合の振る舞いには次の4つのオプションが考えられます

  1. ill-formed
  2. ill-formed、ただし診断不要
  3. 契約条件式は無視される(定数評価中のみ)
  4. 契約条件式は無視するが、警告を発することを推奨する

さらに、このうちどのオプションを選択するかは実装定義とすることもできます。

2つ目のケースの場合については、例えば次のプログラムはどのように動作すべきでしょうか?

int f(int i) constexpr [[ pre: i > 0 ]];

int main() {
  std::array<int, f(0)> a; // 契約条件を満たさない定数式中の呼び出し
  ...
}

実行時の場合は契約条件がtrueに評価されない場合とは多岐にわたります

  • falseに評価された
  • 例外をスローした
  • longjmpを呼び出した
  • プログラムを終了させた
  • 未定義動作に陥った

実行時において契約違反となるのは最初の2つの場合のみです。また、そのセマンティクスもignore, observe, enforceの3つのいずれかが実装定義で選択(指定)され、それによって契約条件は無視されるか(ignore)、破られて違反ハンドラが呼ばれた後どうなるか(observe, enforce)、が変化します。

定数評価中はこの点が大きく異なり、定数評価中は契約アノテーションのセマンティクスを選択できず(どれが選択されているかはわからず)observe, enforceを区別する意味がありません。無視されずにtrueと評価されなかった場合はfalseに評価される以外に選択肢がありません。定数評価中は例外を投げたりlongjmpを呼び出したりできず、それが行われる場合はコア定数式ではないためそのセマンティクスの規定はケース1の領分となります。

そのため、このケース2のセマンティクスの指定は、契約条件式がコア定数式でありfalseに評価された場合についてのみ考慮するだけですみます。ケース1と同様に、ここでも4つの選択肢があります

  1. ill-formed
  2. ill-formed、ただし診断不要
  3. 契約条件式は無視される(定数評価中のみ)
  4. 契約条件式は無視するが、警告を発することを推奨する

ここでも、このうちどのオプションを選択するかは実装定義とすることもできます。

これらのことと設計原則(正しさ、柔軟さ、教えやすさ、コンパイル時間)を前提に、提案では3つのオプションを提示しています

  • オプションA : どちらもill-formedとする
    • 正しさを最も重視
    • チェックできない条件式は許可せず(ケース1)、契約違反を実行時よりもコンパイル時に補足できた方が望ましい(ケース2)
    • 教えやすさにも優れているが、柔軟性とコンパイル時間を犠牲にしている
    • 全ての契約条件が常にチェックされなければならず、それによるコンパイル時間の増大が許容できないものになる可能性がある
  • オプションB : どちらも契約条件式を無視する
    • コンパイル時間を最も重視
    • コンパイラは全ての契約アノテーションを定数評価中に無視し、契約条件は実行時にのみ評価される
    • 教えやすさにも優れているが、柔軟性と正しさを犠牲にしている
  • オプションC : どちらの場合のセマンティクスも実装定義とする
    • 柔軟性を最も重視
    • 正しさとコンパイル時間の間の妥協点を探るもので、コンパイラ(もしくはユーザー)がどちらを優先するかを選択できる
    • 教えやすさを犠牲にしており、ルールを複雑化するとともに契約の一部分を標準からコンパイラベンダの手に委ねてしまっている
    • コンパイラはその引数か独自のビルドモードによって、契約条件をコンパイル時にチェックするかしないか(正しさを優先するかコンパイル時間を優先するか)を選択できるようになる
      • また、コンパイル時と実行時でその指定を共通させることもさせないことも選択できるようになる

契約プログラミングの場合は正しさが最も重要な設計目標であるため、オプションAが最も望ましい選択肢です。しかし、それによってコンパイル時間の増大を許容できなくなったユーザーがマクロにラップしてその有効性をスイッチするようなことをし始めると、契約機能の採用に影響を及ぼす可能性があります。なぜなら、オプションAでは契約条件がコンパイル時に評価されないが実行時には評価される、ようなことを許可しないためです。

オプションBも正しさの観点から適切ではありません。コンパイル時間のために正しさを犠牲にし、結果得られるプログラムが未定義の振る舞いをするようになってしまったら意味がありません。標準は少なくともこのようなケースを診断できるべきです。

したがって、最も推奨されるのはオプションCとなります。コンパイラ引数等のスイッチが増えるのはやや残念な点ですが、これによって契約プログラミングを使用する全てのユーザーに契約を有効に使用するための選択肢を与えることができます。例えば、このオプションでは契約条件がコンパイル時に評価されないが実行時には評価される、といったことが許容されます。

P2896R0 Outstanding design questions for the Contracts MVP

契約プログラミングに関する未解決の問題について収集し、解決を促すための提案。

C++における現在の契約プログラミングに関する議論は、MVPと呼ばれる最小仕様を確立し、それをC++26に導入することを目指しています。そのタスクの残りの主要な論点は契約構文に関するものですが、それ以外にも小さいながらも解決すべき問題がいくつか残されています。

この提案は、それらの未解決の問題(設計上の疑問点)を収集してリスト化し、MVP仕様が確立される前に何らかの回答(SG21における投票や個別の提案の採択など)を求めるものです。

  1. 異なる翻訳単位の最初の宣言における契約について
    • 未解決、提案が必要
    • 現在のMVPでは、fが異なる翻訳単位で宣言されている場合、その契約は同一(identical)でなけれならない(そうでない場合診断不用のill-formed)とされているが、同一(identical)の意味が定義されていない。この定義が必要
    • 選択肢
      1. 同一(identical)の意味を定義し、これが実装可能であることを確認する
      2. 異なる翻訳単位の同じ関数の2つの宣言について、両方に契約がなされている場合をill-formed(診断不用)として問題を回避する。ただしこれは実用的ではない
  2. オーバーライドする関数とされる関数の契約について
    • 未解決、提案が必要
    • 現在のMVPでは、オーバーライドする関数はされる関数の契約を継承し、追加の契約を行えない。これについて異論があり、どうするかを選択する必要がある。
    • 選択肢
      1. なにもしない(現在のMVPのまま)
      2. MVPの制限を強め、オーバーライドする関数もされる関数も契約を行えないようにする
      3. 継承された契約をオーバーライドする機能などの仮想関数に対するより柔軟なソリューションを考案し、MVPを緩和する
  3. ラムダ式に対する契約と暗黙キャプチャについて
    • 未解決、P2890R0とP2834R1で提案済
    • 契約機能はラムダ式においても機能しなければならない。その際、ラムダの本体で使用されていないが契約指定で使用されている名前はキャプチャされるかどうか(契約に表れている名前がODR-usedであるかどうか)が未解決
    • 選択肢
      1. ラムダにおける契約は他の所と同じルールに従う。すなわち、契約条件式でのみ使用されている名前はキャプチャされる
        • P2890R0が提案している
      2. ill-formedとする。ラムダにおける契約条件式は、他の方法でキャプチャされない名前をキャプチャできない
        • P2834R1が提案している
      3. ラムダ式における契約機能を無効にする
  4. コルーチンにおける契約
    • 未解決、P2957R0で提案済
    • コルーチンに対する契約は通常の関数と同様に動作するのかが未解決
    • 選択肢
      1. コルーチンで事前・事後条件とアサーションを許可し、事前条件と事後条件のセマンティクスを指定する
        • P2957R0が提案し、セマンティクスについても提供している
      2. コルーチンではアサーションのみ許可する
      3. コルーチンでは契約機能は無効とする
  5. 定数式における契約について
    • 未解決、P2894R0で提案済
    • 定数評価中に契約条件は評価されるのか、どういうセマンティクスを持つのかが未解決。特に、契約条件式はコア定数式ではない場合と、契約条件式はコア定数式だがfalseに評価された場合にどうなるのかが問題。
    • 選択肢(2つの場合のどちらについても)
      1. ill-formed
      2. ill-formed、ただし診断不要
      3. 契約条件式は無視される(定数評価中のみ)
      4. 契約条件式は無視するが、警告を発することを推奨する
  6. トリビアルな特殊メンバ関数に対する契約について
    • 未解決、P2834R1で提案済
    • トリビアルな関数に契約がなされている場合、そのトリビアル性に影響するかどうかが未解決
    • 選択肢
      1. 契約が指定されていてもトリビアルのまま
      2. 契約が指定されていてもトリビアルのままだが、その結果として事前・事後条件がチェックされない可能性がある
        • P2834R1が提案している

この提案は、これらの問題の解決を2024年春の東京における全体会議までに解決するべきとしています。

P2905R2 Runtime format strings

std::make_format_args()が左辺値でフォーマット対象の値を受け取るようにする提案。

以前の記事を参照

このリビジョンでの変更は、R1に対するLEWGの投票結果を追記したことです。

P2909R0 Dude, where's my char?

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

std::format()ではcharの値をdxオプションによって整数値として出力することができます。ただ、この出力はintに暗黙変換して出力する形になっており、charの符号が実装定義であることを考慮していませんでした。

import std;

int main() {
  for (char c : std::string("🤷")) {
    std::print("\\x{:02x}", c);
  }
}

リテラルエンコーディングUTF-8だとすると、この出力はcharが符号付きか否かによって次のどちらかになります

\xf0\x9f\xa4\xb7
\x-10\x-61\x-5c\x-49

例えば、ARM環境は多くの場合charが符号付きとなります。

std::format()の暗黙的な設計意図として、同じ整数型と同じIEEE754浮動小数点数型についてプラットフォーム間で一貫した出力をする、というものがあります。しかし、現在の仕様だとcharがそのプロパティを破っています。

また、charstd::format()で出力するときは、元のテキストの部分テキストとして出力するか、そのビットパターンを出力するために使用されます。後者の場合、それは符号なし整数を通常意図しているはずで、プラットフォームによっては符号付き整数値として出力されることは意図しないはずであり、10進以外で出力している場合は特にそうでしょう。

この問題は{fmt}ライブラリにおいて報告され、既に修正済みです。この提案は、この修正をstd::format()にも適用しようとするものです。

提案では、charの値をb, B, d, o, x, Xオプションによって出力する際は対応する符号なし整数型(つまり、unsigned char)にキャストしてからフォーマットする、のように指定することでこの問題の解決を図っています。

この修正は実行時の振る舞いを静かに変化させる破壊的変更となりますが、単一のchar値をb, B, d, o, x, Xオプションによって出力する場合、かつcharが符号付き整数型であるプラットフォームにおいてのみ影響があります。変更が{fmt}ライブラリに適用され出荷されて以降この修正が問題となった報告はなく、std::format()の利用率はそれよりさらに低いため破壊的変更の影響はかなり小さいと考えられます。

LEWGにおける投票では、この修正をC++23に対するDRとすることに合意がとれています。

P2933R0 std::simd overloads for <bit> header

<bit>にあるビット演算を行う関数について、std::simd向けのオーバーロードを追加する提案。

std::simdはデータ並列型として、通常のスカラ型とほぼ同様に使用してハードウェアのSIMD演算を活用できるようにすることを意図したクラス型です。そのため、演算子オーバーロードや関数オーバーロードによって通常の四則演算や数学関数などを使用可能なようになっています。

この提案は、現在そこに欠けている<bit>のビット演算系関数のオーバーロードを追加しようとするものです。

この提案の対象は、C++23時点で<bit>ヘッダにあるもののうちstd::endianstd::bit_castを除いた残りの関数です。追加されるオーバーロードの制約は元の関数のものをstd::simdの要素型に対して要求し、効果はstd::simdの値の要素ごとに適用され、結果は入力と同じstd::simd<T, ABI>型の値として返されます。

ただし、countl_zero()bit_width()などのようにビットの状態をクエリする系の関数の戻り値型は通常のものはintとなっていますが、std::simdで同じようにすると結果の値のビット幅とストレージサイズが変化しパフォーマンスに影響を与える可能性があるため、これらの関数については入力の要素型と同じ幅の符号付き整数型を要素型とするstd::simd値を返すようにされています。

P2935R0 An Attribute-Like Syntax for Contracts

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

C++における現在の契約プログラミングに関する議論は、MVPと呼ばれる最小仕様を確立し、それをC++26に導入することを目指しています。意味論面に関しては小さい問題がいくつか残されていますがそれは本質的な問題ではなく、残った大きな決定事項は契約構文に関するものです。

現在の契約プログラミングのための構文としては、C++20で一旦採用された際の属性構文の他、ラムダ式likeな構文、専用の区切り記号を利用するものなどの候補があります。この提案は、そのうちC++20で採用されたものとほぼ同様の属性構文によるものを提案しています。

提案されている構文の文法は次のようなものです

contract-checking-annotation :
  precondition-specifier
  postcondition-specifier
  assertion-specifier

precondition-specifier :
  [ [ pre : conditional-expression ] ]
postcondition-specifier :
  [ [ post postcondition-return-value-specifier(opt) : conditional-expression ] ]
postcondition-return-value-specifier :
  identifier
  ( identifier )
assertion-specifier :
  [ [ assert : conditional-expression ] ]

attribute-specifier :
  [ [ attribute-using-prefix(opt) attribute-list ] ]
  alignment-specifier
  precondition-specifier
  postcondition-specifier

assertion-statement :
  assertion-specifier ;

よりまとめると、契約アノテーションの構文は次のような形式をしています

contract-checking-annotation
  [ [ contract-kind metadata-sequence : evaluable-code ] ]

contract-kindにはpre post assertのいずれかが指定でき、metadata-sequenceには事後条件における戻り値のキャプチャのみが置けます。evaluable-codeは文脈に応じてboolに変換される通常のC++の式、すなわちconditional-expressionを置くことができます。

この構文の利用例

int f(const int x, int y)
  [[ pre : y > 0 ]]
  [[ post : fstate() == x ]]  // 事後条件から参照される仮引数名はconstでなければならない
  [[ post r : r > x ]]        // postの後に変数名を指定することで戻り値を参照する
  [[ post (r) : r > x ]]      // 戻り値名は()で括っても良い
{
  [[ assert : x > 0 ]]; // Assertions form a complete statement.
  return x;
}

事前条件と事後条件は関数ではなく関数の型に関連する属性が配置されるのと同じ場所に配置され、noexceptの後かつ後置戻り値型の前に配置されます。

struct S1 {
  auto f() const & noexcept [[ pre : true ]] -> int;
  
  virtual void g() [[ pre : true ]] final = 0;

  template <typename T>
  void h() [[ pre : true ]] requires true;
};

ラムダ式の場合も同様になります。

auto w = []         [[pre: true]]        { return 6; };
auto x = [] (int x) [[pre: true]]        { return 7; };
auto y = [] (int x) [[pre: true]] -> int { return 8; };

事前条件と事後条件の数や順序に制限はなく、好きな順番で好きな数追加できます。

std::pair<double,double> clamp2d(double x, double y,
                                 const double minx, const double maxx,
                                 const double miny, const double maxy)
  // Check the x-dimension range.
  [[ pre : minx <= maxx ]]
  [[ post r : r.first >= minx && r.second <= maxx ]]
  // Check the y-dimension range.
  [[ pre : miny <= maxy ]]
  [[ post r : r.second >= miny && r.second <= maxy ]];

assertion-statementはそれ単体で1つの文を形成し、ブロックスコープの内側でのみ使用可能となります。ただし、assertion-specifierそのものにはそうした制限はなく、将来的にvoid型の式として式に付随させることを許可することを妨げません。

struct S2 {
  int d_x;
  S2(int x)
    : d-x( [[ assert : x > 0 ]], x ) // error、ただし将来的に許可する事は可能
  {}
};

P2944R1 Comparisons for reference_wrapper

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

以前の記事を参照

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

P2951R2 Shadowing is good for safety

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

以前の記事を参照

このリビジョンでの変更は、5つ目の提案を追加したことです。

5つ目の提案は、2つ目の提案を範囲forで特化させたもので、イテレート対象の範囲をその内部で暗黙的にconstシャドウィングを行うものです。

#include <string>
#include <vector>
#include <optional>

using namespace std;

int main() {
  vector<string> vs{"1", "2", "3"};

  [[checked]]
  for (auto &s : vs) {
    // 暗黙的にconstシャドウィングされる
    //const vector<string>& vs = vs;
  }
  
  // 代替構文
  cfor (auto &s : vs) {
    //const vector<string>& vs = vs;
  }

  return 0;
}

この例のvsのようにイテレート対象の式が単純な変数名であればこの提案の実装には問題がありませんが、ここにはより複雑な式が来る可能性があります。その時に同じことをしようとすると、より複雑な組み合わせのパターンマッチングが必要になります。そのため、実装の選択肢は3段階のチェックに分かれます。

  1. vsのように単純な式(変数名)のみ自動シャドウィングする
  2. vs.member.function().memberのような複雑な式が、その範囲forのスコープ内で非constとして使用されないようにする
  3. 式の組み合わせがその範囲forのスコープ内で非constとして使用されないようにする

2と3の例

// 2の例
[[checked]]
for (auto &s : vs.member.function().member) {
  // 非constで`vs.member.function().member`を使用できない
  vs.member.function().member.non_const_method(); // error
}

// 3の例
cfor (auto &s : vs.member.function().member) {
  auto first2 = vs.member;
  auto remainder = first2.function().member;
  
  // 非constで`vs.member.function().memberと同等のものを使用できない
  remainder.non_const_method(); // error
}

P2952R0 auto& operator=(X&&) = default

defaultな特殊メンバ関数の戻り値型をauto&で宣言できるようにする提案。

現在のC++では、= defaultは特定のシグネチャと戻り値型を持つ関数にのみ使用できます。そのような関数の戻り値型は変更することができ、そのコンテキストから特定可能な型ですが、現在の規定では<=>を除いて戻り値型に対してautoを使用できません。

これにより、例えば代入演算子の実装において冗長な繰り返しが避けられません。

struct ForwardDiffView {
  ...
  
  ForwardDiffView(ForwardDiffView&&) = default;
  ForwardDiffView(const ForwardDiffView&) = default;
  ForwardDiffView& operator=(ForwardDiffView&&) = default;
  ForwardDiffView& operator=(const ForwardDiffView&) = default;
  
  ...
};

また、比較演算子<=>とこの点において一貫していません。

auto operator<=>(const MyClass& rhs) const = default; // well-formed
auto operator==(const MyClass& rhs) const = default;  // ill-formed、boolでなければならない
auto operator<(const MyClass& rhs) const = default;   // ill-formed、boolでなければならない

これらのことはまた、defaultではない場合とも一貫していません。

auto& operator=(const MyClass& rhs) { i = rhs.i; return *this; } // well-formed
auto& operator=(const MyClass& rhs) = default; // ill-formed、'MyClass&'でなければならない

auto operator==(const MyClass& rhs) const { return i == rhs.i; } // well-formed
auto operator==(const MyClass& rhs) const = default; // ill-formed、'bool'でなければならない

この提案は、これらの例のように現在行えない特殊メンバ関数の戻り値型におけるautoの使用を許可しようとするものです。

この場合のautoの示す型名は、特定の許可される型のセットを定義してそこから選択するのではなく、仮想のretrun文を返す関数としての通常の戻り値型推論によって推定されます。

auto戻り値型を持つdefaultな関数宣言は、その関数の種別によって次のような値を返す架空のreturn文から推論された型を持ちます

  • bool型のprvalue : operator==operator!=operator<operator>operator<=operator>=
  • 共通の比較カテゴリ型Q型のprvalue : operator<=>
  • Cの左辺値 : クラス型/共用体Coperator=

その後、このように取得された戻り値型が標準で許可されている戻り値型と一致するかを調べ、一致するならwell-defined、一致しないならill-formedとなります。

コピー代入演算子の例

struct MyClass {
  auto& operator=(const MyClass&) = default;          // Proposed OK: deduces MyClass&
  decltype(auto) operator=(const MyClass&) = default; // Proposed OK: deduces MyClass&
  auto&& operator=(const MyClass&) = default;         // Proposed OK: deduces MyClass&
  const auto& operator=(const MyClass&) = default;    // Still ill-formed: deduces const MyClass&
  auto operator=(const MyClass&) = default;           // Still ill-formed: deduces MyClass
  auto* operator=(const MyClass&) = default;          // Still ill-formed: deduction fails
};

等値比較演算子の例

struct MyClass {
  auto operator==(const MyClass&) const = default;           // Proposed OK: deduces bool
  decltype(auto) operator==(const MyClass&) const = default; // Proposed OK: deduces bool
  auto&& operator==(const MyClass&) const = default;         // Still ill-formed: deduces bool&&
  auto& operator==(const MyClass&) const = default;          // Still ill-formed: deduction fails
};

また、提案ではこの変更に伴って発見された既存の特殊メンバ関数(特に== <=>)の宣言の微妙な差異によるバグやベンダ間の差異について明確となるようにを修正することも提案しています。

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

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

明示的にdefaultで宣言される代入演算子は、その参照修飾(CV修飾は禁止されている)や戻り値型等の微妙な違いによる宣言が許可されており、それによって暗黙のdefault宣言で導入されるものとは異なったシグネチャdefault代入演算子を宣言可能になっています。

その中でも最も意味がないと思われるのが、右辺値参照修飾されたdefault代入演算子の宣言です。

struct S {
  // どちらも、今日可能なdefault宣言
  S& operator=(const S&) && = default;
  S& operator=(S&&) && = default;
};

これには次のような問題があります

  • このような無意味な宣言の可能性は、C++の理解を困難にさせる
  • このような宣言を許可するための標準の文言は、禁止する場合に比べて複雑になる

この提案では、この右辺値参照修飾されたdefault代入演算子の宣言を禁止することを提案しています。

この提案による恩恵はまた、明示的オブジェクトパラメータとP2952(1つ前の節)を採用した場合のコーナーケースを潰すのにも役に立ちます

struct C {
  auto&& operator=(this C&& self, const C&) { return self; }
    // 現在: OK, 戻り値型はC&&が推論される
    // P2952採択後: OK, 戻り値型はC&&が推論される(変わらない)
    // この提案: OK, 戻り値型はC&&が推論される(変わらない)

  auto&& operator=(this C&& self, const C&) = default;
    // 現在: Ill-formed, 戻り値型にプレースホルダを使用できない
    // P2952採択後: OK, 戻り値型はC&が推論される
    // この提案: 削除される、オブジェクトパラメータはC&ではない
};

この提案がない場合、この2つの同じ意味と思われる宣言はP2952の採択後に異なる型を推論するようになり、一見するとこの推論は矛盾しているように思えます。P2952とこの提案を同時に採択することによってこのコーナーケースを軽減することができます。

なお、この提案では該当する代入演算子をill-formedとするのではなく、既存の振る舞いに従って暗黙deleteとすることを提案しています。

struct S {
  // この提案の後、どちらもdeleteされる
  S& operator=(const S&) && = default;
  S& operator=(S&&) && = default;
};

P2954R0 Contracts and virtual functions for the Contracts MVP

現在のC++契約プログラミング仕様に対して、仮想関数に関する契約についてを規定する提案。

仮想関数のオーバーライドに伴う契約の指定に関しては、EiffelにおいてAssertion Redeclaration ruleと呼ばれる規則が確立されています。それは

  1. 事前条件は、継承元と同じかより弱い(緩い)ものである必要がある
  2. 事後条件は、継承元と同じかより強い(厳しい)ものである必要がある

というものです。

C++20の契約仕様においてもこれをベースとして、事前条件と事後条件はオーバーライド元と先で同一となることを指定していました。

  • オーバーライドする関数に契約を指定する場合、オーバーライドされる関数の契約指定と同じものを指定する
  • 関数が複数の関数をオーバーライドする場合、オーバーライドされる関数は全て同じ契約指定を持つ必要がある

現在C++26へ向けて進行中の契約仕様(MVP)においては

  • オーバーライドする関数には契約指定を行うことはできず、オーバーライドされる関数の契約指定を継承する
  • (関数が複数の関数をオーバーライドする場合は同一)

となっており、少し厳しくなっています。

この点に関してはさまざまな意見があるようで、特に、契約指定の同一性(あるいは契約の強弱や広さの定義)をどう判断するのかや、複数の関数をオーバーライドする場合に基底の関数全てに同じ契約指定を要求することが正しいのかについて合意がとれてはいないようです。

この提案は、現在のMVP仕様をC++26に間に合わせるためにも、この点について次のようにすることを提案しています

  • 関数をオーバーライドする仮想関数には契約指定を行えず、オーバーライドされる関数の契約指定を継承する
  • 関数が複数の関数をオーバーライドする場合、オーバーライドされる関数は契約指定を持ってはならない

これは、適切であり将来の互換性に影響がない部分(オーバーライド後関数は元の関数と同一の契約を持つこと)を許可し、現在異論があり意見の一致を見ていない部分(契約条件の同一性の定義、複数の基底関数への契約の強制について)を不許可(ill-formed、要診断)にしようとするものです。これによって、C++26契約仕様としては契約を持つ仮想関数のオーバーライドを可能にしつつ、それはAssertion Redeclaration ruleに従ったものになります(契約条件は基底関数のコンテキストにおいて同一)。現在異論がある部分については、実装経験などを見ながら将来的に決定することを目指します。

例えば、オーバーライドする関数は基底クラスの関数とは全く異なるコンテキスト及びスコープで定義される可能性があるため、トークンの同一性は契約条件の同一性を意味しません。ODRの同一性に関しは実装経験が乏しく(一応、GCCの契約実装がこれをおこなっているらしい)、それがユーザーにとってどのような影響をもたらすのかは不透明です。これを診断不要のill-formedにしてしまうと、契約仕様の理解に混乱をもたらします。そのため、明確にill-formedとしておくことで問題を分離し、単純化した振る舞いをC++26に間に合わせることができます。

GCCの契約実装はODRベースで契約指定の同一性を判断するようですが、これは複数の関数をオーバーライドしている場合には行われないようで、その点に関しても合意や実装経験はありません。そのため、これもill-formedとしておくことで振る舞いを単純化し、将来のために設計スペースを空けておくことができます。

P2955R0 Safer Range Access

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

std::vectorを始めとするコンテナ(std::dequestd::stringstd::spanなどなど)は要素アクセスが安全であるとはいえません。.at()以外のアクセス方法は範囲外アクセスをチェックしておらず、.at()の利用率は低いため要素アクセスの大部分は範囲外参照チェックなしで行われています。

また、.at()も戻り値が要素への直接の参照であるため、参照無効化・ダングリング参照の危険性を孕んでいます。

この提案は、値セマンティクスに基づく関数を提供することで、範囲外アクセスやダングリング参照によるエラーを低減させることを目指すものです。

現在のC++の要素アクセスに共通する課題として、次のようなものがあります

  1. C++の添字演算子は参照セマンティクスを持つ傾向にある
    • 値の取得と設定それぞれに特化した専用の演算子がないためと考えられる
  2. C++の添字演算子はインデックスの一部ではない追加の引数をサポートしない
    • 例えばstd::source_location
  3. std::optional, std::variant, std::expectedは参照をサポートしない

この課題を回避するために、この提案では演算子やプロクシオブジェクトを使用する代わりにメンバ関数を使用しています。

まず追加するのは、与えられたインデックスが有効化どうかを判断する.test()関数です。

[[nodiscard]]
[[safe]]
constexpr bool test( std::vector<T>::size_type pos ) const;

この関数は安全に実装できます([[safe]]は新規属性ではなく、この提案内において安全/安全ではないことを示すマーカーです)。

次に追加する2つの関数は安全なものではありませんが、この提案のメインである安全な関数群を提供するためのベースとなるものです

[[nodiscard]]
[[unsafe(reason=["range", "dangling_reference", "reference_invalidation"])]]
constexpr reference get_reference( size_type pos ) noexcept;

[[nodiscard]]
[[unsafe(reason=["range", "dangling_reference", "reference_invalidation"])]]
constexpr const_reference get_reference( size_type pos ) const noexcept;

ここでの[[unsafe]][[safe]]同様に説明のためのマーカーです。これらの関数は指定されたインデックスに対応する要素の参照を取得するものです。

これらをベースとした安全な関数群は、次のような設計理念によって分類されます

  1. [], at(), front(), back()等範囲アクセス関数に対しては、ゲッター(値を受け取る)とセッター(値を返す)とに分離する
  2. 分離されたゲッターとセッター関数ごとに、エラーを処理する方法に応じたバリアントが追加される
    • 例外送出
    • デフォルト値を返す
    • std::optionalを返す
    • 終了する
    • 何もしない(つまり、無効)
    • 追加する

front()/back()に対応する関数は例えば

取得/設定 エラー処理の方法 front() back()
get get_value get_front_value get_back_value
get get_optional get_front_optional get_back_optional
get get_or_terminate get_front_or_terminate get_back_or_terminate
set set_value set_front_value set_back_value
set set_or_terminate set_front_or_terminate set_back_or_terminate
set set_and_crop set_front_and_crop set_back_and_crop
set set_and_grow set_front_and_grow set_back_and_grow

ゲッター関数は何も受け取らず値を返し、セッター関数は値を受け取り何も返しません。

要素を引き当てその参照に対して何かをしたい(値の更新やメンバ呼び出しなど)場合のために、ゲッター/セッターの分離はtransform/visitに置き換えられ、対応する関数が追加されます

取得/設定 エラー処理の方法 front() back()
transform transform_value transform_front_value transform_back_value
transform transform_optional transform_front_optional transform_back_optional
transform transform_or_terminate transform_front_or_terminate transform_back_or_terminate
visit visit_value visit_front_value visit_back_value
visit visit_or_terminate visit_front_or_terminate visit_back_or_terminate
visit visit_and_crop visit_front_and_crop visit_back_and_crop
visit visit_and_grow visit_front_and_grow visit_back_and_grow

transform系関数はインデックス値とそれによって引き当てた要素に対して何かする関数を受け取り、その関数の戻り値を(非参照で)返します。visit系関数は新規追加する値と追加後の要素に対して何かする関数を受け取り、何も返しません。

これらの関数は多岐に渡りますが、実際にはコンテナ種別によってやることは変わらないため(最初に追加した2種類の関数をベースとするため)、実際にはメンバ関数ではなくフリー関数として追加することが望ましいです。それによって、std::vector以外のコンテナでも同じように利用できるようになります。

提案には、このように追加される関数のstd::vectorに追加する場合の宣言例と実装例があります(長いのでコピペしません)。

この提案のメリットとしては

  • 範囲アクセス関数のエラーを減らす
  • 実行時のダングリング参照の発生を減らす
  • 実行時の参照無効化エラーの発生を減らす
  • プログラマが例外を扱う際に不必要な動的確保を減らす

などがあります。

P2956R0 Add saturating library support to std::simd

P0543で提案されている整数型の飽和演算関数群にstd::simdオーバーロードを追加する提案。

std::simdはデータ並列型として、通常のスカラ型とほぼ同様に使用してハードウェアのSIMD演算を活用できるようにすることを意図したクラス型です。そのため、演算子オーバーロードや関数オーバーロードによって通常の四則演算や数学関数などを使用可能なようになっています。

P0543は整数型向けの飽和演算のためのライブラリ関数を提案するもので、C++26に採用される予定です(まだ承認前)。そこでは整数型のためのオーバーロードのみを提供しています。

この提案は、そこにstd::simd向けのオーバーロードを追加しようとするものです。

提案されているオーバーロードでは、対象となる飽和演算は整数型を要素型とするstd::simdの要素ごとに実行されます。

P2957R0 Contracts and coroutines

コルーチンに対して契約を有効化した場合に、各種の契約がどのように動作するのかについての提案。

コルーチンは、その関数定義内にコルーチン関連の3種類のキーワード(co_return, co_yield, co_await)のいずれかが含まれている場合にその関数はコルーチンとなります。そのため、宣言だけからあるいは呼び出しだけからその関数がコルーチンであるかどうかを知ることはできません。

// コルーチンかもしれないし普通の関数かもしれない
awaitable<int> session(int id);

// 普通の関数
awaitable<int> default_session() { 
  awaitable<int> s = session(0); // コルーチン呼び出しかもしれないし普通の関数呼び出しかもしれない
  return s;
}

default_session()は定義があるため普通の関数であることが分かりますが、session()は宣言のみであるためどちらかは分かりません。

コルーチンを呼び出したとき、呼び出し側から見るとその呼び出しはコルーチンを起動しその戻り値オブジェクトを返すためのファクトリ関数(ramp関数と呼ぶらしい)を呼び出しいています。そして、このramp関数は契約を持つことができます(例えば上記例ではsession()idが正であることなど)。

通常の関数とコルーチンの大きな違いはこの後、呼び出し側に戻った時に関数本体の実行が完了し終了しているかどうかです。通常の関数は確実に実行は終了しており、引数も含めた関数内部の状態は開放済みです。一方で、コルーチンではまだ終了しておらず単に中断しているだけの可能性があり、その場合関数内部状態はコルーチンステート内部に保存されています。そのため、コルーチンの観測可能な副作用はその最初の呼び出しがreturnした後、任意のタイミングで変化し得ます。

したがって、ramp関数がreturnした後その戻り値オブジェクトのいくつかの状態について何か期待を持っていたとしても、それは通常の関数に対するものよりも制限されるでしょう。

このように、コルーチンは通常の関数を一般化したもので、契約注釈が通常の関数と同様に動作するのかどうか(つまりその意味論について)はいくつかの疑問があります。この提案は、3種類の契約注釈がコルーチンにおいてどのように振舞うべきかを提案するものです。

とはいえ、アサーションに関してはあまり疑問は無いでしょう。アサーションが配置されるのはコルーチンの本体であり、それはコルーチンの実装詳細です。アサーションは現在のassertマクロと同様に、実行がそこに到達したときに評価されることが期待されます。

残った2つ事前条件と事後条件はそれぞれ、関数が呼び出された時に期待すること、関数が正常にreturnしたときに関数が保証すること、を表しています。これらがチェックされるべき非コルーチン通常関数における適切な場所は次の場所です

  1. 事前条件 : 関数の呼び出し時、関数引数が初期化された直後
  2. 事後条件 : 関数が正常にreturnする時、戻り値が初期化され自動変数が破棄された後、関数引数が破棄される前

事前条件の実行時チェックによって関数の引数の予期しない組み合わせが関数本体で使用されないことが保証され、事後条件の実行時チェックによって呼び出し元が後続の処理を実行するにあたって望ましい状態のオブジェクト(またはプログラムの他の部分の状態)を確実に保証できるようになります。

awaitable<int> cancelable_session(int id) 
  [[post r: is_cancelable(r)]];
  
template <typename T>
void manage(awaitable<T> session)
  [[pre: is_cancelable(session)]];
  
void test()
{
  awaitable<int> session = cancelable_session(1);
  // 契約を順守してmanage()を呼び出していることを確認するには
  // is_cancelable(session)がtrueであることを保証する必要がある
  manage(session);
}

したがって、事前条件と事後条件は関数の呼び出し側と呼び出される側の関係性であり、呼び出し側は呼び出す関数のシグネチャのみを認識しその本体を認識しません。呼び出し側は呼び出しているものがコルーチンなのか関数なのかを知りません。

このようなことを前提とすると、コルーチンにおける事前条件のセマンティクスは、関数引数が初期化された直後、その引数が使用される可能性のあるコルーチンの初期アクションが実行される前、にチェックされることが最も適切です。

事後条件は呼び出されたコルーチンが呼び出し元に戻るタイミングが2種類あるために、より複雑になります。

通常の関数では、事後条件は戻り値に関して要求されることが多く、戻り値は関数の最後に作成されるため、事後条件が関数の最後に確立されると期待するのは自然です。そこでは、関数の終了時に何が起こるか?と、関数が呼び出し元にreturnしたときに何が起こるか?は同じ意味です。しかし、コルーチンの場合はその2つを区別する必要がある場合があり、その場合は後者のみが呼び出し側に関係します。

コルーチンとよく関連する非同期処理の観点から見ると、非同期処理Aを呼び出す呼び出し元は、呼び出したAの実行結果には関心が無い場合がほとんどで、Aから戻り値の形で処理の成果を取得するわけではありません。

Aの呼び出し元はAが成功したかどうか及びAの処理成果を何らかのグローバル状態やコールバックなどの形でのみ受け取ります。そのため、Aの処理が成功しておりその成果が得られていれば、Aがどのように終了したか(Aの終了時に何が起こったか)は呼び出し側には関係がありません。この場合にもやはり、関数(A)の終了時に何が起こるか?は呼び出し元には関係の無い性質です。

コルーチンによって非同期処理を行う場合、コルーチンの呼び出しにはタスクを非同期機構に投入するramp関数を呼び出す小さな初期の同期部分が存在します。呼び出し側は、この部分の成否には関心がある場合があります。つまり、ramp関数が処理を非同期機構に投入することに成功したかどうかを知りたい場合があります。

awaitable<int> cancelable_session(int id) 
  [[post r: is_cancelable(r)]];
  
void caller()
{
  awaitable<int> s = cancelable_session(1);
  [[assert: is_cancelable(s)]];
  global_cancelable_sessions.push(std::move(s));
}

これは単純な例ですが、コルーチンがどのように終了したかよりも、コルーチンによって非同期処理が開始されたかどうか(ramp関数が成功したかどうか)に関心がある場合のものです。

ただ、コルーチンはその呼び出し時点の状態によっては、初期サスペンドポイントで中断せずに続行しそのまま終了することもできます。つまり、コルーチンは完全に同期的に(通常の関数と同じように)終了することができます。その内部状態や観測可能な副作用が何らかの方法で既知であるとは考えられないため、このことはコルーチンの事後条件がその直接の戻り値オブジェクトによって表される状態に限定されることを示唆しています。

そうはいっても、コルーチンの最後に何が起きたのかを知りたい場合もあるでしょう。しかし、コルーチンの終了はco_return文の直接の到達によるものだけではなく(中断や例外送出など)、そのような終了に対する何らかの条件は呼び出し側と呼び出された側の合意を表すものではなく、別の種類の契約になります。

呼び出し側はコルーチン内部状態を推定できないため、呼び出し側がコルーチンの終了点の状態についてを実行時チェックで検証できることを保証できません。従って、コルーチンの終了点(中断点)における事後条件(コルーチン状態に関する仮想的な保証)はアサーションの方法でコルーチン内部でチェックされるべきです。

事後条件は呼び出し側に関連するものであり、呼び出し側は関数の宣言と呼び出し式そのものしか見えないため、呼び出しているのがコルーチンなのかどうかを知ることはできません。また、現在の契約プログラミング仕様では、関数の事後条件は呼び出し側でもその関数の終了直後に契約条件を評価することによって行える必要があります。

awaitable<int> cancelable_session(int id) 
  [[post r: is_cancelable(r)]];
  
void caller() {
  awaitable<int> s = cancelable_session(1);
  // ここで事後条件を検証できるはず
  ... 
}

これらのことからこの提案では、コルーチンに対する事後条件を許可し、それはコルーチンのramp関数を呼び出した直後に評価され、ramp関数の呼び出しが終了した時点のプログラムの望ましい状態を記述するものとすることを提案しています。コルーチンに対する事後条件の評価は、コルーチン呼び出しの結果オブジェクトの初期化の直後に順序付けられ、コルーチンローカルの状態の破棄に対しては順序付けされません。事後条件から参照される関数引数は、コルーチンフレームにコピーされたものではなく、元の関数引数を参照します。

言い換えると、コルーチンに対する事前条件と事後条件の指定は、そのコルーチンの呼び出しが類似の契約条件指定を持つファクトリ転送関数にラップされた場合と同じように動作する必要があります。

awaitable<int> f1(int i)  // coroutine
  [[pre: p(i)]]
  [[post r: q(r)]];
  
awaitable<int> f2(int i); // coroutine

awaitable<int> ff2(int i)
  [[pre: p(i)]]
  [[post r: q(r)]];
{
  return f2(i);
}
  
void caller() {
  // これら2つの関数呼び出しは契約条件評価に関して類似したセマンティクスを持つ
  f1(1);
  ff2(1);
}

P2958R0 typeof and typeof_unqual

C23に導入されたtypeoftypeof_unqualC++でも有効化する提案。

C言語の次期バージョンであるC23には、長らく実装の拡張として存在してきたtypeof演算子が導入され、そこから修飾情報を取り除いた型を得るtypeof_unqualも同時に導入されました。

typeof演算子は既にCで幅広く使用されており、C++とのコード共有部分においても使用されています。C++は標準としてはtypeof演算子を持たず、typeof_unqualは実装の拡張としても存在しなかったため、その存在がC23とのコード共有部分において問題となります。

typeof演算子のやることはdecltypeとほぼ同じであるためマクロによるラップで単純には置換できますが、Cの式には値カテゴリの概念が無いことから結果が異なる場合がある他、typeof/typeof_unqualには式だけではなく型名も渡せるという微妙な違いがあります。

そのため、C23とのコード共有部分においては、C++のコードとしてtypeofを完全には近似できません。

この提案は、C23との親和性向上のために、C++でもC23の意味論に沿ったtypeof/typeof_unqual演算子を追加しようとするものです。

P2960R0 Concurrency TS Editor's report for N4956

Concurrency TS v2の最新のワーキングドラフト(N4956)の変更点をまとめた文書。

おわり

この記事のMarkdownソース