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

文書の一覧

全部で125本あります。

もくじ

N4966 St. Louis Meeting Invitation and Information

2024年6月24〜29日(米国時間)にかけてアメリカのセントルイスで行われるWG21全体会議の案内。

N4967 WG21 2023-10 Admin telecon minutes

2023年10月27日に行われたWG21管理者ミーティングの議事録

前回(6月)のミーティング以降の各SGの進捗や作業の報告や、11月のKona会議におけるミーティングの予定などが報告されています。

N4970 WG21 2023-11 Kona Minutes of Meeting

2023年11月にハワイのKonaで行われたWG21全体会議の議事録

N4971 Working Draft, Programming Languages -- C++

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

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

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

P0447R24 Introduction of std::hive to the standard library

P0447R25 Introduction of std::hive to the standard library

P0447R26 Introduction of std::hive to the standard library

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

以前の記事を参照

R24での変更は

  • 代替実装詳細のappendixにある、オーバーアライメントなしで小さい型をサポートする方法についての見直し
  • shrink_to_fitの文言を、std::vectorのものに近づける様に変更

R25での変更は

  • spliceが終端イテレータを無効化することを明確化
  • block_capacity_limits()constexprを付加
  • reshape(), shrink_to_fit()で要素の並べ替えが発生する可能性があるタイミングを明確化するとともに、削減
  • sort()list::sort()と調和する様に変更
  • 標準の言葉の表現と一貫するように、"shall be"を"is"に、"into hive"を"into *this"に修正
  • reshape()がキャパシティを変更する可能性があることを追記
  • 他の部分でカバーされているため、sort()の例外に関する記述を削除

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

  • is_active()を削除
  • get_iterator()に事前条件が追加され、効果の指定を削除
  • googleグループのリンク更新
  • 代替実装ガイドラインを代替実装のappendixへ移動
  • よくある質問と、委員会からの一部の質問への回答をappendixにまとめた
  • 最初のリファレンス実装と現在の実装に関するメモとベンチマークをappendixにまとめた
  • 委員会への質問セクションを削除し、簡単な定義セクションに置き換え
  • 負の値を処理できるdistance()への参照を削除
  • size()を持たないことについての情報を、Design DecisionsからFAQへ移動
  • 時間計算量のappendixを削除し、個々の関数のDesign Decisionsセクションへ移動
  • 正確さの向上のため、オーバーアライメントをストレージを人工的に広げる、に変更
  • 代替実装appendixのビットフィールド+ジャンプカウントをより小さな型をカバーできる様に変更
  • 非常に小さな型をサポートするための3つのより良いアプローチについて代替実装appendixに追記
  • FAQにslot mapとの比較に関するセクションを追加

などです。

この提案は、LEWGでのレビューと投票を終えて、LWGに転送されています。

P0609R2 Attributes for Structured Bindings

構造化束縛の個々の名前に属性を指定できるようにする提案。

構造化束縛宣言には属性を指定することができますが、その属性が作用するのは構造化束縛宣言の裏に隠れている分解対象のオブジェクトであり、導入されている個々の名前ではありません。

auto f() -> std::tuple<int, double, char>;

int main() {
  // この属性指定はf()の戻り値(見えていない)に対するもの
  [[maybe_unused]]
  auto [a, b, c] = f();
}

構造化束縛対象そのものに対してアライメントを指定するなど、この指定にはユースケースがあります。

しかし一方で、構造化束縛宣言の個々の名前に対して属性を指定する方法はありません。標準属性で使用可能かつ意味があるのは[[maybe_unused]]のみですが、コンパイラベンダなどが提供する多くのアノテーション属性などを考慮すると、それを行いたい動機付けは大きくなる可能性があります。

この提案は、構造化束縛宣言内のそれぞれの名前に対して直接属性指定を行えるように文法を拡張しようとするものです。

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

auto f() -> std::tuple<int, double, char>;

int main() {
  // cにのみ[[maybe_unused]]を指定
  auto [a, b, [[maybe_unused]] c] = f();
}

構造化束縛宣言の[]の中で名前に対して直接属性を指定できるようにしています。最初の名前に指定する場合[[[のように角括弧が連続する可能性はありますが、構文は他の場所での属性指定構文と一貫しています。

P0952R2 A new specification for std::generate_canonical

std::generate_canonicalの仕様を改善する提案。

以前の記事を参照

このリビジョンでの変更は、提案する文言に、アルゴリズム指定の式中のRについての注記を追加したことです。

この提案は2023年11月のKona会議で採択され、C++26ドラフトに取り込まれています。

P1028R6 SG14 status_code and standard error object

現在の<sysytem_error>にあるものを置き換える、エラーコード/ステータス伝搬のためのライブラリ機能の提案。

以前の記事を参照

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

  • errc::successerrc::invalidに変更
  • status_code_domainstring_viewから構築するconstexprコンストラクタを追加
  • status_code_domaindo_*()protectedメンバ関数_を削除
  • status_code_domain<=>を追加
  • status_code_domainトリビアルコピー可能ではなくなった
  • 型消去されたステータスコードに対しては、通常のコピーコンストラクタではなくstatus_code(in_place_t, const status_code<void> & v)を使用する様に変更

などです。

P1061R6 Structured Bindings can introduce a Pack

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

以前の記事を参照

このリビジョンでの変更は、提案する文言の変更とより複雑な例の追加などです。

この提案は、現在CWGのレビュー受けています。

P1068R10 Vector API for random number generation

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

以前の記事を参照

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

  • ADLの代わりに非静的メンバ関数を探索するようにCPOの動作を変更
  • std::ranges::generate_randomが一時オブジェクトなエンジンと分布生成器をサポートする様に変更(フォーワーディングリファレンスの使用による)

などです。

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

P1673R13 A free function linear algebra interface based on the BLAS

標準ライブラリに、BLASをベースとした密行列のための線形代数ライブラリを追加する提案。

以前の記事を参照

このリビジョンでの変更は多岐に渡りますが、LWGのレビューを受けての文言の細かい調整がメインです。

この提案は、2023年11月のKona会議で全体投票を通過し、C++26ドラフトに取り込まれています。

P1708R8 Basic Statistics

標準ライブラリにいくつかの統計関数を追加する提案。

以前の記事を参照

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

  • コンストラクタは基本的にexplicitではなくした
  • accumulator object.value()メンバ関数を単純化
  • 名前に使用されているstatsstatisticsに変更

などです。

P1709R4 Graph Library

グラフアルゴリズムとデータ構造のためのライブラリ機能の提案。

以前の記事を参照

このリビジョンでは、4年分の経験や検討を反映した大規模な再設計が行われています。その変更は

  • 考慮すべきアルゴリズムの再確認
  • 外向きエッジを持つ隣接リスト、エッジリスト、remove/mutableインターフェースに焦点を絞って、提案の範囲を縮小
  • directed/undirectedコンセプトをグラフ型に対する順序なしエッジのオーバーロード可能な型、に置き換え
  • グラフコンテナ型と関数を単純化
    • 特に、const/非constの変種を1つの定義に統合し、必要に応じて両方の場合を扱える様にした
  • 全てのグラフコンテナインターフェースはカスタマイズポイントとなった
  • NWGraphライブラリの設計からインスピレーションを得たviewを導入し、グラフをトラバースするためのよりシンプルでクリーンなインターフェースを実現し、コンテナインターフェースの設計を簡素化
  • 二部グラフと多部グラフのサポート追加
  • 2つのコンテナ実装を、高性能グラフ処理でよく使用されているデータ構造であるCompressed Sparse Rowに基づく圧縮グラフに置き換え

などです。

このリビジョンでは特に、Boost.Graphの経験を踏まえてC++20で作成されたNWGraphライブラリにおける経験を取り込んでいます。NWGraphでは、範囲の範囲としてのグラフを定義し、その抽象の下でいくつかのグラフアルゴリズムを実装しています。このリビジョンでは、その設計の利点やアルゴリズム実装を取り込むことで以前のAPIを進化させています。

一方、NWGraphには既に使用されている任意のグラフデータ構造を使用可能にするためのAPIが欠けており、この提案では以前のリビジョンからのものを発展させてその部分を補っています。

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

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

以前の記事を参照

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

  • reduce_min_index/reduce_max_indexの戻り値としてstd::optionalを返すAPIの検討
  • CV修飾されていない算術型、をより明確な型のリストに置き換え
  • その他文章と文言の修正

などです。

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

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

以前の記事を参照

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

  • embed-element-widthを削除し、適切なCHAR_BITに置き換え
  • マクロ展開の方法を変更することでマクロ展開の問題の解消を図る

などです。

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

P2022R3 Rangified version of lexicographical_compare_three_way

std::lexicographical_compare_three_wayのRange版を追加する提案。

以前の記事を参照

このリビジョンでの変更は、same_as_any_ofコンセプトを<concepts>に移動したこと、インターフェースを再考したことなどです。

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

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

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

R6での変更は、条件式のboolへの変換に際して、スコープ付き列挙型の値の変換を抑制する巧妙なトリックの採用によって<type_traits>への依存等の懸念を解消したことです。

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

R5では、assertマクロのオペランドを明示的bool変換することで渡された条件式の評価結果をbool値として取得していました。ただし、そうしてしまうとスコープ付き列挙型の値が渡された場合にもbool値に変換できてしまうためこれを防止するためのトリックが必要だったのですが、そのトリックのためには<type_traits>への依存関係や追加のラップ関数等が必要となり、それが懸念されていました。

この問題に対して、スコープ付き列挙値の変換を防ぎつつ他のものはboolに変換する次のようなトリックがフィードバックとして寄せられました

#define assert(...) ((__VA_ARGS__) ? (void)sizeof(bool(__VA_ARGS__)) : (void)__assert_fail(#__VA_ARGS__, __FILE__, __LINE__))

条件演算子の第一オペランドで、文脈的bool変換によって渡された条件式の結果をbool値に変換しています。文脈的bool変換はifオペランドで行われるのと等価の変換で、暗黙変換であるためスコープ付き列挙値をbool値に変換することができません。

R6ではこのトリックの採用を前提として、明示的bool変換の代わりに文脈的bool変換によって渡された条件式の結果を取得するように文言を修正しています。

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

P2267R1 Library Evolution Policies

C++標準ライブラリ設計のためのポリシーについて検討する提案。

以前の記事を参照

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

  • 以前に行った作業のリストに提案を追加
  • LEWGからのフィードバック(ポリシーを採用することで一貫性が向上し時間が節約される根拠)を追加

などです。

P2308R1 Template parameter initialization

非型テンプレートパラメータの初期化に関しての規定を充実させる提案。

以前の記事を参照

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

  • NTTPの模範となる値の宣言にconstexprを付加
  • テンプレートパラメータオブジェクトの制約と選択を明確化し、模範(exemplar)という用語を削除

などです。

このリビジョンでは、以前に使用されていた模範(exemplar)という用語は削除され、代わりに初期化子候補(candidate initializer)という用語が導入されています(その意味するところは若干異なっていますが)。

P2414R2 Pointer lifetime-end zap proposed solutions

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

以前の記事を参照

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

  • 在野のLIFOプッシュアルゴリズムの調査に基づく更新
    • LIFOプッシュライブラリが次のスタックノードへのポインタに直接アクセスできないという事実について
  • 選択されていないオプションを削除して、特定のソリューションに焦点を当てる
  • ソースコード内の特定の明確にマークされたポインタのみが、実装がポインタの無効性を再検討できる様にするアプローチのみに焦点を当てている

などです。

P2447R6 std::span over an initializer list

std::spaninitializer_listを受け取るコンストラクタを追加する提案。

以前の記事を参照

このリビジョンでの変更は、コンストラクタからnoexceptを削除したことです。

この提案は既に、2023年11月のKona会議で全体投票を通過し、C++26WDに取り込まれています。

P2481R2 Forwarding reference to specific type/template

テンプレートパラメータ指定時にconstと値カテゴリを推論可能にする構文の必要性について説明した文書。

このリビジョンでの変更は、EWGにおける投票結果を追記したことと、提案する対象を絞ったことなどです。

EWGにおける投票では、この問題の解決の手段として新しい転送参照(forwarding reference)を求める意見が明確化された様です。

これを受けて、この提案は新しい転送参照として提案する対象を2つに絞っています。

1つは以前から提示されていたT auto&&形式

void f(std::string auto&& a);

template <typename... Ts>
void g(std::tuple<Ts...> auto&& b);

template <typename T>
void h(T auto&& c);

template <typename T>
void i(T auto& d);

もう一つはこの提案で提示されたforward Tの形式

void f(forward std::string a);

template <typename... Ts>
void g(forward std::tuple<Ts...> b);

template <typename T>
void h(forward T c);

template <typename T>
void i(forward T& d);

forward Tの形式T auto&&とほぼ同じように使用でき同じ利点がありますが、concept autoと構文が異なることで異なる動作をすることが明確になる点が改善されています。ただし、他の欠点(decltype()を使用しないと型を取得できない)はそのままであり、またforwardというキーワードが一般的すぎる点などが欠点として追加されます。

P2542R7 views::concat

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

以前の記事を参照

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

  • !common_range && random_access_range && sized_rangeのような範囲をサポートしないことを文言に適用
  • const変換コンストラクタを修正
  • 文言の修正

などです。

この提案はLEWGのレビューを終えて、LWGに転送されています。

P2573R1 = delete("should have a reason");

関数のdelete指定にメッセージを付加できるようにする提案。

以前の記事を参照

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

  • C++26をターゲットとした
  • ベースとなるドラフトの更新と、関連する提案の追記
  • 以前の同様の提案であるN4186の投票結果を追記

などです。

P2642R5 Padded mdspan layouts

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

以前の記事を参照

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

  • 機能テストマクロを削除して、__cpp_lib_submdspanバンプアップするように変更
  • P2630R3(submdspan)の内容を適用
  • 実装経験などを更新

などです。

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

P2662R3 Pack Indexing

パラメータパックにインデックスアクセスできるようにする提案。

以前の記事を参照

このリビジョンでの変更はCWGレビューを受けての文言の改善です。

この提案はすでに2023年11月の全体会議で採択され、C++26に取り込まれています。

P2663R5 Proposal to support interleaved complex values in std::simd

std::simdstd::complexをサポートできるようにする提案。

以前の記事を参照

このリビジョンでの変更は、std::complex浮動小数点数型の特殊化のみが許可されることを明確にしたこと、real/imagセッターのフリー関数を考慮するオプションを削除しメンバ関数のみを考慮するようにしたことです。

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

P2664R5 Proposal to extend std::simd with permutation API

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

以前の記事を参照

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

  • gatherscatterをそれぞれgather_fromscatter_toに変更
  • gather_fromscatter_toにマスキングオーバーロードを追加
  • メモリの動作を制御するために、gather_fromscatter_toにフラグを追加
  • gather_fromscatter_toのメリットについて追記
  • simd_splitsimd_catに新しい名前を使用するよう切り替え

などです。

P2717R4 Tool Introspection

P2717R5 Tool Introspection

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

以前の記事を参照

R4での変更は、ツールにとっての後方互換性の意味についての説明を追加したことです。

このリビジョンでの変更は、機能名の区切り文字(コマンドオプションではなく、返すJSONのキー名)をドッド(.)に変更し、文言をEcosystem ISドラフトにマージしたことなどです。

P2747R1 constexpr placement new

定数式において、placement newの使用を許可する提案。

以前の記事を参照

このリビジョンでは、以前に提案していた3つのことのうち1つ(placement newの定数式での許可)にのみ提案を絞ったことです。

以前のこの提案では

  1. void*からの適切なポインタキャストを定数式で許可する
  2. 定数式でplacement newを許可する
  3. 未初期化オブジェクトの配列の取り扱いの改善

の3つを提案していました。1はP2738R1の採択によって解決され、3は本質的に別の問題であるため分離され他のところで対処されようとしています(P3074R0など)。

そのため、この提案は2の解決にのみ対象を絞っています。

定数式でのplacement newを行う関数としては、std::construct_atが既に存在しています。しかし、この関数による初期化方法は非常に限定されています。

初期化方法 placement new construct_at()
値初期化 new (p) T(args...) std::construct_at(p, args...)
デフォルト初期化 new (p) T できない
リスト初期化 new (p) T{a, b} できない
指示付き初期化 new (p) T{ .a=a, .b=b } できない

また、ライブラリ関数であるためにコピー省略を妨げる問題もあります。

auto get_object() -> T;

void construct_into(T* p) {
  // get_object()の結果をムーブして構築
  std::construct_at(p, get_object());

  // get_object()の結果から直接構築
  :::new (p) T(get_object());
}

placement newが定数式で禁止されていたのはポインタをvoid*で受け取るためほぼなんでもできてしまうためで、それを回避するためにstd::construct_atという限定された機能を持つ関数が導入されていました。

しかし、定数式ではポインタの正しい型を追跡することができ、placement newができることを制限することができます。この能力を使用することでvoid*からのキャスト(static_cast<T*>(static_cast<void*>(p)))も許可されており、定数式でならplacement newを安全に使用することができます。

P2758R1 Emitting messages at compile time

コンパイル時に任意の診断メッセージを出力できるようにする提案。

以前の記事を参照

このリビジョンでの変更は、P2741R3と重複する内容を削除したことです。

以前のリビジョンでは、static_assertの第二引数に文字列範囲を渡せるようにすることとコンパイル時メッセージ出力のためのライブラリ関数の2つの事を提案していました。前者はP2741がC++26に採択されたことで不要になったため、このリビジョンでは後者のライブラリ機能だけに的を絞っています。

P2760R1 A Plan for C++26 Ranges

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

以前の記事を参照

このリビジョンでの変更は、output_iteratorの改善と並行アルゴリズムサポートについてを優先度1に追加した事です。

output_iteratorの改善とは、T*のように入力イテレータでもある出力イテレータに対して、back_insert_iteratorのように出力だけしかできない出力イテレータを区別することです。それを行うことで、出力イテレータの定義を簡略化するとともに出力動作を効率化する機会を提供できるためです。

出力イテレータは典型的には次のように使用されます

template <typename InputIt, typename OutputIt>
void copy(InputIt first, InputIt last, OutputIt out) {
  for (; first != last; ++first) {
    *out++ = *first;
  }
}

outが出力イテレータですが、これは入力イテレータよりも明らかに必要な操作が少ないことが分かります。これを反映して、back_insert_iteratorを例えば次のように実装できるかもしれません。

template <typename C>
class back_inserter {
  C* cont_;

public:
  explicit back_inserter(C& c) : cont_(&c) { }

  // these do nothing
  auto operator*() -> back_inserter& { return *this; }
  auto operator++() -> back_inserter& { return *this; }
  auto operator++(int) -> back_inserter { return *this; }

  // this one does something
  auto operator=(typename C::value_type const& val) -> back_inserter& {
      cont_->push_back(val);
      return *this;
  }

  // same
  auto operator=(typename C::value_type&& val) -> back_inserter& {
    ...
  }
};

この実装は有効ではありますが、冗長な関数をいくつも記述しなければならないなど面倒な部分が多数あります(本来=のみでいいはず)。

そして何より、出力イテレータの出力が要素を一個づつ出力していくことしか考慮していないことが、出力パフォーマンスを低下させています。入力のサイズが分かっている場合、reserve()したりC++23の範囲挿入関数を使用するなどしてより出力操作を効率化できる可能性があります。しかし、現在の出力イテレータの定義はその機会を提供していません。

出力専用のイテレータを定義することで、出力イテレータの実装簡易化と効率的な出力経路の提供が同時に達成できます。

P2761R0 Slides: If structured binding (P0963R1 presentation)

P0963R1の紹介スライド。

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

EWGIのメンバに向けて、P0963R1で提案されている機能のモチベーションや動作について説明するものです。

P2767R2 flat_map/flat_set omnibus

flat_map/flat_setの仕様にあるいくつかの問題点とその解決策について報告する提案。

以前の記事を参照

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

  • LWGを対象とするものとLEWGを対象とするものに分割し、並べ替えた
  • 1つの提案で一度に解決されるサブセクションを統合
  • 多くの例を前後比較するテーブルに置き換えた
  • コンテナのゼロ初期化についてを追加
    • 内部コンテナを値初期化していることで一部のコンテナでは非効率になる可能性がある

などです。

P2795R4 Erroneous behaviour for uninitialized reads

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

以前の記事を参照

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

  • 再び、対象をすべての自動変数(一時オブジェクトを含む)に戻した
  • std::bit_castに誤った値(EBとして読み取られた値)を処理するための文言を追加
  • オブジェクト表現がその型に対して有効ではない場合、誤った動作の後で未定義動作が発生する可能性がある事を明確化
  • 関数パラメータに対するオプトアウト属性指定は、関数の最初の宣言に指定する必要があることを明確化
  • 誤った動作が発生する状況を定義するために使用される標準のフレーズを確立するために文言を更新

などです。

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

P2806R2 do expressions

値を返せるスコープを導入するdo式の提案。

以前の記事を参照

このリビジョンでの変更は、do式からのreturndo return ...からdo_return ...へ変更した(曖昧さを解消するため)ことと生存期間に関するセクションを追加したことなどです。

P2810R2 is_debugger_present is_replaceable

P2810R3 is_debugger_present is_replaceable

P2546で提案されているis_debugger_present()をユーザーが置換可能にする提案。

以前の記事を参照

R2での変更は、2023年9月のLEWGにおける投票結果を追記し、それを反映してユーザー置換関数に事前条件を付加しないように修正した事です。

このリビジョンでの変更は、提案する文言から問題のあった注記を削除した事です。

この提案はLEWGのレビューを終えてLWGに転送されています。

P2819R2 Add tuple protocol to complex

std::complextupleインターフェースを追加する提案。

以前の記事を参照

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

  • 専用の機能テストマクロを削除
  • Hidden friendなget()はtuple-likeでは動作しなかったため、フリー関数に戻した
  • Annex Cの文言を削除

などです。

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

P2821R5 span.at()

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

以前の記事を参照

このリビジョンでの変更は、余分なフリースタンディングコメントを削除したことです。

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

P2826R1 Replacement functions

ある関数を指定したシグネチャでそのオーバーロード集合に追加できるようにする、一種の関数エイリアスの提案。

以前の記事を参照

このリビジョンでの変更はよく分かりませんが、文書の構成を整理していくつか例を追加しているようです。

P2827R1 Floating-point overflow and underflow in from_chars (LWG 3081)

std::from_chars浮動小数点数を変換する際にアンダーフローを検出できるようにする提案。

以前の記事を参照

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

P2830R1 constexpr type comparison

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

以前の記事を参照

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

  • std::type_info::beforeの変更を行わないオプションを追加
  • 匿名名前空間を空にすることができないことを明確化
  • FAQセクションを追加
  • 実用的なサンプルコードを追加
  • 提案する構文を追加
  • appendixを追加

などです。

この提案ではオプションとして次の5つを提示しています

  1. std::type_info::beforeconstexprを付加
    • 利点
      • 新しいトークンが必要ない
      • 発見されやすい
      • 組み込みのtype-erasureであること
    • 欠点
      • ABI破壊
      • 多くの場所で禁止されている<typeinfo>をインクルードする必要がある
      • NTTPに対して使用できない
  2. std::strong_ordertype_info比較のためのオーバーロード追加
    • 利点
    • 欠点
      • <compare><typeinfo>のインクルードが必要になる
      • NTTPに対して使用できない
  3. std::entity_ordering変数テンプレートの導入
    • 利点
      • 新しい名前であり、明確にコンパイル時に利用可能
      • <typeinfo>のインクルードが必要ない
    • 欠点
      • 新しい名前であること
      • 発見されづらい
  4. std::type_identityoperator<=>を追加
    • 利点
      • 若干やることが明白
    • 欠点
      • <type_traits><compare>に依存するようになる
      • NTTPに対して使用できない
  5. ユニバーサルテンプレートパラメータを用いたstd::__lift::operator<=>を導入
    • 利点
      • 強いて言うなら、そのうちこの__liftのようなものが必要になるかもしれない
    • 欠点
      • 何をするか明確ではない
      • 新しい名前であること
      • 発見されづらい
      • 本質的に別のtype_infoを追加している

3つ目のstd::entity_orderingとは次のような変数テンプレートです。

template <universal template T, universal template U>
inline constexpr std::strong_ordering entity_ordering = ORDER(T, U);

すなわち、std::entity_ordering<T, U> < 0のようにしてT, Uの順序付比較が行えます。

P2845R5 Formatting of std::filesystem::path

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

以前の記事を参照

このリビジョンでの変更は、「無効なコード単位」をより具体的な「不適格な部分列の最大部分」に置き換えたこと、LEWGでの投票結果を追記したことです。

P2863R3 Review Annex D for C++26

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

以前の記事を参照

このリビジョンでの変更は、関連提案のステータス更新や、その表記法の変更などです。

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

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

以前の記事を参照

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

  • Kona会議でのEWGのレビューの記録を追加
  • いくつかのセクションの並べ替え
  • 非推奨警告についての調査を更新
  • ベースとなるドラフトの更新
  • Annex Cのテキスト修正

などです。

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

P2865R4 Remove Deprecated Array Comparisons from C++26

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

以前の記事を参照

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

  • 最新のWDに追随
  • 委員会での進捗を追記
  • C互換性について追記
  • 非推奨警告の調査を更新

などです。

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

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

以前の記事を参照

このリビジョンでの変更は、最新のWDに追随したことと、Annex Cの修正などです。

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

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

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

以前の記事を参照

このリビジョンでの変更は全体的な修正などです。

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

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

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

以前の記事を参照

このリビジョンでの変更は、最新のWDに追随したことと、Annex Cの修正などです。

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

P2871R3 Remove Deprecated Unicode Conversion Facets From C++26

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

以前の記事を参照

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

  • 最新のWDに追随した
  • 互換性の文言を更新し、ヘッダユニットのインポートについて言及
  • C++03との差分にある<codecvt>ヘッダ周りの参照を削除

などです。

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

P2878R6 Reference checking

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

以前の記事を参照

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

などです。

P2890R1 Contracts on lambdas

P2890R2 Contracts on lambdas

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

以前の記事を参照

R1での変更は、キャプチャの問題を解決するための別のオプションを追加したことです。

このリビジョンでの変更は、無意味な名前探索ルールを修正したことです。

ラムダ式で契約注釈を使用可能にする際に最も問題となるのは、デフォルトキャプチャ使用時に契約注釈内部だけで使用されている外部エンティティを暗黙的にキャプチャするかどうかです。

constexpr auto f(int i) {
  return sizeof( [=] [[pre: i > 0]] {});  // 戻り値はどうなる?
}

constexpr auto g(int i) {
  return sizeof( [=] { [[assume (i > 0)]]; } ); // 戻り値はsizeof(int)
}

R0では契約注釈だけで使用されているラムダ式外部の変数はデフォルトキャプチャによるキャプチャの対象とすることを提案していました。これについて、R1では全部で7つのオプションが提示されています

  1. 契約述語はキャプチャをトリガーできる
    • assume属性における同様の議論では、そのようなキャプチャをill-formedにするために言語の複雑化を正当化するには実際のコードで問題が起きる可能性が低い(エッジケースである)として、キャプチャをトリガーすることを維持した
    • 契約注釈にも同じことが言える
  2. 契約述語はキャプチャをトリガーしない
    • P2932R2で提案されている
    • 単にキャプチャをトリガーしない、とするだけだと契約注釈で間違った変数を使用してしまう可能性がある(ローカル変数と同名のグローバル変数を暗黙的に使ってしまう)
  3. 契約述語がキャプチャをトリガーする場合ill-formed
    • オプション2の問題をカバーしたもの
    • 言語が複雑化し、ユーザーにとって自明ではないかもしれない
    • assume属性と矛盾する
  4. 契約述語がキャプチャをトリガーする場合警告する
    • 契約注釈からキャプチャをトリガーするコードは常に疑わしいコードであり、警告するならill-formed(オプション3)の方が望ましい
  5. 契約述語内で他の方法でodr-usedされないエンティティをodr-usedすることはill-formed
  6. デフォルトキャプチャを使用したラムダ式で契約注釈指定を許可しない
    • ユーザにとってあまりに多くの価値を奪うものであり、思い切りが良すぎる
  7. ラムダ式で契約注釈の使用を許可しない
    • 6と同様

この提案では、この中で実行可能な選択肢は1か3のどちらかであるとしています。なお、現在のContracts MVP仕様としては2が採用されています(別の提案によるもの)。

P2894R1 Constant evaluation of Contracts

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

以前の記事を参照

R0では、コンパイル時に契約チェックするかどうか(及び定数式で実行できない契約条件の扱いをどうするか)について、グローバルなスイッチによって一律的に制御するようなことを提案していましたが、翻訳単位によってその設定が異なる可能性があることを指摘されました。それによって再検討が行われ、このリビジョンでは結局実行時と同様に、全ての契約注釈のセマンティクスは実装によって定義され、注釈ごと、その評価ごとに変化しうる、というモデルを採用することになりました。

以前はコンパイル時にセマンティクスを区別する必要はない、としていましたが、実装が定数評価中にそのセマンティクスを選択する以上3種類のセマンティクスが定数評価中にどのように動作するかを指定する必要があります。

この提案での検討対象はR0と同様に次の場合のみです

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

まず1の場合、契約注釈を持つ関数が定数式で呼ばれるまではその契約条件式の定数実行可能性を気にしないことはR0と同様です。定数実行できない契約注釈を持つ関数が定数式で呼ばれてしまった時が問題になります。

その場合、R0では実装定義(コンパイラオプション等で指定)としていました。このリビジョンでは、契約違反が起きたものとして扱うことを提案しています。すなわち、コンパイル時に呼び出すことのできない事前・事後条件(及びアサーション)はコンパイル時には決して満たされることはないと言うことであり、それは契約違反となります。契約違反時の動作は契約注釈のセマンティクスによって指定されます。

2の場合とはつまり契約違反が起きたと言うことであり、その動作はセマンティクスごとに指定され、次のようになります

  • ignore, observe : 診断を発行(エラーにならない)
  • enforce : 診断を発行しill-formed(エラーになる)

そして、定数式における契約注釈のセマンティクスは契約注釈及びその評価ごとに実装定義(オプション等によって指定)となります。

また、定数初期化されうる変数の初期化式などの定数式で評価されるかどうかが決定的でない文脈においての契約注釈の評価については、上記のようなセマンティクス適用以前に、その契約注釈を含む式が定数評価可能かを契約注釈を無視してテストして、定数評価可能ならば再度契約セマンティクスを有効化した上で初期化式の評価を行うようにすることを提案しています。

P2900R2 Contracts for C++

P2900R3 Contracts for C++

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

以前の記事を参照

R2での変更は

  • 構文としてnatural syntaxを採用
    • P2961R2を採択
  • default関数に対する事前/事後条件の指定はill-formed
    • P2932R2を採択

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

  • delete関数に対する事前/事後条件の指定はill-formed
  • ラムダ式に対する事前/事後条件の指定を許可
  • 契約注釈は暗黙のラムダキャプチャをトリガーしない
    • P2932R2を採択
  • std::contracts::invoke_default_contract_violation_handler()(デフォルトの違反ハンドラを呼び出す関数)を追加
  • 契約条件式から参照されるローカルエンティティは暗黙的にconstとする
    • P3071R1を採択
  • 事後条件における戻り値名のセマンティクスを明確化
    • P3007R0を採択し、戻り値名は暗黙的にconstとする
  • Overviewセクションを追加
  • Recursive contract violationsセクションを追加

などです。

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

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

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

以前の記事を参照

R3での変更は

  • LEWGでの投票結果を追加

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

  • 機能テストマクロ__cpp_lib_format__cpp_lib_format_ucharに置き換え
  • wchar_tへの対応を改善するために文言を調整

などです。

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

P2918R2 Runtime format strings II

std::format()の実行時フォーマット文字列のためのAPIを追加する提案。

以前の記事を参照

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

  • 機能テストマクロ__cpp_lib_formatバンプするようにした
  • runtime-format-stringに説明専用メンバstrを追加
  • runtime-format-stringにコンストラクタを追加
  • この提案で追加される関数にはnoexceptを付加する
  • runtime-format-stringを固定化

などです。

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

P2932R2 A Principled Approach to Open Design Questions for Contracts

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

以前の記事を参照

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

  • セクションをいくつか追加
  • 定数式の処理セクション、observeセマンティクスを持つ契約注釈の実行時以外の評価についてのセクションを明確化
  • 提案1.Bをトリビアルな特殊メンバ関数に適用するように修正し、1.Cを追加
  • natural syntaxを使用するように書き換え

などです。

提案1.Cはトリビアルでないものも含めてdefaultで定義された関数に対して契約注釈を行えるのかどうかについてのもので、次のようなものです

  • default関数が、その最初の宣言で事前条件/事後条件を持つ場合、ill-formed

これは、defaultとはインターフェースなのか実装なのか、それに関して契約注釈はどうあるべきかなどについての議論を行うために、C++26に対してはとりあえず禁止しておくことを意図したものです。C++26 Contracts仕様にはこれが採用されています。

P2933R1 std::simd overloads for <bit> header

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

以前の記事を参照

このリビジョンでの変更は、文言も含めた全体のテキストの修正などです。

P2935R4 An Attribute-Like Syntax for Contracts

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

以前の記事を参照

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

  • [[assert : expr]]を式にした
  • 事後条件における戻り値命名の代替案の追加

などです。

2023年11月のKona会議において、C++契約プログラミングのための構文としてはこちらではなくP2961R2のnatural syntaxを採用することが決定されました。

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

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

以前の記事を参照

このリビジョンでの変更は、void戻り値型の指定とそのトリビアル性(この提案では許可しない)についての議論を追加したことです。

P2961R2 A natural syntax for Contracts

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

以前の記事を参照

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

  • アサートのキーワードとしてcontract_assertを採用
  • assertが契約におけるアサートのキーワードとしてふさわしくない理由を追記
  • 実装経験について追記
  • SG22での議論をもとに、Cでの実現可能性について追記

などです。

この提案は2023年11月のKona会議でC++26の契約プログラミング機能のための構文として採用されました。

P2968R1 Make std::ignore a first-class object

P2968R2 Make std::ignore a first-class object

std::ignoreを戻り値破棄のために合法的に使用できるようにする提案。

以前の記事を参照

R1での変更は

  • ビットフィールドについて言及し、それがstd::ignoreの代入にとって有効な右辺オペランドではないことを説明
  • 右辺オペランドとしてnon-volatileビットフィールドがサポートされるべきかの質問を追加
  • 既存の主要な実装の分析を含むフィードバック提案を組み込み
  • volatileビットフィールドによる副作用を防ぐために、ビットフィールドを右辺オペランドとして指定しないoperator=(auto&&)を使用してコードとして仕様を推奨

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

  • 個別のヘッダにしないことを決定
  • std::ignoreの代入演算子シグネチャとしてoperator=(auto const &) const noexceptを使用する
    • auto&& -> const auto&に変更し、左辺値参照修飾を削除

などです。

std::ignoreの代入演算子はあらゆる値を受けることができ、ほとんどの場合に元の値の読み取り(すなわちコピー)は発生しません。しかし、その例外がビットフィールドであり、ビットフィールドを渡そうとすると対応する整数型の一時オブジェクトを生成して渡す形になり、ビットフィールドの値へのアクセスが発生します。これが特に問題となるのはそのビットフィールドがvolatileである場合で、volatileの場合はその値へのアクセスを消し去ることができず、std::ignoreの代入演算子が何もしないという効果を達成できなくなります。

std::ignoreの代入演算子の規定としてどんな値でも受け取り何もしないと言う風に指定する代わりに、コードとして代入演算子シグネチャを指定し引数型を制限することでこの問題は解決できます。その際に、引数型をauto&&(ビットフィールド禁止)にするのかcosnt auto&volatileビットフィールドを受けられない)にするのかによってビットフィールドを許可するか、あるいは非volatileビットフィールドを許可するかが分岐します。

このリビジョンでは、cosnt auto&にすることでstd::ignoreではビットフィールドを許可するもののvolatileビットフィールドはサポートしないことにしています。

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

P2969R0 Contract annotations are potentially-throwing

contract_assert式に対するnoexcept演算子の振る舞いについての解決策を探る提案。

C++26 Contractsのセマンティクスとして、特に例外に関する部分についてSG21では次のことが合意されています

  • 契約条件式の評価に伴って例外がスローされる場合は契約違反として扱われ、関数の例外仕様とは無関係に違反ハンドラを呼び出す
  • 違反ハンドラからの例外送出は、通常の例外と同じ扱いとなる
    • スタック巻き戻しが発生し、巻き戻しがnoexcept関数呼び出しに及んだ場合はstd::terminate()によってプログラムは終了する
  • 契約注釈がチェックされているかどうかによって、noexcept演算子の結果や契約注釈が付加されている関数の型が変化することはない
  • 事前条件・事後条件・アサーションは全て、述語または違反ハンドラからの例外送出に関して同じように動作する

これによって、チェックされると常に例外を送出する、Well-definedな動作をもつWell-formedなC++プログラムが存在し得ます

#include <contracts>
using namespace std::contracts;

void handle_contract_violation(const contract_violation&) {
  throw 666;
}

int main() {
  contract_assert(false); // チェックされると必ず例外を送出する
}

しかもこのことは、契約違反ハンドラがnoexceptであることがわかっている場合にのみコンパイル時にわかります(違反ハンドラの置換は一般的にリンク時に行われるため、コンパイル時には分からないでしょう)。従って、違反ハンドラがどの様にカスタマイズされているか(あるいはされていないか)によらず、ユーザーから見た契約注釈は常に例外を投げうるものとして写ります。

現在のC++にはプログラムのコンパイル時のセマンティクスが特定の式が例外送出するかどうかに依存する場合があります。それは、noexcept演算子の結果を決定する時、最初の宣言がdefaultである特殊メンバ関数の例外仕様を決定する場合、の2つの場合です。

2023年11月のKona会議において、クラスの特殊メンバ関数の最初の宣言がdefaultである場合、契約注釈を付加できないことが決定されました。しかし、それでもなお、この2つの場合のそれぞれについて契約注釈の有無によって結果が変化しうる場合が存在し、現在のMVP仕様ではその状況について何も指定していません。

1つは、契約注釈そのものにnoexcept演算子を適用した場合で、これはcontract_assert()でのみ起こります。

noexcept(contract_assert(false)); // これはtrueになる?falseになる?

2つめは、クラスの特殊メンバ関数の例外仕様が推定される場合で、通常この場合はその関数が例外を投げうるものを含んでいなければnoexcept(true)と推定されます。現在その様な場所に直接契約注釈を付加することはできませんが、間接的に現れる可能性があります

struct B {
  int i = (contract_assert(false), 42);
};

struct S : B {
  // これらのコンストラクタのnoexcept性はどうなる?
  S() = default;
  S(S&&) = default;
};

この様なコンテキストは現在この2つだけのはずですが、将来の機能(例えば、noexcept(auto))を考慮するとさらに増加する可能性があります。

この提案は、C++26に契約機能を導入する前にこれらの仕様の空白を埋めるために可能な解決策を探るものです。

この提案で上げられている解決策は次の7つです

  1. 契約注釈を例外を送出しうるものとして扱う
    • 上記どちらも、noexcept(false)となる
    • ゼロオーバーヘッド原則に反する(契約注釈の追加によりムーブコンストラクタの例外仕様が変化するなど)。これは契約機能の採用の阻害要因となりうる
  2. 契約注釈を例外を送出しないものとして扱う
    • 上記どちらも、noexcept(true)となる
    • 契約注釈が存在する場合にnoexcept演算子が嘘をつくことなる
    • 例外中立なライブラリフレームワーク(P2300の様なもの)の記述が困難となる
  3. 1と2の両方の状況を許可する
    • 契約注釈のメタアノテーションコンパイラオプションによって契約注釈が例外を投げうるかを指定する
    • ユーザーが契約注釈にnoexceptメタアノテーションを付加する場合、単純に関数全体にnoexceptを付加するだけで良い
    • ビルドモードを導入するとC++の方言が生まれる
  4. 謝って送出された例外が推定されたnoexcept(true)例外仕様を回避できる様にする
    • 契約注釈は言語内で例外を投げないものとして扱われるが、違反ハンドラからスローされた例外のみnoexcept(true)な関数のスタック巻き戻しを行うことが許可される
    • 呼び出しが例外を投げないことを前提とする処理でこれが起こると、結局プログラムの実行は安全ではなくなる
    • 例外仕様のメンタルモデルが複雑化し言語内に矛盾が生じ、仕様と実装が複雑化する
  5. これらの場合が現れない様にする
    • 次の3つの方法が考えられる
      1. contract_assertを式ではなく文にする
      2. noexcept演算子contract_assertまたはcontract_assertを部分式として含む式に適用するとill-formed
        • さらに、例外仕様に影響を与えうる箇所にcontract_assertが出現するとill-formed
      3. noexcept演算子contract_assertまたはcontract_assertを部分式として含む式に適用するとill-formed
        • ただし、例外仕様に影響を与えうる箇所にあるcontract_assertがill-formedとなるのはその存在が例外仕様の推定に影響を与える場合のみ
    • 仕様や実装が複雑になり、ユーザーにより多くの苦労を強いることになるが、上記問題を回避し全ての場合にゼロオーバーヘッド原則を満たす
  6. ガイドラインと診断に頼る
    • ユーザーにその様なコードを書かないほうがいいと警告するならば、単にill-formedとしたほうがいい
  7. 違反ハンドラからの例外送出サポートを禁止する
    • 違反ハンドラからの例外送出を使用するのは実際には非常に難しいため、これを削除し例外を使用しない方法で同等の契約違反後の処理継続を可能にする方法を模索する
    • それを決定するとこの提案で挙げられている問題は全て解決されるが、後から違反ハンドラからの例外送出を許可するのは困難になる
    • 現時点では例外に代わる同等のメカニズムは存在していない

この提案では、それぞれの解決策による影響や実現可能性について考察していますが、どれかを推しているわけではありません。ただし、3,4の案は実現可能ではないと評されており、1,2,6の案は問題があり、7の案も困難が伴うとして、一番ポジティブな評価を受けているのは5(の2,3)の案です。

この提案でどの様な決定がなされたとしても事前・事後条件における違反ハンドラからの例外送出について同様の問題がある様に見えますが、それはすでに合意済みのことです。契約注釈の存在は例外仕様等のコンパイル時セマンティクスに影響を与えず、Lakos Ruleに基づいて契約注釈がなされている限り(無条件noexcept指定と契約注釈の両立は誤っている)それは問題になりません。例えば、あるクラスのムーブコンストラクタがnoexceptとなる場合、そのムーブコンストラクタは広い契約を持ちいかなる事前・事後条件も持たないはずで、そのクラスのメンバのムーブコンストラクタそれぞれについても同様の仮定が成立するはずです。この過程が成り立っていない場合、そのムーブコンストラクタはnoexcept(false)とするのが適切であり、その場合にのみ契約注釈によって事前・事後条件をチェックすることができます。

P2977R0 Module commands database format

ツール間で同じモジュールを生成するために必要となるコンパイルのための情報をファイルにまとめておく提案。

ビルドシステムのようなC++プロジェクトのビルドを管理するツールは、それをコンパイルしたコンパイラとは異なるコンパイラを使用するプロジェクトにおいてもモジュールを管理しなければなりません。しかし、一般的にビルド済みモジュールはコンパイラ間や同じコンパイラのバージョン間、あるいはモジュールを分析する必要のあるツール(静的解析ツールなど)等それぞれの間で互換性がありません。これらのツールはメインのビルドの設定に対応する形で、それぞれ独自の形式によってモジュール(のインターフェース)をビルドできる必要があり、メインのビルドと(形式等は除いて)同等のコンパイル済みモジュールを生成するために必要なフラグなどを知る必要があります。

この提案は、そのような情報をファイルに保存してツール間でやり取りすることを目的として、どのような情報が必要かやその保存の方法などを特定しようとするものです。

メインビルドと同等のコンパイル済みモジュールを作成するためには、ツールは少なくとも次の情報を知っている必要があります

  • モジュールインターフェースのソース
  • 生成されるモジュールのモジュール名
    • そのモジュールが使用される場所を特定するために必要
  • 必要なモジュールの名前(依存モジュール名)
    • モジュールの依存関係を解決するために必要
  • 必要なモジュールを提供するソースコード
  • ソース内でのモジュールの可視性
    • モジュール実装単位/実装パーティションのように、プライベートなモジュールが存在する
    • そのようなモジュールに属するシンボルは外部からアクセスできない場合があり、より的確な診断のためにモジュール名を使用できる
  • ソースを処理するビルド中に使用されるローカルプリプロセッサ引数
    • メインビルドの成果物に対応するものを生成するために使用する必要がある
    • ツールは、メインビルドに使用されるコンパイラのフラグを自身の利用のために変換する必要がある可能性がある
  • コンパイルのための作業ディレクト

これらの情報をビルドツリー内で表現する方法としては次の3つが挙げられています

  1. スタンドアロン
    • 全ての情報を何らかのファイルに保存する
    • (おそらくソースファイルごとに)別々のファイルとして存在している
    • この利点は、情報をビルドだけでなくインストールにも使用できる点
  2. コンパイルコマンドデータベースとの相互参照
    • Compile Commands Databaseと手元の情報とを組み合わせて使用する
    • 重複を減らすことができるが、必要な情報を全て取得するには2つのデータベースを手動でマージするツールが必要になる
    • この欠点は、あるソースが異なるフラグでビルドグラフ上に複数回現れうる点
  3. コンパイルコマンドデータベースとの共有
    • 手元の情報をコンパイルコマンドデータベースと共有できる部分とそうでない部分に分割し、共有できる部分はコンパイルコマンドデータベースのものを取得する

このようなモジュールコンパイルデータベースは、一般的にビルド中に作成される必要があります。なぜなら、モジュール名はビルドが完了するまでわからないからです。しかし、上記コンパイルコマンドデータベースがビルドの一部が完了するまで存在しない生成されるソースのコンパイルを参照することができ、新しい問題というわけではありません。

メインビルドに関わらない他のツールがこれらの情報を簡易に取得するために、ビルドシステムはモジュールコンパイルデータベースをよく知られた場所(おそらく、pkgconfigなどのようなものがある場所)にあるファイルにまとめる仕組みを提供する必要があります。これによって、他のツールが関連ファイルを探索する手間が軽減され、ファイル全体にわたって情報が一貫していることが保証されます。

この提案はこのような要件を列挙したもののようで、まだ何かを提案するには至っていないようです。

P2980R1 A motivation, scope, and plan for a quantities and units library

物理量と単位を扱うライブラリ機能の導入について説明する提案。

以前の記事を参照

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

  • ガーディアン紙の華氏の取り扱いに関する問題について追加
  • ScopeとPlan for standardizationを更新
  • タイトルからPhysicalを取り除いた

などです。

P2981R1 Improving our safety with a physical quantities and units library

↑の物理量と単位を扱うライブラリ機能について、コンパイル時の安全性に関する側面を解説する文書。

以前の記事を参照

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

  • ガーディアン紙の華氏の取り扱いに関する問題について追加
  • 燃料消費の例を拡張
  • いくつかの参考リンクを追加
  • 型を保持する型特製についてLack of safe numeric typesで説明
  • Non-negative quantitiesを書き直し
  • Limitations of systems of quantitiesセクションを追加
  • Temperaturesセクションを追加

などです。

P2982R1 std::quantity as a numeric type

↑の物理量と単位を扱うライブラリ機能について、数値と計算の側面に関する設計の説明をする文書。

以前の記事を参照

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

  • 燃料消費の例を拡張
  • Dimension is not enough to describe a quantityセクションを拡張
  • Lack of convertibility from fundamental typesセクションを追加
  • Terms and definitionsセクションにmp-unitsの用語集へのリンクを追加
  • Equivalenceセクションのサンプルコードを修正

などです。

P2984R1 Reconsider Redeclaring static constexpr Data Members

static constexprメンバ変数のクラス外定義の非推奨の扱いを検討する提案。

以前の記事を参照

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

  • コンパイラの警告についての状況を追加
  • 2023年11月のKona会議におけるレビュー結果について追加
  • conclusionセクションを追加

などです。

EWGのレビューにおいては、static constexprメンバ変数のクラス外定義を削除することが好まれた様でしたが、一方で現在十分に非推奨動作であることが警告されていない状況でいきなり削除してしまう事は好まれなかった様です。そのため、C++26に対してはこの点に関して変更を加えず、コンパイラベンダにはこの問題についての警告をよりユーザーに見える様にすることを推奨する、という方向性が支持されました。

P2996R1 Reflection for C++26

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

以前の記事を参照

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

  • 全てのサンプルにCompiler Explorerでの動作例リンクを付加
  • synth_structdefine_classに再指定
  • いくつかのメタ関数を関数テンプレートの代わりに関数として再指定
  • エラー処理メカニズムと例外の優先設定に関するセクションを追加
  • チケットカウンタとバリエーションの例を追加
  • entity_refpointer_to_membervalue_ofで表現する様に置き換え

などです。

静的リフレクションにおけるエラーとは例えば、std::meta::template_of(^int)のような間違ったコード(template_of(^T)は型Tのテンプレートの鏡像を取得する。intはテンプレートの特殊化ではないのでエラー)が何を返すか?という問題です。

この候補としては現時点で次のものが考えられます

  1. 静的リフレクション過程のエラーは定数式ではない(コンパイルエラー)
  2. 無効値(エラーであること、およびソースの場所とその他有用な情報)を保持する鏡像を返す
    • P1240の推奨アプローチ
  3. std::expected<std::meta::info, E>を返す。Eはソースの場所とその他有用な情報を提供する
  4. Eを例外としてスローする
    • 定数式で例外を許可する必要がある。おそらく、定数式中でキャッチされない例外のみコンパイルエラーとなる様にする

2つ目の方法の欠点は、メタ関数が範囲を返す場合(template_arguments_of(^int)の様な場合)に何を返すかという点です。この場合に単一の無効な鏡像値を返すと正常なものが帰る場合との一貫性がなくなり、使いづらくなります。かといって、空の範囲を返してしまうとエラー情報が失われてしまいます。結局、何を返したとしても、expectedもしくは例外のアプローチの方がより一貫性があり簡単なエラー処理を提供します。

例外にはいくつかの懸念がありますが、ここでの文脈は定数式であるため、実行時における例外の問題点はここでは問題になりません。その上で次のようなよく使用されると思われる様な例を考慮すると

template <typename T>
  requires (template_of(^T) == ^std::optional)
void foo();
  • template_of(^T)expected<info, E>を返す場合、foo<int>は置換失敗
    • expected<info, E>infoは等値比較可能であり、結果はfalseとなり制約を満たさなくなる
  • template_of(^T)が例外を送出する場合、foo<int>から送出された例外がキャッチされない場合はコンパイルエラー
    • これは置換失敗ではなく、制約が定数式ではなくなることによるエラー
    • 置換失敗にする場合、まず制約でTがテンプレートであることを確認するか、制約が定数式であることを要求する言語の規定を変更する

これらのことを考慮し、例外の懸念を考慮しても、この提案では静的リフレクションにおけるエラー伝搬にはexpectedよりも例外の方がユーザーフレンドリーであるとしています。

P2999R1 Sender Algorithm Customization

P2999R2 Sender Algorithm Customization

P2999R3 Sender Algorithm Customization

P2300のsenderアルゴリズムがカスタマイズを見つける手段を修正する提案。

以前の記事を参照

R1での変更は、提案する文言を追加したこと、比較テーブルを追加したことなどです。

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

  • 早期のカスタマイズと後からのカスタマイズの例を追加
  • 早期のカスタマイズを残す理由について追記
  • connect時のドメイン計算を修正
  • senderの要件に完了schedulerが全てドメインを共有するという要件を追加
  • transform_senderは必要に応じて再帰することを明確化
  • 不要になっていた説明専用のmake-transformer-fnを削除

などです。

P3006R0 Launder less

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

バイト配列を確保して、その配列の要素型とは異なる型のオブジェクトをその配列上に構築する場合、そのオブジェクトへの正しいアクセス方法は非常に限定されています。

// Tのオブジェクトを配置するバイト配列
alignas(T) std::byte storage[sizeof(T)];

// Tのオブジェクト構築、戻り値のポインタをそのアクセスに使用する
T* ptr1 = ::new (&storage) T(); // ok

// storageのアドレスから直接Tのオブジェクトへのアドレスを得る
T* ptr2 = reinterpret_cast<T*>(&storage);  // UB
T* ptr3 = std::launder(reinterpret_cast<T*>(&storage));  // ok

この場合、配置newによってstorage上に構築されているオブジェクトへのアクセスを行えるのはptr1ptr3のみであり、storageのアドレスからreinterpret_cast<T*>しただけのptr2からのアクセスは未定義動作となります。

これはポインタとその領域で生存期間内にあるオブジェクトとが結びつかないためで、配置newの戻り値を使用しない場合はptr3のようにstd::launderによってその領域上で生存期間内にあるオブジェクトへのポインタを取得しなければなりません。

とは言えこのことはかなり意味論的なことで、多くのコンパイラptr2の様にstd::launderを使用しない場合でもstd::launderを使用する場合と同等のコードを出力する様です。

この提案は、その様な既存の慣行を標準化し、このようなユーザーの期待に沿わないUBを取り除こうとするものです。

これによるメリットは、現在UBを回避するために配置newの戻り値を保存している様なコード(例えば、Boost.Optionalなど)において、その様なポインタを保存しなくてもよくなることでストレージサイズを削減でき、なおかつstd::launderというよく分からないものを使用せずとも直感的なコードによってUBを回避して意図した動作を実現できる点です。

P3007R0 Return object semantics in postconditions

事後条件の契約注釈で戻り値を参照する場合の、その戻り値のセマンティクスに関する提案。

事後条件注釈では、その関数の戻り値を使用するために戻り値の名前を指定してそれを参照することができます。

int f()
  post (r: r > 0);  // 戻り値は正の値

現在のMVP仕様では、まだこの戻り値に関するセマンティクスが正確に指定されていません。

C++20時点の仕様及びそれを受け継いだMVP仕様においては、「事後条件では、その関数のglvalueもしくはprvalueの結果オブジェクトを表す識別子を導入できる」とだけ言及されていて、その値カテゴリは何か、それは参照なのか、その型は何か、アドレスを取れるのか、変更可能なのか、などについての規定はありません。

この提案は、事後条件の戻り値のセマンティクスについての現在の不明な点について考察し、そのセマンティクスを決定しようとするものです。ここで提案されていることは次のような事です

  • 事後条件における戻り値を表す変数名は、本物の戻り値オブジェクトを参照する左辺値である、とする
    • これは構造化束縛によって導入される名前のセマンティクスからの借用
    • その値カテゴリは左辺値(lvalue)
    • 言語参照(T&)ではないが、戻り値を参照している
    • 従って、事後条件から戻り値を参照する場合にはコピーもムーブも行われず、RVOを妨げない
  • 戻り値名rに対するdecltype(r)の結果は、関数の戻り値型
  • 戻り値型がtrivially copyableである場合、戻り値名のアドレスと実際の戻り値のアドレスは異なる可能性がある
    • これは、trivially copyableオブジェクトをレジスタに配置して返す挙動を変化させない(ABI破壊を回避する)ため
    • その場合、戻り値名は戻り値を保持する一時オブジェクトを参照している
  • 呼び出し側で戻り値を受ける変数がconstである場合、関数の戻り値型がconstでなければ、事後条件における戻り値の変更は未定義動作とならない、とする
    • 変数に対するconstは初期化が完了するまで有効ではなく、事後条件は戻り値を受ける変数の初期化よりも前に呼び出される

この提案では、事後条件における戻り値名が暗黙constであるかは著者の間で合意できなかったことから提案していません。それには利点と欠点がありますがどちらを採用するべきかは明確ではないため、その方向性の決定は委員会に委ねています。

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

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

以前の記事を参照

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

などです。

P3019R1 Vocabulary Types for Composite Class Design

P3019R2 Vocabulary Types for Composite Class Design

P3019R3 Vocabulary Types for Composite Class Design

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

以前の記事を参照

R1での変更は

  • 機能テストマクロの追加
  • std::indirectstd::formatサポートを追加
  • 使用前後の比較サンプルをまとめたAppendix Bを追加
  • 型が値を持つことを事前条件として追加
  • constexprサポートを追加
  • std::polymorphicのQoIとしてsmall buffer optimizationを許可
  • アロケータサポートのために文言を追加
  • 不完全型のサポートを有効化
  • pointer入れ子型はallocator_traits::pointerを使用する様に変更
  • std::uses_allocator特殊化を削除
  • std::indirectコンストラクタのinplace_tを削除
  • sizeofエラーを削除

R2での変更は

  • std::indirect比較演算子の戻り値型がautoであることについての議論を追加
  • emplace()の議論をappendixに追加
  • allocator awarenessサポートのために文言調整

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

  • コンストラクタにexplicitを追加
  • indirect(U&& u, Us&&... us)コンストラクオーバーロードと制約を追加
  • polymorphic(allocator_arg_t, const Allocator& alloc)コンストラクオーバーロードを追加
  • std::variantとの類似/相違点についての議論を追加
  • 破壊的変更とそうではない変更の表を追加
  • 不足している比較演算子を追加し、それらが条件付きnoexceptであることを確認
  • std::indirectの推論補助を修正
  • 複雑な例におけるstd::indirectの間違った使用例を修正
  • swap()noexceptに関する文言を修正
  • std::indirectの比較演算子の制約に関する文言についての問題を解決
  • コピーコンストラクタはallocator_traits::select_on_container_copy_constructionを使用する様にした
  • 自己swapと自己代入が問題とならないことを確認
  • std::optional特殊化を削除
  • erroneousの使用をundefined behaviourで置き換え
  • コピー代入における強い例外保証を追加
  • コンストラクタにおいて、Tのuses-allocator構築を行う様に指定
  • 文言の見直し

などです。

P3022R1 A Boring Thread Attributes Interface

std::thread/std::jthreadに対してスタックサイズとスレッド名を実行開始前に設定できるようにするAPIの提案。

以前の記事を参照

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

  • P2019R4の変更に追随
  • 文言と例を追加
  • Conclusionセクションを追加

などです。

この提案の方向性はLEWGでの指示が得られなかったため、追及は停止されています。

P3023R1 C++ Should Be C++

C++標準化委員会の目標・見通しについて問い直す文章。

以前の記事を参照

R0が主張をリストアップしたものだったのに対して、こちらはそれを文章にまとめた形になっている完成版です。

P3024R0 Interface Directions for std::simd

C++26に向けて提案中のstd::simdのインターフェースやその設計動機などについて紹介するスライド。

P3025R0 SG14: Low Latency/Games/Embedded/Financial trading/Simulation virtual Minutes to 2023/09/12

SG14の2023年9月12日に行われたオンラインミーティングの議事録。

P3026R0 SG19: Machine Learning virtual Meeting Minutes to 2023/07/13

SG19の2023年7月13日に行われたオンラインミーティングの議事録。

P3027R0 UFCS is a breaking change, of the absolutely worst kind

P3021のUFCS(Unified function call syntax)提案に異議を唱える提案。

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

P3021では、メンバ関数呼び出しを拡張する形でUFCSを有効化することを提案しており、メンバ関数呼び出し(x.f(a,b)x->f(a, b))を非メンバ関数の呼び出し(f(x, a, b)f(*x, a, b))にフォールバックすることでUFCSを可能にするものです。

P3021のUFCSは確かに既存のコードを壊しません(有効なコードがill-formedにならない)が、現在のメンバ関数呼び出しに関してユーザーが依存している保証を壊している、というのがこの提案の主張です。

その保証とは、メンバ関数呼び出しはADLとは無関係に行われるというものであり、別の言い方をするとメンバ関数呼び出しではADLは行われないという保証です。このため、確かに現在ある既存のコードが壊れることはありませんが、将来のコードは壊れる可能性があります。

例えばリファクタリングにおいて誰もが経験すると思われる、メンバ関数の名前を変更する場合を考えます。

struct Foo {
  // この関数名を変えたい
  void snap();
};

// この.snap()は例えばこのように呼び出されている
template <class T>
void do_snap(T&& f) {
  f.snap(); 
}

このFoo::snap()の名前をslap()に変更しようとする場合、これを宣言・定義しているファイルを編集し名前を変更してから使用されているところを修正していけば良いでしょう。修正を忘れたり、呼び出しを間違えればコンパイラコンパイルエラーとして修正が完全ではないことを教えてくれます。これは静的型付け言語の基本中の基本振る舞いであり、コードベースが大きくなったとしてもそれは変化しません。

しかしP3021が有効化された場合そのような保証はもはや期待できず、snap()slap()に変更してなにも修正しなかったとしても一切エラーが出ない可能性があります。なぜなら、ADLによってslap()を探しに行き、たまたま呼び出せる候補を見つけてしまうかもしれないからです。P3021が導入される以前はメンバ関数呼び出しからADLが発動されることはなく、ADLによって呼び出せてしまうようなslap()を定義していたとしても安全でした。しかし、P3021導入後はそのような関数がメンバ関数呼び出しからのADLによって呼び出されることを意図していたかどうかは分からなくなります(P3021以前の保証の上に立っているのか、P3021の機能の上に立っているのかが分からなくなるため)。

これはまた、メンバ関数を削除する場合や、関数の引数型を変えるなどのリファクタリングにおいても同様です。

本来であればメンバ関数呼び出しではそのようなADLの複雑さを考えなくても良いはずで、P2855のようにそれを有効に利用しようとする向きもあります。しかし、P3021の機能が導入されるとそのような保証はなくなり、メンバ関数呼び出しは常に意図しない関数呼び出しによってバグを静かに埋め込んでしまう可能性を抱えることになります。

意図的かどうかはともかく、現在の多くのC++ユーザーはメンバ関数呼び出しを使用することでクラスのメンバ関数のみが呼び出され、ADLの複雑さについて頭を悩ませることを回避しています。メンバ関数はクラスという単位でのカプセル化によって、そのアクセス範囲及び呼び出される方法はかなり明確です。

メンバ関数呼び出し構文がそのようなカプセルの外側に及ぶようにするというのは、その単純さと分かりやすさを壊しています。それはユーザーへのサービスではなく、ユーザーに対して破滅的な不利益を与えるものです。

このような理由によりこの提案はP3021のUFCSに反対し、UFCSが必要ならば専用の構文を導入して行うべきであり、既存の関数呼び出し構文を拡張する形でそれを行えば以前にはその複雑さが無かった場所に新しい複雑さを導入してしまい、UFCSによる利点を複雑さの増大による欠点が上回るとしています。

P3028R0 An Overview of Syntax Choices for Contracts

2つの契約構文候補を比較する提案。

C++26契約プログラミング機能に向けて議論が進んでおり、残す大きな問題は構文の決定のみとなっています。現在契約構文としてはC++20の契約機能由来の属性様構文(P2935)と新しい文脈依存キーワードによる自然な構文(P2961R2)の2つが候補として残っています。

P2695で示されたロードマップに従って、2023年11月のKona会議ではこのどちらの構文を採用するのかを決定する予定です。

この提案はその議論のために、2つの構文候補を比較することでそれぞれの特性を明らかにしようとするものです。

この提案では次の2つの観点から候補を比較しています

  1. 提案されている全ての構文候補を、契約のある機能についてそれぞれ対応する構文で記述したものを横に並べて比較
  2. P2885で挙げられている原則とともに、各構文候補がその原則をどの様に満たすのか(あるいは満たさないのか)を比較

事前条件構文の比較

// P2935
void f() [[ pre : true ]];

// P2961
void f() pre( true );

事後条件構文の比較

// P2935
int f() [[ post r : true ]];
int f() [[ post (r) : true ]];
// P2935R4の代替提案
int f() [[ post : r : true ]];

// P2961
int f() post( r : true );

アサーション構文の比較

// P2935
void f() {
  [[ assert : true ]];
}

// P2961
void f() {
  contract_assert( true );
}

かかる文字数の比較

P2935 P2961
事前条件 8 5
事後条件 9〜10 6〜7
アサーション 11 17

これ以外にも様々な観点からの比較が行われています。

2023年11月に行われたKona会議では、P2961の自然な構文をC++契約プログラミング機能のための構文として採用することで合意されました。

P3029R0 Better mdspan's CTAD

std::span/std::mdspanコンパイル時定数によってインデックス指定を受ける場合のCTADを改善する提案。

たとえば、mdspanでよく使用されることになると思われる配列ポインタとそれを参照する多次元インデックスの指定を受けるコンストラクタに対応する推論補助は次の様に定義されています

template<class ElementType, class... Integrals>
  requires((is_convertible_v<Integrals, size_t> && ...) &&
            sizeof...(Integrals) > 0)
explicit mdspan(ElementType*, Integrals...)
  -> mdspan<ElementType, dextents<size_t, sizeof...(Integrals)>>;

このため、このコンストラクタ(CTAD)が使用される場合にはインデックスにコンパイル時定数を渡していてもmdspanのエクステントはdextent(実行時エクステント)が使用されます。

mdspan ms (p, 3, 4, 5); // mdspan<int, extents<size_t, dynamic_extent, dynamic_extent, dynamic_extent>>
mdspan ms2(p, 3, integral_constant<size_t, 4>{}, 5);                              // 同上
mdspan ms3(p, integral_constant<size_t, 3>{}, 4, integral_constant<size_t, 5>{}); // 同上

後2つについてはコンパイル時定数を渡しているため、エクステントのその部分は静的になってほしいものがあります。現在これを叶えるためには、ユーザーは次の様に記述する必要があります

mdspan ms2(p, extents<size_t, dynamic_extent, 4, dynamic_extent>(3, 5)); // mdspan<int, extents<size_t, dynamic_extent, 4, dynamic_extent>>
mdspan ms3(p, extents<size_t, 3, dynamic_extent, 5>(4));                 // mdspan<int, extents<size_t, 3, dynamic_extent, 5>>

最初の例の後ろ2つが自動的にこれと同等になることが望ましいでしょう。また、std::spanにも同様の問題があります。

この提案は、std::mdspan及びstd::spanの推論補助を修正して、動的インデックス指定がコンパイル時定数(std::integral_constantのような型の値)によって指定されている場合にそれを静的な指定としてエクステントに反映する様にしようとするものです。

これによる利点は次の様なものが挙げられています

  • 動的・静的エクステントを同等な形式によって指定できるため、エクステント型の反映が直感的になる
    • 現在、動的エクステントは数値で直接コンストラクタに指定できるのに対して、静的エクステントはstd::extents<I, idx...>{...}の様に指定する必要がある
  • 正しい数の引数を渡すためにdynamic_extentsの数を計算する必要がなくなり、エラーが起こりにくくなる
    • extents<size_t, dynamic_extent, 4, dynamic_extent>(3, 5)の様に、静的エクステント中に動的エクステントが混ざっている場合にextentsのコンストラクタでその要素数を指定しなければならない
  • P2781のstd::constexpr_vを使用すると、エクステントが混在するmdspanmdspan(c_<3>, 4, c_<5>)の様に記述できる様になる。

この様なことはstd::submdspanC++26に導入済)では既に行われており、その仕組みを再利用することで実装可能です。

まず次の様な検出ユーティリティを用意して(integral-constant-likestd::submdspanとともに導入済)

// std::integral_constantと同等の型を検出する
template<class T>
concept integral-constant-like =        // exposition only
  is_integral_v<decltype(T::value)> &&
  !is_same_v<bool, remove_const_t<decltype(T::value)>> &&
  convertible_to<T, decltype(T::value)> &&
  equality_comparable_with<T, decltype(T::value)> &&
  bool_constant<T() == T::value>::value &&
  bool_constant<static_cast<decltype(T::value)>(T()) == T::value>::value;


template<class T>
constexpr size_t maybe-static-ext = dynamic_extent;        // exposition only

template<integral-constant-like T>
constexpr size_t maybe-static-ext<T> = static_cast<size_t>(T::value);

これを用いてstd::mdspan及びstd::spanの既存の動的エクステント指定に対応する推論補助を修正します

// 変更前
template<class It, class EndOrSize>
span(It, EndOrSize) -> span<remove_reference_t<iter_reference_t<It>>>;

// 変更後
template<class It, class EndOrSize>
span(It, EndOrSize) -> span<remove_reference_t<iter_reference_t<It>>, maybe-static-ext<EndOrSize>>;
//                                                                  ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^


// 変更前
template<class ElementType, class... Integrals>
  requires((is_convertible_v<Integrals, size_t> && ...) && sizeof...(Integrals) > 0)
explicit mdspan(ElementType*, Integrals...)
  -> mdspan<ElementType, dextents<size_t, sizeof...(Integrals)>>;

// 変更後
template<class ElementType, class... Integrals>
  requires((is_convertible_v<Integrals, size_t> && ...) && sizeof...(Integrals) > 0)
explicit mdspan(ElementType*, Integrals...)
  -> mdspan<ElementType, dextents<size_t, maybe-static-ext<Integrals>...>;
//                                        ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

P3031R0 Resolve CWG2561: conversion function for lambda with explicit object parameter

ジェネリックthisパラメータを持つラムダ式の関数ポインタへの変換の規定が曖昧なのを解決する提案。

C++23のDeducing thisはクラスのメンバ関数thisパラメータを明示的に書くことができる機能です(この引数の事を明示的オブジェクトパラメータと呼びます)。これはラムダ式においても使用することができますが、ラムダ式の場合はそのクロージャ型名を知ることができないので、通常this autoのように書かれます。

また、ラムダ式はキャプチャしていない場合にのみ関数呼び出し演算子シグネチャと互換性のある関数ポインタへ変換することができます。

このとき、明示的オブジェクトパラメータを持つラムダ式が対応する関数ポインタへ変換可能であるかについて実装間で挙動に差があります。

int main() {
  using T1 = decltype([](int x) { return x + 1; });  // ok、ラムダ式のクロージャ型
  int(*fp1)(int) = +T1{};  // ok、関数ポインタへの変換

  using T2 = decltype([](this auto, int x) { return x + 1; });  // ok、ラムダ式のクロージャ型
  int(*fp2)(int) = +T2{};  // Clangはng、MSVCはok
}

1つ目の例は通常のラムダ式から関数ポインタへ変換する例です。これはC++11以来のもので今回特に問題はありません。

2つ目の例がこの提案の主要な問題であり、ジェネリックな明示的オブジェクトパラメータを持つラムダ式が対応する関数ポインタへ変換できるかどうかが、現時点でこの機能を実装しているClangとMSVCの間で異なっているようです。

また、ラムダの明示的オブジェクトパラメータがジェネリックではなかったとしてもClangは拒否するようで、どうやら規格ではこの場合に生成されるクロージャ型のメンバがどうなるかについて曖昧なようです。

struct Any { Any(auto) { puts("called"); } };

int main() {
  auto a1 = [](int x) { return x+1; };
  auto a2 = [](this Any self, int x) { return x+1; }; // 明示的オブジェクトパラメータはジェネリックではない
  
  int(*fp)(int) = +a2;  // Clangはng、MSVCはok
}

このa1, a2それぞれのクロージャ型は例えば次のようになります

// 型変換演算子で複雑な型を使用するためのエイリアス
template<class T>
using Just = T;

// a1のクロージャ型の例
struct A1 {
  int operator()(int x) const { return x+1; }
  operator Just<int(*)(int)>() const { return +[](int x) { return A1()(x); }; }
};

// MSVCにおけるa2のクロージャ型の例
struct A2_MSVC {
  int operator()(this Any self, int x) { return x+1; }
  operator Just<int(*)(int)>() const { return +[](int x) { return A2_MSVC()(x); }; }
};

// Clangにおけるa2のクロージャ型の例
struct A2_Clang {
  int operator()(this Any self, int x) { return x+1; }
  operator Just<int(*)(Any, int)>() const { return &A2_Clang::operator(); }
};

Clangにおいては明示的オブジェクトパラメータの部分の引数についても関数ポインタ型へ現れるようです。一方、MSVCは明示的オブジェクトパラメータは関数ポインタに現れません。

MSVCの挙動は、従来の暗黙的オブジェクトパラメータを持つラムダを明示的オブジェクトパラメータを持つものにそのままリファクタリングでき(その逆も可能)たり、自身の型を関与させない形で再帰ラムダを定義できたりと、よりユーザーフレンドリーであると思われます。

// Error on Clang, OK on MSVC
auto fib = [](this int (*fib)(int), int x) {
  return x < 2 ? x : fib(x-1) + fib(x-2);
};
int i = fib(5);

この例は、Clangの場合はthisパラメータの型に自身の型が再帰的に現れるのを回避することができませんが、MSVCはこのように関数ポインタによってその再帰を断ち切ることができます。これは再帰ラムダを非ジェネリックに定義できるため、コンパイル時間で有利になるかもしれません。

ラムダ式の関数ポインタへの変換演算子に関して、MSVCはラムダ式における明示的オブジェクトパラメータをかなり特別扱いしており、その引数は常にそのラムダ式自身と同等とみなせると強く仮定しています。ラムダ式が関数ポインタへ変換可能であるのはキャプチャしていない場合のみなので、これは実際にはあまり問題にならないかもしれません。

一方で、this autoを用いずにテンプレート構文によって明示的オブジェクトパラメータを記述するとMSVCでもClangと同様なクロージャ型を生成するようです。

auto a = [](this auto) {}; // MSVCは非ジェネリックとして扱う
auto b = []<class T>(this T) {}; // ジェネリックラムダ

auto pa = +a; // OK on MSVC
auto pb = +b; // error on MSVC
void (*qa)() = a; // OK on MSVC
void (*qb)() = b; // error on MSVC

この非一貫性は非自明ではあります。

この問題はCWG Issue 2561で捕捉され、当初はClangのアプローチを標準化する方向性でした。この提案はそれに対してMSVCのアプローチの方を推すものでしたが、それには文言についてさらなる検討が必要になるということで、明示的オブジェクトパラメータを持つ関数の関数ポインタへの変換をとりあえず禁止しておくことを提案するものです(このR0が出る前に6つのリビジョンがあった様子)。

この提案には、明示的オブジェクトパラメータを持つラムダ式の関数ポインタへの変換を禁止する、Clangのアプローチを採用、MSVCのアプローチを採用、の3つのオプションが含まれており、EWGは1つ目のアプローチを採用したようです。ただし、これはMSVCのアプローチを将来的に採用することを妨げるものではありません。

P3033R0 Should we import function bodies to get the better optimizations?

モジュールのインターフェースにある関数定義をそのモジュールをインポートした翻訳単位にインポートしないようにする提案。

Clangの最適化においては、あるモジュールからインポートされた関数について、その関数の定義をインライン化するような最適化をリンク前に行っているようです。

// a.cppm
export module a;
export int a() { return 43; }

// use.cpp
import a;
int use() { return a(); }

たとえば、最適化を有効にするとuse.cppuse()int use() { return 43; }であるかのようにコンパイルされます。

これはゼローバーヘッド原則に則っており、一見合理的であるように見えます。しかし、use.cppコンパイルする時にオプティマイザはモジュール内のa()に対しても作用してしまうため、プロジェクトの規模が大きくなるとコンパイル時間に跳ね返ってきます。

とはいえそれでも、この問題は単に実装の問題であり規格が口を出す話ではないように思えます。しかし、実際にはABI依存関係と潜在的なODR違反に関連しています。

例えば上記コード群が次のようなコンパイル結果を生成していた場合

a.o       // モジュールaのオブジェクトファイル
a.pcm     // モジュールaのBMI
use.o     // use.cppのオブジェクトファイル
libuse.so // use.cppを含む共有ライブラリ

この時にモジュールa内のa()44を返すように変更して再コンパイルした場合、再コンパイルが発生して再生成されるのはどのファイルでしょうか?これには2つのオプションがあります

rebuild a.o
rebuild a.pcm
rebuild use.o
link libuse.so

もしくは

rebuild a.o
rebuild a.pcm
link libuse.so

この2つの違いはuse.cppが再コンパイルされるかどうかだけです。モジュールの位置づけを考えた場合は再コンパイルされないのが正しい振る舞いにも思えますが、その場合インライン化されているa()の定義の変更が反映されません。 再コンパイルする場合は実装の一貫性は保たれますが、コンパイル時間が増大します。あるいは、最適化を有効にしている場合にのみABI依存関係(現在一般的ではない)に基づいて再コンパイルを行うべきでしょうか?

この提案ではこの問題への対処として、モジュール本文内の非inline関数本体を変更する場合、対応するモジュールインターフェースのBMIを変更するべきではない、とすることを提案するものです。

すなわち、ユーザーがモジュールインターフェースユニット(の本文)内の非インライン関数の本体のみを変更したプロジェクトを再コンパイルする場合、再コンパイルされるのは変更されたモジュールインターフェースのみであり(そのBMIすらも再コンパイルするべきではなく)、他のすべてのコンパイルは行われるべきではありません。ただし、リンクを除きます。

提案では、これによりユーザーエクスペリエンスが向上するはずとしています。

ビルドシステムのレベルでは、ビルドシステムが再コンパイルが必要かを決定する依存関係はモジュールのソースではなくモジュールのBMIに依存するように実装することでこれが実現できます。そして、コンパイラBMIにはモジュールからエクスポートされているインターフェースのみを保存しておき、その定義を保存しないようにする必要があります。GCC/MSVCは現在そのようにしていますが、Clangは2フェーズコンパイルモデルを実行する都合上そのようになっていないようです。

実行時のパフォーマンスについても、ヘッダファイルベースのライブラリをモジュールに移行することを考えた場合、ヘッダファイル内の関数はほぼインライン関数であるためこの提案の制約に接触せず、パフォーマンスの低下が発生する場合は限定されるとしています。また、LTOを使用することで翻訳単位を跨いだ定義のインライン化のような最適化が可能となるため、問題とされている最適化が全く利用できなくなるわけではありません。

P3034R0 Module Declarations Shouldn't be Macros

名前付きモジュールのモジュール宣言において、モジュール名のマクロ展開を禁止する提案。

モジュールのソースファイル形式は他のものと区別されておらず、あるファイルがモジュールソースであるかは、そのファイルの先頭にモジュール宣言があるかどうかによって決まります。モジュール宣言およびグローバルモジュールフラグメントの宣言はプリプロセッサによって導入することができませんが、モジュール名はマクロの展開によって指定することができます。

例えば、次の様なコードはC++20時点で有効です

// version.h
#ifndef VERSION_H
#define VERSION_H

#define VERSION libv5

#endif


// lib.cppm
module;
#include "version.h"
export module VERSION;

あるソースファイルがモジュールファイルであり、かつそのモジュール名が何であるかを知ることは、ソースの依存関係を知るために必要な作業です。従来のヘッダインクルードであれば、その依存関係を知らなくてもビルドを行うことができますが、モジュールの場合はビルドにあたってその依存関係を把握しソースのビルド順を決定する必要があります。これを行うのはコンパイラではなくビルドシステムの仕事であるため、ビルドシステムはソースファイルを読み込みそれがモジュールであるか、モジュールの場合はその名前は何かを読み取る必要があるかもしれません(これを行わなくてもいい方法がいくつか考案されていますが、まだ標準化等されてはいません)。

モジュール宣言はプリプロセッサによって導入されないものの、モジュール名はマクロの展開を完了させた上で読み取らなければなりません。そのためには、上記例のようにグローバルモジュールフラグメント内のインクルードやマクロ定義を読み込んだ上でマクロの展開を行わなければなりません。その作業は実装も処理も簡単なものとは言えず、ビルドシステムの実装および処理時間にかなりの負担になります。

この様な理由から、この提案はモジュール名もマクロによって導入できない様にする提案です。

その目的はモジュールファイルのパースを簡単にすることでビルドシステムがモジュール名パースを実装しやすくすることにあります。また、ビルドの前にモジュールの依存関係解決フェーズを行う場合に、パース処理が単純化されることで依存関係解決フェーズの遅延時間を短くすることもできます。

ただし、この変更はC++20への破壊的変更になります。提案では、モジュールの実装はまだ出揃っておらず使用例も稀であるため、影響は最小限である、としています。

この提案はSG15でもEWGでもほぼ反対なく支持されたようで、EWGではこの提案をC++20へのDRとするつもりの様です。

P3037R0 constexpr std::shared_ptr

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

C++20で定数式における動的メモリ確保が可能になり、C++23でstd::unique_ptrconstexpr対応され定数式で使用できる様になりました。

スマートポインタは実行時と同様に定数式においてもメモリ管理を自動化することができます。しかし、std::shared_ptrはその実装に必要な言語機能の一部が定数式で使用可能ではなかったためすぐにconstexpr対応することができませんでした。

C++23におけるP2738R1(void*からの正しいポインタキャストの許可)とP2448R2(定数式で実行不可能なものは評価されるまではエラーにならない)の採択によりその障害は取り除かれており、この提案はそれを受けてC++26に向けてstd::shared_ptrconstexpr対応しようとするものです。

筆者の方はlibstdc++のstd::shared_ptr実装をベースとして実装を試みており、アトミック操作の使用を回避の必要性やstd::make_shared()などの行う1回のメモリ確保による初期化の問題などを報告していますが、いずれも回避は可能であり実装可能であるとしています。

また、この提案ではさらに、C++23ではconstexprで実装できなかったため外されていたstd::unique_ptrの比較演算子に対してもconstexprを付加することも提案しています。

P3038R0 Concrete suggestions for initial Profiles

既存のC++コードの一部により強力な保証を付加するためのプロファイルについての提案。

この提案は、P2687で提案されていたアイデアについて、より具体的な最初の機能について説明するものです。

プロファイルはC++コード上でユーザーによって指定されるもので、スコープもしくはモジュールに対して付加することができます。

// モジュール宣言にmemory_safetyプロファイルを適用
export module DataType.Array [[enforce(memory_safety)]];

// 名前空間宣言にプロファイルを適用
namespace N [[enforce(p)]] {
  ...
}

[[enforce(p)]]はプロファイルpをそのスコープに対して適用するもので、そのスコープの内側にあるコードに対してプロファイルpの保証が強制されます。モジュールの場合の適用範囲は、そのモジュール本文の全体です。

// モジュールMに対してプロファイルPを適用
import M [[enable(P)]];

// モジュールoldでtype_safetyプロファイルを無効化
import old [[suppress(type_safety)]];

プロファイルは既存コードに付加して保証を強化するものであり、[[enable(P)]]によって特にプロファイルを使用していないモジュールのインポート時にプロファイルを適用することができます。また、プロファイルはスコープに対して指定されある程度広い領域でその保証が強制されるため、[[suppress(P)]]によって部分的にプロファイルを無効化することもできます。

想定されるプロファイルにはいくつかの種類が考えられますが、この提案では実装負担の軽減のために最初の小さいものとしてtype_safetyプロファイルに焦点を当てています。与えられる保証は例えば

  • 変数初期化の強制
    • [[uninitilize]]とマークされない変数には初期化が必要
  • ポインタの利用の制限
    • ポインタは単一要素を指すか、nullptrであるかのどちらか
      • ポインタによる範囲のランダムアクセス禁止、その用途にはspanvectorを使用する
    • ownerとマークされていない限り、ポインタは所有権を持たない
      • ownerはポインタの先のオブジェクトを破棄する責任を負う
      • owner以外のポインタに対してnew/deleteできない
    • nullptrチェックなしのポインタアクセスの禁止
  • ダングリングポインタ(参照)の抑止
    • ポインタ(参照)はオブジェクトを指すか、nullptrのどちらか
    • ownerではないポインタはdeleteできない
    • 生存期間が不明なポインタを外側のスコープに漏出できない
    • returnできるポインタを制限する
  • ポインタ(参照)の無効化の防止
    • const参照によってコンテナを取得する関数では参照の無効化が発生する可能性があり、const参照によってコンテナを取得する関数では参照の無効化が発生しないと仮定
    • const参照によってコンテナを取得するがコンテナを変更しない関数では[[not_invalidating]]アノテーションによってそれを表明する
      • 間違った[[not_invalidating]]の利用は検出できるはずで、エラーにする

提案文書より、例

void f1() {
  int n;  // error

  [[uninitialized]]
  int m;  // ok
}

void f2(int* p1, owner<int*> p2) {
  delete p1; // error、ownerでは無いポインタをdeleteいている
  delete p2; // p2はdeleteしないとエラー
}

void f3(int* p1, owner<int*> p2) {
  p1=p2; // OK、p1はownerではないが、p2と同じオブジェクトを指す
  p2=p1; // error、p2は上書きされる前にdeleteされなければならない
}

int* glob = nullptr;
void f4(int* p) {
  glob = p; // error、不明な生存期間のポインタを保存しようとしている
}

int glob2 = 0;
int* f5(int* p) {
  int x = 4;
  return &x;          // error: ローカルオブジェクトへのポインタを返そうとしている
  return p;           // OK: pは関数呼び出し時に有効であり、無効化されていない
  return new int{7};  // error, ownerポインタを非ownerで返そうとしている
  return &glob2;       // OK 静的オブジェクトへのポインタ
  throw p;            // error: pを*pのスコープ外に漏出しうる
}

void f6(vector<int>& vi) {
  vi.push_back(9); // 要素の再配置が発生しうる
}

void f7() {
  vector<int> vi { 1,2 };
  auto p = vi.begin(); // viの最初の要素を指すイテレータ
  f6(vi); // 参照を無効化しうる関数呼び出し
  *p = 7; // error、参照が無効化されている可能性がある
}

提案では、このtype_safetyプロファイルに加えて、vector等の範囲に対するアクセスの境界チェックを行う実行時検査を伴うプロファイルであるrangesプロファイルや、組み込み数値演算の安全性向上(オーバーフロー防止、縮小変換・符号変換の禁止など)のためのプロファイルであるarithmeticプロファルなどを初期のプロファイルの候補として挙げています。

このようなプロファイルに基づく保証の提供はC++ Core Guidelineおよびそのチェッカー実装とガインドラインサポートライブラリの経験から来ています。それはあくまで静的解析としてC++コンパイルとは別でチェックされることでしたが、プロファイルとしてその保証をC++のコードに対して取り込むことで、既存のC++コードの上に被せる形でC++コードの安全性を高めることができ、プロファイルの指定は小さななスコープから始めることができます。

この提案のプロファイルとその静的な検査については、コアガイドラインチェッカーにて現在利用できるものであり、実装可能であることが確かめられています。また、この提案による安全性の静的検査は、コンパイラに強力なフロー解析などを強いるものではなく、危険を招く可能性のある操作を制限することで抑止するとともに、コンパイラの静的解析にいくつかの仮定を与えることで解析を補助する事を目指す物です。

P3039R0 Automatically Generate operator->

<=>演算子の様な書き換えによってoperator->を導出する提案。

この提案では、->->*演算子オーバーロード解決時に書き換えて実行することでこの2つの演算子を自動で導出できる様にすることを提案しています。それぞれ次の様になります

  • lhs->rhs(*lhs).rhsに書き換えて実行
  • lhs->*rhs(*lhs).*rhsに書き換えて実行

ライブラリソリューション(Boost.Operatorsのような)でこれと同じことを行おうとする場合、*lhsがprvalueを返す場合(例えばプロクシイテレータなど)に一時オブジェクトの寿命が->の定義内で尽きてしまうことによって未定義動作が発生する問題が回避できません。しかし、言語機能による演算子の書き換えはその様な問題を回避することができます(その場でインラインに置換される形になるので、->の呼び出しコンテキストと書き換え後の*lhsの生存コンテキストは一致する)。

また、比較演算子の場合は逆順の演算子や細かいコーナーケースを処理するためにその書き換えルールが複雑になっていますが、->->*はどちらも逆順を考慮する必要がなく、->はクラス内でのみ定義でき右辺のオペランドオーバーロード解決とは無関係となるため、書き換えに伴う仕様はかなりシンプルになります。

どちらの演算子でも、まずは->/->*として定義されたもの(delete含む)を優先して選択し、それが見つからずoperator*が利用可能である場合にのみ書き換えた候補を使用します。->を定義したいクラス側でdefault宣言しておく必要はなく、書き換えによって導出されたくない場合はdelete宣言をしておくことで書き換えを抑止できます。

これによるメリットは、主にイテレータ定義時の->に関する記述をほぼ完全に削除することができる点です。

提案では、<=>にならってこの提案が採択された場合に既存の->定義を削除するオプションについて検討されており、そこではC++20時点の標準ライブラリで->を持つクラスにおける定義のされ方を調査しています。それによれば、スマートポインタやstd::optional、コンテナのイテレータ型や一部のRangeアダプタのイテレータ型など、多数のクラス型において->定義を削除することができることが示されています。

ただし、std::iterator_traitspointerメンバ型の定義や、std::to_addressstd::pointer_traitsなどその動作について->演算子の存在に依存している部分があるライブラリ機能について、この提案の影響を回避する様にしなければなりません。それについてはいくつか方法が提示されているものの未解決です。

P3040R0 C++ Standard Library Ready Issues to be moved in Kona, Nov. 2023

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

P3041R0 Transitioning from "#include" World to Modules

ヘッダファイルによるライブラリをモジュールベース変換する際の実装戦略についての報告書。

標準ライブラリモジュールstdと標準ヘッダファイルは同時にインポート/インクルードしたとしてもODR違反等を起こさず一貫して使用可能であることが規定されています。

#include <vector>
import std;

int main() {
  // vectorおよびそのメンバ関数実体は曖昧にならない
  std::vector<int> vee { 1, 2, 3, 4, 5 };
  return vee.size();
}

これは、通常のユーザーが定義できる名前付きモジュールでは得られない保証です。

ヘッダインクルードによるエンティティはグローバルモジュールに属しており、モジュールのエンティティは名前付きモジュールという翻訳単位に属しています。この2つのものは例え同じ名前で定義されていたとしても異なるものとして扱われるため、上記の様なコードをユーザー定義ライブラリでやると意図通りになるか曖昧になるかは実装次第となります。しかし標準ライブラリモジュールとヘッダファイルに関しては、これが確実に動作する(ヘッダファイルとstdモジュールとの対応する名前は同じ1つのエンティティを参照する)ことが規定され、要求されています。

これは、標準ヘッダと標準モジュールの両方を適用する必要がある現状においても両方を自然に同居させるための要求ですが、もしこの様な保証をユーザー定義の名前付きモジュールに対しても与えることができれば、ヘッダファイルとモジュールを同時に提供するライブラリの実装が可能になり、ヘッダからモジュールへの移行を促進することができます。

また、上記の様な標準ライブラリの保証を実現する実装戦略は、グローバルモジュール(ヘッダファイル)のエンティティに対してstdモジュールのエンティティを対応づけるような形になる様ですが、これは名前付きモジュールのいくつかの利点を犠牲にしています。

この報告書は、ヘッダとモジュールを同時に提供しながら名前付きモジュールの利点を余すところなく享受し、なおかつそれを任意のC++ライブラリで利用可能にする実装戦略について説明するものです。

この戦略は、ビルド定義とコンパイラが連携してBMIマッピング#include変換を組み合わせることで、現在のstdモジュールの保証を実現するものです。標準ライブラリヘッダに関しては、ビルドは次の様に行われます

  1. 標準ライブラリヘッダのインクルードをヘッダユニットのインポートへ変換
  2. 全ての標準ヘッダユニットに対して、stdモジュールのBMIを使用する様にコンパイラへ指示する
  3. 標準マクロを強制的にインクルードする

マクロに関してはこの方法では導入できないため、別途(コマンドライン等から)インクルードする必要があります。この文書では、C互換ではない標準ヘッダが提供する必要のあるマクロをまとめたヘッダファイルを用意しそれをインクルードする事を推奨しています。

この実装戦略は標準モジュールに対してのものですが、より一般のC++ライブラリに対しても適用可能です。ただし、そのためには次のようなものが必要です

  1. あるヘッダが名前付きモジュール(または別のヘッダ)に含まれていることを記述する機能
  2. ヘッダのインクルードをヘッダユニットのインポートへ変換する機能
  3. 2のヘッダユニットのBMIを包含モジュールのBMIマッピングする機能
  4. ヘッダファイルで導入されるはずのマクロを強制的にインクルードする機能

現在のところこれらの機能のいずれも非標準ライブラリに対しては提供されていません。

P3042R0 Vocabulary Types for Composite Class Design

P3019の紹介スライド。

std::indirectstd::polymorphicのモチベーションや設計要求などについて丁寧に説明されています。おそらく、提案を見るよりも分かりやすそうです。

P3043R0 Slides: Using variable template template without meta programming

変数テンプレートテンプレートの動機付けについて説明する文書。

lldにあるコードを簡略したものを整理することを例にとって、変数テンプレートテンプレート(変数テンプレートを受け取るテンプレートパラメータ)の必要性を説明しています。ただし、これ自体は何かを提案しているわけではありません。

これを可能とする提案としては例えばP2989があります。

P3046R0 Core Language Working Group "ready" Issues for the November, 2023 meeting

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

P3050R0 Optimize linalg::conjugated for noncomplex value types

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

std::linalg::conjugated()複素数配列を参照するmdspanの各要素を、その複素共役となるように変換する関数です。ただし、戻り値もmdspanで返され、変換はmdspanのアクセサポリシーを変更することで行われます。従って実際の要素は変更されず、mdspanからの要素参照時に引き当てられた要素に対して複素共役への変換を行うことで配列全体の変換を行います。

namespace std::linalg {

  // conjugated()の宣言例
  template<class ElementType,
           class Extents,
           class Layout,
           class Accessor>
  constexpr auto conjugated(mdspan<ElementType, Extents, Layout, Accessor> a);
}

std::linalg::conjugated()の現在の動作は次のようになっています

  • 入力mdspan<T, E, L, A>のアクセサ型Aconjugated_accessor<NestedAccessor>NestedAccessorは任意の他のアクセサ型)である場合、mdspan<NestedAccessor::element_type, E, L, NestedAccessor>を戻り値型として入力aの領域とレイアウトをそのまま渡して返す
  • それ以外の場合、mdspan<T, E, L, conjugated_accessor<A>>を戻り値型として入力aの領域とレイアウトをそのまま渡して返す
    • 複素共役を行うアクセサポリシーconjugated_accessorで元のアクセサをラップする

conjugated_accessor<A>はアクセサポリシーAをラップして、Aで定義されたアクセス結果に対して複素共役変換を適用して返すアクセサポリシー型です。

conjugated_accessor<A>による変換は、conj-if-needed()という説明専用の関数によって行われ、conj-if-needed(c)cに対するADLによって非メンバconj()が使用可能であればそれを使用して複素共役を取得し、それが見つからない場合はcをそのまま返します。これによって、cおよびmdspanの要素型が複素数型ではない場合はこの変換は最適化によってスキップされることが期待できます。

しかし、その呼び出し階層を削除することができたとしても、conjugated_accessor型の存在を削除することはできません。

mdspanを扱う多くのユーザーは関数等でmdspanを受け取る場合デフォルトのポリシーを使用して型を記述し、特にアクセサ型を変更する形で記述されることは稀だと思われます(すなわち、要素型Tに対してdefault_accessor<T>が専ら使用されるはず)。

そのようなユーザーは<linalg>の主機能であるBLASラッパを使用しないとしても、std::linalg::conjugated()などのユーティリティは使用することになるでしょう。そして、自身の持つmdspanstd::linalg::conjugated()に通すと、その要素型がなんであれアクセサポリシーが変更されたmdspanが得られ、デフォルトのアクセサを使用したmdspanを受け取るように定義された関数に対してそれを渡すとコンパイルエラーに遭遇するでしょう。

std::linalg::conjugated()複素数要素に対して作用するためこれは回避不可能なものであるといえるかもしれません。しかし、BLASそのものやMatlab等では、転置と共役転置(随伴)の操作は同じものとして統合されており、<linalg>でもconjugate_transposed()が用意されている他、conjugated(transposed(x))のように書かれることもあるでしょう。これらの関数に対する入力はその要素型が浮動小数点数型か複素数型かを意識せずに渡されるはずで、この場合に非複素数要素型のmdspanに対してアクセサ型の変更が行われることは驚きを伴う可能性があります。

これに対応するにはアクセサポリシー型をジェネリックにしなければならず、それはコンパイル時間の増大を招くとともに、デフォルトアクセサを仮定する最適化を行えなくなることを意味します。

LWGにおけるP1673のレビュー中にこの問題が指摘され、そのままでも致命的な問題ではなかったためP1673はそのままレビューされC++26 WDにマージされました。この提案は、改めてこの問題を解決するために提出されました。

この提案による変更は、std::linalg::conjugated()がアクセサポリシーをconjugated_accessorに変更しようとする場合(共役の共役とならない場合)にその要素型が複素数型ではないならば入力のmdspanをそのまま返すようにします。これによって、次の2点の変更は観測可能となります

  • std::linalg::conjugated()の戻り値型は必ずしもconjugated_accessor<A>ではなくなる
  • 複素数型の要素型のmdspanに対して、戻り値型のmdspanconst element_typeを持たなくなる

これは、conjugated()の呼び出しは常にconst element_typeを持つわけではないことや、結果を他の関数に渡しているコードの呼び出しが壊れるわけではないことなどから許容されるとしています。

P3051R0 Structured Response Files

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

現在、いくつかのコンパイラはそのオプションをファイルにまとめて指定する方法を提供しています。それはよく似た方法で行われていますが、コンパイラ間で互換性はなく、相互運用が可能なものではありません。例えばファイルを渡すオプション名が異なり、ファイル形式もバラバラです。

そのような方法を標準化することで、ツールが他のツールへ(例えばビルドシステムからコンパイラへ)そのコマンドラインオプションを渡すことが容易になり、ツール間の相互運用性が向上します。また、そのような一貫した方法/フォーマットはツールに対する一貫した共通オプションのようなものを定義するための下地にもなります。

この提案は、コマンドラインオプションをまとめたファイルのフォーマットとそれを受け渡す方法について提案するものです。

提案ではファイルの形式としてJSONファイルとすることを提案しています。

そして、そのファイルにツールのオプションを記録する方法として、引数とオプションの2つのスタイルを提案しています。引数はコマンドラインオプション文字列をそのまま記録するもので、オプションは実際のコマンドラインオプションに対応するより概念的な指定となるものです。

提案より、それぞれの表現例

// 引数の例
{
  "arguments": [
    "-fPIC",
    "-O0",
    "-fno-inline",
    "-Wall",
    "-Werror",
    "-g",
    "-I\"util/include\"",
    "-c"
  ]
}

// オプションの例
{
  "options": [
    "fPIC",
    { "O": "0" },
    "fno-inline",
    { "W": [ "all", "error" ] },
    "g",
    { "I": [ "util/include" ] },
    "c"
  ]
}

引数のスタイルは既存のコマンドラインオプション構文に直接対応しており移行しやすいものですが、ツール依存になります。オプションは既存のコマンドラインオプション構文をより抽象化したもので、ツール間のオプション構文の差異を吸収できる可能性があります。また、これら2つのスタイルは1つのファイル内に同居することができます。

それぞれの利点/欠点

  • 引数
    • 利点
      • 既存のJSON compilation databaseをベースとしており、これをパースする実装は既に存在している
      • ツールのコマンドラインオプションとの直接のマッピングがあり、サポートに労力がかからない
    • 欠点
      • オプションとその値を取得するにはパースが必要
      • 通常のコマンドラインオプションと同じ制限を受ける
  • オプション
    • 利点
      • オプション名はオプションのプリフィックス- -- /など)を省略しているため、ツールに依存しない共通名を使用できる
      • オプション値に配列やオブジェクトを利用できることで論理的なグループ化が可能になり、コマンドライン引数のパースで発生するような追加の引数マージ処理のようなものが不用になる
    • 欠点
      • 既存ツールはこの新しいオプション構文を読み取る実装が無い
      • JSON compilation databaseでもこの形式が採用される場合、さらに追加の作業が発生する

そして、このファイルを指定するコマンドラインオプションとしてstd-rspを提案しています。

tool --std-rsp=file
tool -std-rsp:file

実際のファイル全体は例えば次のようになります

{
  "$schema": "https://raw.githubusercontent.com/cplusplus/ecosystem-is/release/schema/std_rsp-1.0.0.json",
  "version": "1",
  "arguments": ["-fPIC", "-O0", "-fno-inline", "-Wall", "-Werror", "-g", "-I\"util/include\"", "-c" ]
}

提案では、オプションスタイルの場合の各オプション名について、実際のコマンドライン引数名に対応させるか、より抽象的な名前にするかについて未解決としています(例えば、W, o, Iwarning, output, include)。これにも利点欠点があるため、どちらを選択するかやこの提案でそれを追求するかについてはSG15の決定に委ねています。

P3052R0 view_interface::at()

view_interfaceat()メンバ関数を追加する提案。

C++26でP2821が採択されたことでstd::spanでもインデックスアクセスにat()が使用できる様になり、既存の標準コンテナ等とのインターフェースの一貫性が向上しています。これにより、標準にある2つのview型(spanstring_view)でat()が使用できる様になったため、これをより汎用的なview型でも一貫させることの根拠が生まれました。

残りのview型とは<ranges>の各種view型(subrangeやRangeアダプタのview型)のことで、これらの型はその共通インターフェースをview_interfaceというCRTPベースクラスを継承することで提供しています。

この提案は、インデックスアクセスの安全性とインターフェースの一貫性を向上させるために、view_interfaceat()メンバ関数を追加しようとするものです。

namespace std::ranges {
  template<class D>
    requires is_class_v<D> && same_as<D, remove_cv_t<D>>
  class view_interface {
    ...
  public:
    ...
    
    // 現在の添字演算子オーバーロード
    template<random_access_range R = D>
    constexpr decltype(auto) operator[](range_difference_t<R> n) {
      return ranges::begin(derived())[n];
    }

    template<random_access_range R = const D>
    constexpr decltype(auto) operator[](range_difference_t<R> n) const {
      return ranges::begin(derived())[n];
    }

    // 提案するat()
    template<random_access_range R = D>
      requires sized_range<R>
    constexpr decltype(auto) at(range_difference_t<R> n);

    template<random_access_range R = const D>
      requires sized_range<R>
    constexpr decltype(auto) at(range_difference_t<R> n) const;
  };
}

このat()はコンテナ等のそれと同様に動作し、指定されたインデックスが範囲外参照となる場合にout_of_range例外を送出するものです。view_interfaceで提供されることで、<ranges>のほぼ全てのview型で使用可能になります。

P3053R0 2023-12 Library Evolution Polls

2023年12月に行われる予定のLEWGの全体投票の予定。

次の19個の提案が投票にかけられます

後ろの2つを除いて、残りのものはC++26に向けてLWGに転送するための投票です。

P3055R0 Relax wording to permit relocation optimizations in the STL

リロケーション操作による最適化を標準ライブラリのコンテナ等で許可するために、標準の規定を緩和する提案。

C++26に向けてリロケーション(relocation)操作を言語に導入する議論が進んでいます。リロケーションは意味的にはムーブ+破棄に相当し、ムーブした直後にムーブ元オブジェクトを破棄する操作をひとまとめにしたものです。中でも、トリビアルリロケーションはmemcpyによってオブジェクト表現(ビット列)をコピーするだけで行うことができます。

リロケーション後の元のオブジェクトはコード上で使用不可になるという性質から、一部のムーブを伴う操作はリロケーションによって効率化できる可能性があります。特に、標準コンテナの操作やアルゴリズムに関わる操作などにおいて最適化を促進することが期待されています。

仮にリロケーション操作が言語に入った時に問題となるのは、それらコンテナやアルゴリズムの規定、特に計算量の規定がリロケーションではなくムーブを前提として指定されていることです。たとえば、std::vector::erase()をリロケーションによって書き換えると次の様な実装になるでしょう

void erase(iterator it) {
  if constexpr (std::is_trivially_relocatable_v<value_type>) {
    std::destroy_at(std::to_address(it));
    std::uninitialized_relocate(it + 1, end_, it);
  } else {
    std::ranges::move(it + 1, end_, it); // operator=
    std::destroy_at(std::to_address(end_ - 1));
  }
  
  --end_;
}

しかし、std::vector::erase()の計算量の指定は、「要素型Tのデストラクタは消去された要素の数と等しい回数呼ばれ、Tの代入演算子は消去された要素の後にある要素の数と等しい回数呼ばれる」と規定されています。トリビアルリロケーションの場合、要素のムーブはそのオブジェクト表現のコピーのみで元オブジェクトの破棄は行われない(オブジェクトの配置場所が変わるだけでオブジェクトそのものは何ら変化しない)ため、std::vector::erase()ではリロケーション操作が利用可能になったとしても標準の範囲内でそれを利用することができません。

このような規定がなされているものがコンテナの操作やアルゴリズムに関して存在しており、これがある限り言語にリロケーションが導入されても標準ライブラリはそれを活かすことができません。しかし、これを取り除いておけばリロケーションの到来と関係なく、標準ライブラリはトリビアルリロケーション可能であると現在わかっている型(std::deque<int>など)についてそのような最適化を行うことができます。

この提案は、その様な過剰な制限を強いてしまっている現在の規定をリストアップし、それをトリビアルリロケーションをサポート可能なように緩和しようとするものです。

提案する変更は、全て現在の制限を若干緩めるものなので、既存の実装がこれを受けて何か変更する必要があるものではありません。たとえば、先ほどのstd::vector::erase()の計算量の規定の場合、「元のvector上で削除された一番先頭にある要素の後にある要素の数について線形」の様に変更しています。これによって、特定の操作に対して計算量を指定する事を回避しています。

P3056R0 what ostream exception

現在例外を投げる際に避けることのできない動的メモリ確保を回避する提案。

標準ライブラリにあるstd::exception派生クラスは.what()メンバ関数からエラーメッセージを返すためにそのコンストラクタで文字列を受け取りますが、動的に構成した文字列をstd::stringに保持している状態で渡そうとする場合、文字列をコピーして受け取る以外の選択肢がありません。

void f(int n) {
  std::string err_msg = std::to_string(n);
  std::runtime_error err{err_msg};  // コピーされる

  throw err;
}

これは、std::exception派生クラスのstd::stringを受け取るコンストラクタはconst std::string&を受け取るものしかないためです。

また、このように例外が発生するコンテキストでエラーメッセージを動的に構成する場合、その作業そのものに伴って動的メモリ確保が発生しています。例えば例外の.what()が呼ばれない場合、このコストは余分なものとなります。

この提案は、std::exception派生クラスおよびstd::exceptionに2種類のメンバ関数を追加することによって、この2つの動的メモリ確保を回避もしくは遅延させ、ライブラリ実装者およびそのユーザーが動的メモリ確保を制御できる様にしようとするものです。

追加するのは次の2つです

  1. std::exception派生クラスのコンストラクタにstd::string&&を受け取るコンストラクタを追加する
  2. std::exception.what()オーバーロードとして、std::ostream&を受け取りエラーメッセージの構築と出力まで行うオーバーロードを追加する

1つ目の変更によって、std::exception派生クラスにエラーメッセージのstd::stringをムーブ渡しできる様になり、コピーに伴う動的メモリ確保を回避することができます。

void f(int n) {
  std::string err_msg = std::to_string(n);
  std::runtime_error err{std::move(err_msg)};  // ムーブされる

  throw err;
}

2つ目の変更ではさらに、例外オブジェクト内部に必要な情報を保持しておき、エラーメッセージが必要になったタイミングでエラーメッセージをオンデマンドに構成することが可能になります。

class runtime_error_v2 : virtual public runtime_error_v2 {
private:
    const int m;
    const std::source_location location;
public:
  runtime_error_v2(int n, const std::source_location location)
    : m{n}
    , location{location}
  {}

  virtual std::ostream& what(std::ostream& os) const noexcept override {
    // 呼ばれてからメッセージを構成する
    return os << "file: "
        << location.file_name() << '('
        << location.line() << ':'
        << location.column() << ") `"
        << location.function_name() << "`: "
        << "value: " << n << '\n';
  }
};

void f(int n) {
  std::runtime_error err{n, std::source_location::current()};
  throw err;
}

P3057R0 Two finer-grained compilation model for named modules

名前付きモジュールの依存関係管理について、より細かい単位で依存関係を管理する方法についての報告書。

名前付きモジュールでは、ヘッダファイルとは異なり個々のモジュールが1つの翻訳単位を成しているため、プログラム全体をビルドするためにはその依存関係を把握した上で依存関係の根本から順番にビルドしていく必要があります。そのため、インクリメンタルビルド等においては、ある1つのファイルの変更がより多くのモジュールや翻訳単位のリビルドを引き起こす可能性があります。

この文書は、この問題を軽減するために、より細かい単位で依存関係管理を行うコンパイルモデルを説明するものです。

この文書で挙げられているモデルは2つあります。

  1. 使用したファイルベースのソリューション
    • あるソースファイルのコンパイル中に使用されたソースファイルを記録しておき、2回目以降のビルドでは自身及び使用したファイルが変更されていなければ再コンパイルを省略する
    • この場合の使用されたかされていないかは、ファイルのインポートやインクルードではなく、その中身の宣言が使用されているかによって判定される
    • ここでのファイルの変更は、ファイルシステムにおける変更によって判定する
  2. 宣言のハッシュによるソリューション
    • あるソースファイルのコンパイル中に、そこで使用されている宣言のハッシュを記録しておき、2回目以降のビルドでは記録した宣言ハッシュを比較して変更がなければ再コンパイルを省略する
    • ハッシュの計算と比較のコストやビルドシステム側での対応など課題がある

この2つの方法はClangのプラグインを通して既に試すことができるようで、文書中でも実際のデモの様子が報告されています。

P3059R0 Making user-defined constructors of view iterators/sentinels private

<ranges>の内部イテレータ型のコンストラクタを非公開にする提案。

<ranges>にある各種のview型は、その動作の実装のほとんどの部分をイテレータによって行なっています。その様なイテレータは構築時に親のviewを受け取りそのポインタを保存しますが、そのコンストラクタは親のview型からアクセス可能であれば良いはずで、他のところからアクセスできる必要はありません。

現在のところ、標準のRangeアダプタのview型のイテレータのその様なコンストラクタは、ものによってアクセスできたりできなかったりします。

int main() {
  auto base = std::views::iota(0);
  auto filter = base | std::views::filter([](int) { return true; });

  // 内部イテレータ型のコンストラクタが呼べる(場合もある)
  auto begin = decltype(filter.begin())(filter, base.begin()); // ok
  auto end   = decltype(filter.end()  )(filter);               // ok
}

この提案は、この様なコードはエラーとなるべきで、標準のRangeアダプタのview型のイテレータのコンストラクタは一部を除いて公開されるべきではない、とするものです。

上記のコードは実はGCCにおいてはエラーになります。それは、GCCfilter_viewイテレータの実装が親のfilter_viewを参照ではなくポインタで受け取る様になっているためです。実装の観点からは、これによってfilter_viewbegin()内では構築時にthisを渡すだけですみ、イテレータ側もaddresof()の呼び出しを適用する必要がなくなります。

現在の規定に照らせばGCCのこの実装は間違っていますが、これはviewの実装詳細の部分であり、本来公開されるべきではないものが公開されていることによる副作用と見なすことができます。また、このGCCfilter_viewにおけるイテレータの実装は、chunk_viewイテレータにおいては規格でそのように指定されており一貫していません。このことからも、これらのコンストラクタは公開されないのをデフォルトにするのが望ましいと言えます。

この提案の対象はあくまで実装のために使用されるコンストラクタを非公開化するもので、デフォルトコンストラクタやムーブコンストラクタ、変換コンストラクタなどを非公開にしようとするものではありません。

P3060R0 Add std::ranges::upto(n)

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

この提案のviews::upto(n)views::iota(0, n)と同じシーケンスを生成します

import std;

int main() {
  // iota(0, n)
  for (int i : std::views::iota(0, 10)) {
    std::println("{} ", i);
  }

  std::println("");

  // upto(n)
  for (int i : std::views::upto(10)) {
    std::println("{} ", i);
  }
}

どちらも0 1 2 3 4 5 6 7 8 9が出力されます。

このため実装はごく簡単に行うことができます

namespace std::ranges {
  // ranges::upto 実装例
  inline constexpr auto upto = [] <std::integral I> (I n) {
    return std::views::iota(I{}, n);
  };

  namespace views {
    using ::std::ranges::upto;
  }
}

これだけだとiotaで十分にしか見えませんが、uptoの意義は符号なし整数型で同じことをする場合の微妙な使用感の悪さを改善することにあります。

void f(const std::vector<int>& vec) {
  auto seq1 = std::views::iota(0, vec.size());  // ng
  auto seq2 = std::views::upto(vec.size());     // ok
}

整数値a, ba < bとして)によってviews::iota(a, b)の様にする場合、a, bの型は異なっていても構いませんが少なくとも符号有無は一致している必要があります(これは、iota_viewの推論補助の制約によって要求されます)。この様な制約は、符号有無が混在した整数型の比較が暗黙変換の結果として意図通りにならなくなる場合があり、それを防止するためのものです。

そのため、上記例のように符号有無が混在した整数値によって指定するとコンパイルエラーとなります。正しくはviews::iota(0u, vec.size())とすべきですが、出力されるエラーメッセージも難しくこの原因を推察するのは容易ではありません。

uptoはシーケンス終端の整数値を1つ指定するだけで、先頭の値はその整数型をデフォルト構築して(0が)補われるため、この問題を回避することができます。

また、同じシーケンスを生成する際にはわずかではありますがviews::iotaよりも短く書くことができ、その意図も明確になります。

P3061R0 WG21 2023-11 Kona Record of Discussion

2023年11月に行われたKona会議の全体会議における議事録。

会議期間中の各SGの作業報告や、LWG/CWGを通過した提案の投票の様子が記載されています。

P3062R0 C++ Should Be C++ - Presentation

P3023の紹介スライド。

EWG/LEWGのメンバに向けてP3023の主張を紹介したものです。

プレゼンテーション用のスライドなので、文章よりも行間が補われている部分があり、主張が分かりやすくなっています。

P3066R0 Allow repeating contract annotations on non-first declarations

関数の最初の宣言にのみ契約注釈を行えるという制限を撤廃する提案。

現在C++26に向けて議論が進められている契約プログラミング機能においては、関数に対する事前条件・事後条件は関数の最初の宣言でのみ行うことができ、たとえ内容が同じだったとしても再宣言で契約注釈を指定する(あるいは再宣言のみで契約を行う)ことはできません。

// 最初の宣言(ヘッダ内など)
int f(const int n)
  pre(n < 100)
  post(r: r == n);

int g();


// 再宣言、f()の定義(翻訳単位内)
int f(const int n)
  pre(n < 100)      // ng
  post(r: r == n)   // ng
{
  return n;
}

int g()
  post(r: -10 < r)  // ng
{
  return 20;
}

これは、同じ関数に対して異なる翻訳単位で異なる契約注釈が行われてしまうことを防止するための制限です。

関数の宣言と定義がヘッダファイルと実装ファイルに分割されている場合、多くのユーザーはヘッダに書いた宣言をコピペして実装ファイルにおける定義を書き始めますが、その関数に契約がなされている場合契約注釈を削除しないとコンパイルエラーになることになります。これは驚くべき動作かもしれません。

クラスのメンバ関数の定義など、関数の宣言と定義が離れていてそこで使用されるエンティティが直接的に見えていない場合、契約注釈がそれを表示しなおかつそれが繰り返されることでコードの可読性を向上させられる可能性があります。

// Widget.h
struct Widget {
  int f() const noexcept
    pre(i > 0);
  
  ...
  
  // much further below:
private:
  int i;
};


// Widget.cpp
int Widget::f() const noexcept
  pre(i > 0)
{
  return i * i; // using i here!
}

元々、C++20で一旦導入されていた契約プログラミング機能では、契約注釈のリストが同じであるという制約の下で再宣言でも契約注釈を行うことができました。初期のMVPにもこれは受け継がれていましたが、後ほど削除されました。なぜなら、当初の仕様では契約注釈のリストについての同一性の定義がなく、どのように同一であるとするのかが不明だったためです。GCCの契約機能の実験実装(C++20の機能ベース)では契約条件式のODRベースの同一性を判定して実装されている様です。

しかし後で、異なる翻訳単位で同じ関数の最初の宣言が複数含まれるプログラムがwell-formeddであるかを指定する必要が出てきたことで、結局契約注釈の同一性の定義を行わなければならなくなった様です。そのため、これは解決すべき問題としてリストアップ(P2896R0)されており、その解決はP2932R2で提案されています。

この提案は、P2932R2で提案されている契約注釈の同一性の定義を採用することで、最初の宣言にある契約注釈を後の宣言で繰り返すことができる様にしようとするものです。

P2932R2で提案されている契約注釈の同一性の定義は次の様なものです

関数宣言d1の契約注釈c1と関数宣言d2上の契約注釈c2は、仮引数名・戻り値名・テンプレートパラメータ名が異なることを除いて、 その述語(契約条件式)p1, p2がそれぞれ宣言d1, d2に対応する関数定義上に置かれた場合にODRを満たしているならば、 c1c2は同じ契約注釈である

この提案はこの定義を採用した上で、C++20時点の仕様だった、関数の後の宣言は最初の宣言と同じ契約注釈を指定するか契約注釈を省略するかのどちらかを行う、というものを復活させることを提案しています

この提案ではあくまでこのことだけを提案していて、最初の宣言で契約注釈を省略して後の宣言でのみ指定する、ことを可能にすることは提案していません。

P3070R0 Formatting enums

列挙型の値を簡単にstd::formatにアダプトさせるための、format_asの提案。

C++23時点のstd::format()std::print)で自前の列挙型を出力しようとする時、主に2つの方法があります。

namespace kevin_namespacy {

  // フォーマットしたい列挙型
  enum class film {
    house_of_cards, 
    american_beauty,
    se7en = 7
  };
}

// 1. フォーマッター特殊化を定義
template <>
struct std::formatter<kevin_namespacy::film> : formatter<int> {
  auto format(kevin_namespacy::film f, format_context& ctx) {
    return formatter<int>::format(std::to_underlying(f), ctx);
  }
};

int main() {
  using kevin_namespacy::film;

  film f = film::se7en;

  // 2. 基底の整数値を出力
  auto s = std::format("{}", std::to_underlying(f));
}

1つはその列挙型のためにstd::formatterを特殊化してフォーマット方法を定義することです。ただし、この例のように整数型のフォーマッタを再利用したとしてもそれなりの量のボイラープレートコードの記述が必要となります。また、フォーマット方法の定義を同じ名前空間で行うことができず、列挙型とそのフォーマッタの定義が空間的に別れてしまいます。

もう1つはstd::to_underlying()によって列挙値に対応する整数値を取得してそれを出力する方法です。これはフォーマット時に常にstd::to_underlying()の呼び出しが必要となります。

この提案は、これらの方法の欠点を改善したフォーマットのカスタマイズ方法を提案するものです。

この提案では、std::formatに対してformat_as()というカスタマイズポイントを導入することを提案しています。

namespace kevin_namespacy {
  // フォーマットしたい列挙型
  enum class film {
    ...
  };

  // filmのためのカスタムフォーマット定義
  auto format_as(film f) {
    return std::to_underlying(f);
  }
}

format_as()std::format()呼び出し内からADLによって発見される関数であり、フォーマット対象の値(ここでは列挙値)を受け取ってそれを既にフォーマット可能な他の型の値(整数値や文字列など)に変換して返すようにしておく必要があります。

この方法のメリット・目的は次の様なものです

  • 列挙型のためのフォーマットカスタマイズ方法の単純化
  • 列挙型のフォーマット効率を向上
    • 既にフォーマッタ特殊化が存在する場合、フォーマッタを経由しないことでフォーマットのパフォーマンスを向上させられる
  • 後方互換性を確保し、std::formatへの移行を促進する

提案より、他の例

enum class color {
  red,
  green,
  blue
};

auto format_as(color c) -> std::string_view {
  switch (c) {
    case color::red:   return "red";
    case color::green: return "green";
    case color::blue:  return "blue";
  }
}

auto s = std::format("{}", color::red); // s == "red"

この提案ではこれを列挙型に限って有効化することを提案していますが、この仕組みはより一般の型に対して拡張可能です。実際に、{fmt}ライブラリではこの仕組みが列挙型に限らず一般の型に対して有効化された上で出荷されています。

P3071R0 Protection against modifications in contracts

P3071R1 Protection against modifications in contracts

契約注釈内から参照されるローカル変数と関数引数は暗黙的にconstとして扱われるようにする提案。

現在のContracts MVP仕様では、契約注釈内での意図しないプログラム状態の変更に対する保護が欠けているため、それを追加しようとする提案です。次のようなことを提案しています

  • contract context(契約コンテキスト)は契約注釈内の条件式
    • その文法はP2961R2で提案されているnatural syntaxのもの
  • 契約コンテキストの部分式であり、オブジェクト型Tの自動変数、または自動変数に対するT型の構造化束縛を指名するid式は、const T型の左辺値(lvalue
  • 契約コンテキストの部分式であり、自動変数であるTの参照を指名するid式は、const T型の左辺値(lvalue
  • 契約コンテキストの部分式であるラムダ式がコピーによって非関数エンティティをキャプチャする場合、暗黙に宣言された(クロージャオブジェクトの)メンバ型はTだが、ラムダの本体内でそのようなメンバを指名するとラムダがmutableでない限りconst左辺値が返される(これは通常通り)
    • ラムダ式が参照によってそのようなエンティティをキャプチャする場合、その参照を指名するid式は、const T型の左辺値(lvalue
  • 契約コンテキストの部分式で現れるthis式はcv Xへのポインタを示すprvalue
    • cvconstと囲むメンバ関数のCV修飾(存在する場合)との組み合わせ
    • この場合の契約コンテキストの部分式には、非静的メンバ関数の本体内での暗黙変換の結果を含む
  • 契約コンテキストの部分式であるラムダ式Tへのポインタであるthisをキャプチャする場合、暗黙に宣言された(クロージャオブジェクトの)メンバ型はconst Tへのポインタ

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

int global = 0;

int f(int x, int y, char *p, int& ref)
  pre((x = 0) == 0)            // proposal: ill-formed、const左辺値への代入
  pre((*p = 5))                // OK
  pre((ref = 5))               // proposal: ill-formed、const左辺値への代入
  pre(std::same_as_v<decltype(ref), int&>)  // OK; 結果はtrue
  pre((global = 2))            // OK
  pre([x] { return x = 2; }())           // error: xはconst
  pre([x] mutable { return x = 2; }())   // OK, 関数引数xのコピーを変更する
  pre([&x] { return x = 2; }())          // proposal: ill-formed、const左辺値への代入
  pre([&x] mutable { return x = 2; }())  // proposal: ill-formed、const左辺値への代入
  post(r: y = r)               // error: yはconstではないので事後条件で使用できない
{
  contract_assert((x = 0));    // proposal: ill-formed、const左辺値への代入
  int var = 42;
  contract_assert((var = 42)); // proposal: ill-formed、const左辺値への代入

  static int svar = 1;
  contract_assert((svar = 1)); // OK
  return y;
}

struct S {
  int dm;

  void mf() /* not const */
    pre((dm = 1))                         // proposal: ill-formed、const左辺値への代入
    pre([this]{ dm = 1; }())              // proposal: ill-formed、const左辺値への代入
    pre([this] () mutable { dm = 1; }())  // proposal: ill-formed、const左辺値への代入
    pre([*this]{ dm = 1; }())             // error: ill-formed、const左辺値への代入
    pre([*this] () mutable { dm = 1; }()) // OK, *thisのコピーを変更
  {}
};

proposalとコメントされているところがこの提案によって動作が変更されるところです。メンバ関数の契約注釈からthisを使用する場合、それはconstメンバ関数内であるかのように扱われます。

この提案は既にSG21においてMVPに採用することに合意されているようです。

P3072R0 Hassle-free thread attributes

スレッドへの属性指定APIについて、集成体と指示付初期化によるAPIの提案。

P2019ではstd::thread/jthreadに対してスレッド属性(のうちスレッド名とスタックサイズ)を指定できるようにすることを提案しています。そのAPIについては揺れているようで、P2019のリビジョン毎に変化している他、P3022では既存のライブラリに倣った異なるAPIが提案されています。

この提案は、P2019とP3022とも異なるAPIを提案するものです。

P2019R4では、属性ごとに異なる型を用意して、スレッドのコンストラクタ先頭でそれを受け渡します。P3022では、1つのスレッド属性クラスに全てのスレッド属性をまとめて、それをコンストラクタ先頭で受け渡します。

この提案は、P3022のアプローチに近いものですが、P3022とは異なりスレッド属性クラスを集成体として、それをコンストラクタ先頭で渡します。

// P2019R4
std::jthread thr(std::thread_name("worker"),
                 std::thread_stack_size_hint(16384),
                 [] { std::puts("standard"); });

// P3022R0
std::jthread::attributes attrs;
attrs.set_name("worker");
attrs.set_stack_size_hint(16384);

std::jthread thr(attrs, [] { std::puts("standard"); });

// この提案
std::jthread thr({.name = "worker", .stack_size_hint = 16384},
                 [] { std::puts("standard"); });

この提案のAPIは、P3022の利点(属性を1つにまとめられる、既存の慣行に従っている)という点を受け継ぎながら、よりユーザーにとって使いやすい構文で属性を指定することができます。

この実装はまず、std::thread内部にスレッド属性クラスを定義したうえで

class thread {
  ...

public:

  // スレッド属性集成体
  struct attributes {
      std::string const &name = {};
      std::size_t stack_size_hint = 0;
  };

  ...
};

これを受け取るコンストラクタをstd::threadstd::jthreadに追加します

class thread {
  ...

  // 追加するコンストラクタ
  template<class Attrs = attributes, class F, class... Args>
    requires std::is_invocable_v<F, Args...>
  explicit thread(Attrs, F &&, Args &&...);

  ...
};

class jthread {
  ...

  // 追加するコンストラクタ
  template<class Attrs = thread::attributes, class F, class... Args>
    requires std::is_invocable_v<F, Args...>
  explicit jthread(Attrs, F &&, Args &&...);

  ...
};

このようにすることで、先程のようにコンストラクタ引数内での指示付初期化による属性指定が可能になります。さらに、ベンダ定義の独自属性指定を使用することもできます。

std::thread t5(__gnu_cxx::posix_thread_attributes{.schedpolicy = SCHED_FIFO},
               std::puts, "vendor extension");

また、将来属性が増えた場合は新しい属性クラスを用意して、これらのコンストラクタのデフォルトテンプレートパラメータを差し替えることでAPI/ABIの互換性を保ったまま拡張することができます。

P3074R0 constexpr union lifetime

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

この提案の動機は、現在議論中のstd::inplace_vectorを定数式でも利用できるようにしようとするもので、そこでの問題は次のようなものです

template <typename T, size_t N>
struct FixedVector {

  // 単一要素union、ストレージ領域のオブジェクトを構築しない
  union U {
    // storageの各要素の生存期間は外側のFixedVectorが管理する
    constexpr U() { }
    constexpr ~U() { }

    T storage[N]; // 要素が挿入されるまでは初期化したくない
  };

  U u;
  size_t size = 0;

  // note: we are *not* constructing storage
  constexpr FixedVector() = default;

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

  constexpr auto push_back(T const& v) -> void {
    std::construct_at(u.storage + size, v); // ng、u.storageはアクティブメンバではない
    ++size;
  }
};

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

このコードは合法的に動作しないようです。例えば、MSVC/EDG/GCC13.21までは動作しますが、clangや最新のGCCはこれを拒否します。

問題は、共用体のコンストラクタがメンバを初期化していないため、その唯一のメンバstorageオブジェクトの生存期間が開始されていないため、その領域(の一部)を遅延初期化しようとすると非アクティブメンバに対するアクセスになってしまい、これが定数式で許可されていないためにエラーとなることです。

とはいえ、共用体のコンストラクタでstorageを初期化してしまうと、storageの全ての要素の初期化が必要になってしまいます。要素型がデフォルトコンストラクタを持たない場合はこれはエラーになります。また、共用体を利用せずにこのような遅延初期化を行うことはできません(aligned_storageのようなものはTではない別の型の領域を再利用する形になる)。

この提案は、このような共用体使用時の非アクティブメンバアクセスを定数式でも行えるようにしようとするもので、3つのアプローチを紹介しています。

  1. 上記のようなストレージのための特別扱いされたライブラリ型std::uninitialized<T>を提供する
    • まさに上記のU::storageを提供するための汎用型、要素型がimplicit-lifetime typeでないならば各要素の生存期間は自動的に開始されない
    • その初期化と破棄の管理は完全に利用者の責任
    • コンパイラの特別扱いなどにより、上記の問題を回避する
  2. 共用体の最初のメンバがimplicit-lifetime typeならば、共用体の生存期間開始時に暗黙的にそのメンバの生存期間を開始する
    • 上記例の場合、配列オブジェクトstorageの生存期間は開始されるが、各要素の生存期間は開始されず初期化もされない
  3. 初期化を伴うことなく、メンバの生存期間だけをユーザーが明示的に開始する方法を提供する
    • 2の方法の問題点を回避する

2の方法の問題は、共用体が複数のimplicit-lifetime typeメンバを持っていてコンテキストに応じて使い分けたい場合に先頭以外のメンバの生存期間(だけ)を開始する方法がないことです。

union U {
  T x[N];
  U y[M];
} u;

例えばこのような共用体の場合、2の仕様を有効化したとするとU::xだけは定数式で初期化せずに使用できますが、U::yは最初の問題と同じことに悩まされます。そのため、3の方法では暗黙的ではなく明示的に、共用体の特定のメンバの生存期間だけを開始するライブラリ関数を提供することでこれを解決します。

現在の標準ライブラリにはそれに近いことを行ってくれる関数std::start_lifetime_as()がすでに存在しています

template<class T>
T* start_lifetime_as(void* p) noexcept;

しかし、これが行うことは今回解決したい問題の解消とは少し異なっており、いくつか問題があります

  • constexpr指定されていない
  • 実行時に使用されないようにif constevalで囲う必要がある
  • 型名を指定しなければならない
  • 戻り値を使用しなければならない
    • 実装によって[[nodiscard]]が付加される場合警告されてしまう
template <typename T, size_t N>
struct FixedVector {
  union U { constexpr U() { } constexpr ~U() { } T storage[N]; };
  U u;
  size_t size = 0;

  // note: we are *not* constructing storage
  constexpr FixedVector() {
    if consteval {
      std::start_lifetime_as<T[N]>(&u.storage);
    }
  }
};

そこで、この提案では共用体の特定メンバの生存期間を開始することに特化した関数を改めて追加することを提案しています

template<class T>
constexpr void start_lifetime(T*);

ただし、この関数で生存期間を明示的に開始できるのはTimplicit-lifetime typeの場合に限ります。

これを用いると、先ほどの例は次のようになります

template <typename T, size_t N>
struct FixedVector {
  union U { constexpr U() { } constexpr ~U() { } T storage[N]; };
  U u;
  size_t size = 0;

  // note: we are *not* constructing storage
  constexpr FixedVector() {
    std::start_lifetime(&u.storage);
  }
};

また、この関数を利用すると1の方法を簡単に実装することができるようになります。

この提案では、この3番目の方法をメインとして提案しています。

P3075R0 Adding an Undefined Behavior and IFNDR Annex

C++の規格書の付属としてコア言語の未定義動作のリストを追加する手続きについての提案。

P1705R0にてコア言語の未定義動作のリストを規格書に添付することが提案されています。そこでは、未定義動作を少なくとも1つのサンプルコードと共に例示しておくことで、言語の未定義動作を明確にするとともにそのリストをC++コミュニティに対して提供し、各未定義動作についての追跡を容易にすることを目的としていました。また、追加で診断不用のill-formed(IFNDR)のリストも同様に提供しようとしています。

この提案はその具体的なプロセスについてのもので、序文およびリストの各要素がどのような情報を伴うかについて提案するものです。

提案する文書構造としては、現在の規格書のコア言語のグループに準じた形でグループ化したうえで、その下にそのグループに属する未定義動作/IFNDRを要素として配置するものです。

各要素は次のようなレイアウトになります

  • 問題を簡潔にまとめたタイトル
  • メインの規格の関連する部分へのクロスリファレンス
  • 標準のnote形式の問題の概要テキスト
  • 問題が起こる場合を示すサンプルコード

転記はしませんが、UB/IFNDRのAnnexの序文も提案されています。

おわり

この記事のMarkdownソース