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

文書の一覧

全部で40本あります。

P0493R3 Atomic maximum/minimum

std::atomicに対して、指定した値と現在の値の大小関係によって値を書き換えるmaximum/minimum操作であるfetch_max()/fetch_min()を追加する提案。

以前の記事を参照

このリビジョンでの変更は、read-modify-writeのセマンティクスをSG1のフィードバックに基づいて修正、replace_xxx関数を削除、機能テストマクロの追加、提案する文言の調整、サンプルの追加、実装例とそのベンチマークの追加、などです。

この提案はC++23に向けてLWGに転送されています。

P1467R8 Extended floating-point types and standard names

C++コア言語/標準ライブラリに拡張浮動小数点型のサポートを追加する提案。

以前の記事を参照

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

  • 浮動小数点数型のCV修飾を正しくハンドルできるように文言を修正
  • 暗黙変換と定数式、暗黙変換と縮小変換の間の相互作用に関するセクションを追加
  • オーバーロード解決の例を追加
  • 実装経験の欠如に関するセクションを追加

などです。

P1673R6 A free function linear algebra interface based on the BLAS

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

以前の記事を参照

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

  • P0009とP2128の参照を更新した事
  • 2より大きなランクのmdspanへの参照を削除
  • symmetric_matrix_rank_k_update, hermitian_matrix_rank_k_updatealphaを取らないオーバーロードを削除
  • conjugated_scalar+. *と比較演算子を追加

などです。

P1774R4 Portable assumptions

コンパイラにコードの内容についての仮定を伝えて最適化を促進するための[[assume(expr)]]の提案。

以前の記事を参照

このリビジョンでの変更は、文言の改善(機能テストマクロ削除、重複属性指定の許可など)、EWGのフィードバックに基づく文章の改善、例の追加などです。

P2093R11 Formatted output

std::formatによるフォーマットを使用しながら出力できる新I/Oライブラリstd::printの提案。

以前の記事を参照

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

  • LEWGのフィードバックに基づいて、vprint_unicodeの規定にあった「ネイティブのユニコードAPIの呼び出しで変換が必要となる場合、実装は無効なコードユニットを(ユニコード標準に従って)U+FFFDに置換すべき」という文章をRecommended Practiceに移動
  • ストリームを受け取らないprintlnオーバーロードを追加
  • 文言の改善
  • ユニコード標準の参照を更新

などです。

P2152R1 Querying the alignment of an object

alignofを型だけではなくオブジェクトに対しても使用出来るようにする提案。

以前の記事を参照

このリビジョンでの変更は、フィードバックに基づいて提案する文言を改善したこと、以前に問題と定義していたものを削除した事、文言と代替案のセクションを分割したことなどです。

このリビジョンでの提案内容は式(オブジェクト)に対するalignofを許可することだけに絞られています。alignof(expr)に対するその結果は、exprの結果となるオブジェクトtについて、tの型Talignas(x)が指定されている場合はx、そうでない場合はalignof(decltype(t))となるように提案されています。

以前の提案に含まれていたCとのalignasの非互換やalignofalignasの値が異なる場合のハンドルなどは、分離されてはいないものの解決のための文言を提案してはいません。

P2173R1 Attributes on Lambda-Expressions

ラムダ式の関数呼び出し演算子に対して属性指定を出来るようにする提案。

以前の記事を参照

このリビジョンでの変更は、CWGのレビューを提案する文言に反映したこと、最新のドラフトをベースとするように更新した(P1102の変更を反映した)ことです。

この提案はすでにCWGのレビューを終えており、次の全体会議で投票にかけられることが決まっています。

P2198R4 Freestanding Feature-Test Macros and Implementation-Defined Extensions

フリースタンディング処理系でも使用可能なライブラリ機能について、機能テストマクロを追加する提案。

以前の記事を参照

このリビジョンでの変更は、LEWGのフィードバックを提案する文言に反映したこと、既存のマクロの値を更新する代わりに新しいマクロを追加することにしたこと、フリースタンディングでも使用できる機能についての機能テストマクロの必要性を追記したことなどです。

この提案はLEWGで議論中ですが、C++26に向けて作業されることになったようです。

P2248R3 Enabling list-initialization for algorithms

値を指定するタイプの標準アルゴリズムにおいて、その際の型指定を省略できるようにする提案。

以前の記事を参照

このリビジョンでの変更はreplace_copy_ifrange版のデフォルト引数型を修正したこと、projectionがある場合のデフォルト引数型に関する議論の追加などです。

P2283R2 constexpr for specialized memory algorithms

<memory>にある未初期化領域に対する操作を行う各関数をconstexprにする提案。

以前の記事を参照

このリビジョンでの変更は、default_construct_atuninitialized_default_constructを削除したことです。これは、この2つの追加に伴ってコア言語の変更が必要になるためです。

P2286R4 Formatting Ranges

任意の範囲を手軽に出力できる機能を追加する提案。

以前の記事を参照

このリビジョンでの変更は範囲の要素のpair/tupleのフォーマットについてを削除したこと(複雑すぎるため)、動的・静的なデリミタに関するセクションを追加しstd::format_joinを削除したこと、format_as_debugset_debug_formatに変更したこと、filesystem::pathについての議論を追記(コンセプトの制約の再帰が発生する)したことです。

P2300R3 std::execution

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

以前の記事を参照

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

  • いくつかのバグ修正
  • run_loop実行コンテキスト(処理をFIFOかつシングルスレッドで実行する)の追加
  • receiver_adaptorreceiverを簡単に書けるようにする)の追加
  • schedulersendersender_ofを要求することで、completion schedulerを提供するようにする
  • when_allのキャンセル機会について規定
  • as_awaitableCPO(senderなどのオブジェクトをコルーチンで待機可能なものに変換する)の追加
  • as_awaitableによるawaitableを考慮するようにconnectを変更
  • エイリアステンプレートvalue_types_of_t(そのsenderreceiverset_valueに渡す引数列)とerror_types_of_t(そのsenderreceiverset_errorに渡す引数列)を追加
  • stop_token_type_tstop_token_of_tに変更
  • possibly eager algorithmを削除する根拠を追記
  • 実装経験について追記

などです。

P2302R2 std::ranges::contains

新しいアルゴリズムとしてstd::ranges::containsを追加する提案。

このリビジョンでの変更は、範囲が含まれているかを調べるオーバーロードcontains_subrangeに変更したこと、実装経験について追記したことです。

P2329R0 Move, Copy, and Locality at Scale

どのような条件でムーブ代入を使用すればコピー代入よりもパフォーマンスが向上するのか、を実験した報告書

この文書では、単純化したシステムによる実験によってムーブ代入がコピー代入よりも有利な場合、あるいはコピー代入の方が有利な場合を明らかにしようとしています。

実験におけるシステムは複数のサブシステムから構成され、各サブシステムは複数のデータエレメントを保持し、それぞれのエレメントが一定サイズのメモリ領域を使用します。各エレメントはサブシステム内で頻繁にアクセス(読み書き)され、サブシステム間で転送もされると想定し、この転送にムーブ代入orコピー代入を使用します。

実験のパラメータは次の7つです。

  1. システムの全体サイズ(systemSize
    • systemSize = numSubsystems * elemsPerSubsys * elemSize
  2. システム内のサブシステムの数(numSubsystems
  3. サブシステム内のエレメント数(elemsPerSubsys
  4. 各エレメントのサイズ(elemSize
  5. エレメントがサブシステム間でシャッフルされた回数(churnCount
  6. 各サブシステム内のエレメントのアクセス回数(accessCount
  7. シャッフル/アクセスのサイクル全体が繰り返される回数(repCount

これらのパラメータを変更しながら実行時間を計測し、コピーとムーブの実行時間を比較します。パラメータ数は7つですが、一部実行に数時間かかるものもあったため、今回の実験では全部はカバーしきれなかったようで、実行したテストは次の3つです。

Test1

L1キャッシュサイズ未満からL3サイズの数倍までのシステムサイズでテスト

パラメータ 変数?
systemSize 変数 213~225 [byte]
elemsPerSubsys 変数 4~systemSize / elemSize / 2
elemSize 定数 128 [byte](キャッシュラインサイズの2倍)
churnCount 定数 1
accessCount 定数 4
repCount 変数 min(1/systemSize, 32)

Test2

Test1をベースに、エレメントサイズがキャッシュラインサイズの倍数では無いようにした

パラメータ 変数?
systemSize 変数 213~225 [byte]
elemsPerSubsys 変数 4~systemSize / elemSize / 2
elemSize 定数 96 [byte](キャッシュラインサイズの1.5倍)
churnCount 定数 1
accessCount 定数 4
repCount 変数 min(1/systemSize, 32)

Test3

エレメントアクセス回数がシャッフル回数を大幅に超える場合を、物理メモリを超える非常に大きなシステムサイズでテスト

パラメータ 変数?
systemSize 変数 232~235 [byte]
elemsPerSubsys 変数 8~systemSize / elemSize / 16
elemSize 定数 64 [byte](キャッシュラインサイズ)
churnCount 定数 1
accessCount 定数 8
repCount 定数 5

テスト環境

  • Model: MacBook Pro 2018 (Model ID: MacBookPro15,1)
  • CPU: 6-core Intel Core i7, 2.2 GHz
  • L1 Data Cache: 32KiB per core
  • L1 Instruction Cache: 32KiB per core
  • L2 Cache 256KiB per core
  • L3 Cache 9MiB shared
  • RAM: 16GiB
  • Disk: 512GB SSD

結果

提案では結果の包括的な分析をしていませんが、とりあえず観察された結果を報告しています。詳細な結果は筆者のリポジトリcsvで置いてあります。

  • エレメントサイズ128[byte]、システムサイズ32[MiB]では、ムーブ代入により小さいサブシステムが多い場合は2倍まで高速化(50%の実行時間)
    • 大きいサブシステムが少ない場合は2倍まで低速化(実行時間189%)
  • エレメントサイズ64[byte]、システムサイズ4/8[GiB]では、ムーブ代入はコピー代入よりも悪い結果(最大7倍悪い)となった
    • この場合でも、多数の小さなサブシステムではムーブ代入はコピー代入より大幅に高速だった
  • 結果はややノイジーで、わずかなパラメータの変更で連続実行すると大きく結果が振動するケースが多く見られた
    • 一説では、キャッシュライン上の要素のアライメントが実行の度に異なっていたと言われる
    • それでも、ページのスラッシングによって引き起こされる大幅な速度低下など、一定のパターンが浮かび上がった

この報告はこれで終わりではなく、さらなる改善実験を予定しているようです。

P2363R2 Extending associative containers with the remaining heterogeneous overloads

連想コンテナの透過的操作を、さらに広げる提案。

以前の記事を参照

このリビジョンでの変更は、LEWGからのフィードバックを適用してDesign decisionsの項を拡張したこと、この提案によるパフォーマンス改善を測定し追記したこと、提案する文言を改善したことなどです。

P2374R2 views::cartesian_product

P2374R3 views::cartesian_product

任意個数のシーケンスの直積を取って、その元のシーケンスを生成するcartesian_product_viewの提案。

以前の記事を参照

R2での変更は

  • 提案する文言の修正
  • コンストラクタをexplicitにした
  • cartesian_product_viewborrowed_rangeにしないことについての説明を追記
  • 機能テストマクロの追加
  • input_rangeを入力できるようにした
  • common_rangeとなるように要件を緩和
  • イテレータとセンチネルに-(距離を計算する)を追加

R3での変更は、sizedifference_typeの型を実装定義にしてそれらの要件のみを指定するようにしたことです。

P2387R3 Pipe support for user-defined range adaptors

ユーザー定義のRangeアダプタに対して、パイプライン演算子|)サポートを簡単に行えるユーティリティを提供する提案。

以前の記事を参照

このリビジョンでの変更は、文言のアップデートのみです。

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

P2416R1 Presentation of requirements in the standard library

現在の規格書の、要件(requirement)の記述方法を変更する提案。

以前の記事を参照

このリビジョンでの変更は、LWGのフィードバックを反映したことと文言の修正です。

P2438R1 std::string::substr() &&

右辺値std::stringからのsubstr()を効率化する提案。

以前の記事を参照

このリビジョンでの変更は、実装経験を追加したこと、P1787R6を考慮したconstオーバーロードについての説明を追記、ユーザー提供のアロケータを持つ場合のsubstrについての説明の拡張などです。

P2440R1 ranges::iota, ranges::shift_left, and ranges::shift_right

Rangeアルゴリズムranges::iota, ranges::shift_left, ranges::shift_rightの提案。

以前の記事を参照

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

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

P2442R1 Windowing range adaptors: views::chunk and views::slide

元のシーケンスの各要素を指定した要素数のウィンドウによって参照するようなviewを生成する、views::chunk/views::slideアダプタの提案。

以前の記事を参照

このリビジョンでの変更は、機能テストマクロを2つに分割したこととLWGのフィードバックを反映したことです。

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

P2447R1 std::span and the missing constructor

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

以前の記事を参照

このリビジョンでの変更は、破壊的変更についての説明を追記したことなどです。

この提案はLEWGのメーリングリストレビューにおいて少し紛糾しているようです。特に、この提案の内容が破壊的かつ危険なものである点が問題視されていて、より詳細なレビューが行われるようです。

P2455R0 2021 November Library Evolution Poll Outcomes

2021年11月に行われたLEWGにおける全体投票の結果。

次の13の提案が投票にかけられ、否決されたものはありません(全てLWGへ転送されています)。また、投票に当たって寄せられた賛否のコメントが記載されています。

P2456R0 2021 December Library Evolution Polls

2021年の12月に予定されている、LEWGでの全体投票の予定表。

以下の12の提案が投票にかけられる予定です。

11と12を除いて、C++23に導入するためにLEWGでの作業を終えてLWGへ転送するための投票です。

P2468R1 The Equality Operator You Are Looking For

==から導出される!=演算子とユーザー定義!=演算子が衝突してしまう問題を解消する提案。

以前の記事を参照

このリビジョンでの変更は、更新されたルールに従って2つ目の解決策と提案する文言を更新したことです。

以前は「生成された演算子候補について、同じスコープで宣言された同じシグネチャを持つ別の候補が存在し、その候補が生成されていない(ユーザー定義である)候補である場合、生成された演算子オーバーロード候補から除外される」というルールを提案していましたが、このリビジョンでは「生成された候補について、その生成元がoperator==であり、それにマッチするoperator!=が宣言されている場合(つまり、そのoperator==の名前をoperator!=に書き換えた時に何かの再宣言となる場合)、生成された候補を取り下げる」というルールを提案しています。これによって、C++20のほかの部分(演算子)のルールに影響を与えることなく問題となる== !=の問題を改善することができます。

これによって、operator==に対応するoperator!=が宣言されていると、そのoperator==からの演算子生成はすべて(逆順の==も)行われなくなります。

struct A {};

template<typename T> 
bool operator==(A, T);  // #1

bool a1 = 0 == A();  // OK、#1から生成された逆順の候補が呼ばれる

template<typename T> 
bool operator!=(A, T);

bool a2 = 0 == A();  // error、#1に対応する!=が宣言されているため、#1からの生成は行われない


struct B {
  bool operator==(const B&);  // #2 (const忘れ)
};

struct C : B {
  C();
  C(B);

  bool operator!=(const B&);  // #3 (const忘れ)
};

bool c1 = B() == C();  // OK、#2が呼ばれる。#2に対応する!=(#3)がCに関して見つかるため、#2から逆順の候補は生成されない
bool c2 = C() == B();  // error, Cに関してみつかった#2とBに関してみつかった#2の逆順候補との間で曖昧となる 

最後のc1, c2のケースは少し理解か難しいです。B() == C()では#2およびその逆順の候補を考慮しますが、2つ目の引数C()のスコープで(つまりクラスCについて)#3が見つかるため、それによって#2からの演算子生成を抑制します。

C() == B()でも#2およびその逆順の候補を考慮しますが、2つ目の引数B()のスコープで(つまりクラスBについて)対応する!=は見つからないため、逆順の候補が生成されます。その結果、#2const修飾がないため、#2とそれを用いた逆順の候補の間のオーバーロード解決は曖昧となり、コンパイルエラーとなります。

このoperator==に対するoperator!=のチェックは、演算子を使用した時に生成された候補に対して使用されるoperator==とその1つ目の引数について行われます。つまり、x == yに対してはy == xx !== yに対してはx == y(選択されると最終的に!(x == y)となる)のように候補が生成され、この時に使用されている==についてその1つ目の引数のスコープで対応する!=を検索するわけです。したがって、x == yではyのスコープでのoperator==について、x != yではxのスコープのoperator==について、それぞれoperator!=が探索され、見つかれば生成された候補は取り下げられ、見つからなければ生成された候補が考慮されます。

これはかなりパッチ感のあるルールですが、この意図は主にconst修飾を忘れているメンバopertor==/!=定義に対する一貫比較仕様の影響を緩和しようとするものです。このルールによって、C++20一貫比較のほかの部分に影響を与えることなくこれらの問題に対する破壊的な影響を抑えることができています(提案では、110のプロジェクトに対してコンパイルエラーが起こるのは8プロジェクト)。

この提案のEWGにおけるレビューと投票では、この提案をC++20へのDRとすることに合意が取れているようです。

P2473R1 Distributing C++ Module Libraries

C++モジュールのビルド済み成果物の配布を容易にするための、ビルドツール・コンパイラ・静的解析ツール間の相互運用性のための共通フォーマットの提案。

以前の記事を参照

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

  • meta-ixx-infoファイルが必ずしもixxファイルと同じ場所になく、相対パスでの検索の対象となることを明確にした
  • 変数置換の責任をパッケージマネージャに移動
  • 同じインターフェースファイルの代替解析オプションについてのセクションを追加。さらに、BMIファイル名に使用された解析オプションを識別するためのものを挿入するようにした
  • 「Search order」のセクションを明確にした
  • meta-ixx-infoファイルに基づくBMiファイルの生成について概念実証を追記

などです。

P2474R0 views::repeat

指定された値の繰り返しによる範囲を生成するRangeファクトリである、views::repeatの提案。

int main() {
  for (int i : std::views::repeat(17, 4)) {
    std::cout << i << ' ';  // 17 17 17 17 
  }
}

repeat_viewiota_viewを参考にして設計されており、views::repeat(v)repeat_view{v})のように呼ぶとvの値の無限列を、views::repeat(v, n)repeat_view{v, n})のように呼ぶとvの値のサイズnの範囲を生成します。指定された値はrepeat_view内部にコピーorムーブして保持されており、repeat_viewイテレータは値をコピーせずrepeat_view本体に保存されている値へのポインタを持っています。repeat_viewイテレータの間接参照は値をコピーして返すのではなく、そのconst参照を返します。

この提案ではrepeat_viewに指定する値の型にはcopy_constructibleであることが要求されていますが、上記性質を考えるとmove_constructibleで十分なはずです。ただ、それは現在の他のviewsingletransformなど)と一貫せず、何かを保持する必要のあるview型の要求をコピーからムーブに変更するのは別の提案(P2494R0)で行なっています。

また、iota_view同様に、views::take, views::dropに対してrepeat_viewのための特殊対応も追加されます。

int main() {
  auto tr1 = std::views::repeat(17, 4) | std::views::take(2);  // repeat_view{17, 2}が返る
  auto dr1 = std::views::repeat(17, 4) | std::views::drop(2);  // repeat_view{17, 2}が返る

  auto tr2 = std::views::repeat(17) | std::views::take(2);  // repeat_view{17, 2}が返る
  auto dr2 = std::views::repeat(17) | std::views::drop(2);  // repeat_view{17}が返る
}

iota_viewと異なるのは、終端が指定されない場合でもこのような特殊対応がなされることです。iota_viewの場合、iota_view{a, b}[a, b)の範囲を示し、この場合のa, bの型が等しくないとiota_viewsized_rangeではなくなります。すると、有限かつrandom_access_rangeだけどサイズを求められないiota_viewが出来上がり、bが範囲に含まれない範囲終端であることから、その場合に範囲をオーバーランする危険性があるため、iota_viewsized_rangeであるときだけviews::take, views::dropで特殊対応されます。repeat_viewの場合は、2つ目の引数は範囲のサイズそのものであり、iota_viewのような状況は発生し得ないため常に特殊対応が可能となります。

P2486R1 Structured naming for function object and CPO values

CPOやniebloidなどの関数オブジェクトに対しての命名ルールに関する提案。

以前の記事を参照

このリビジョンでの変更は、フィードバックを反映したこと、実装経験について追記したことです。

この提案はSG9とLEWGの投票においてコンセンサスを得ることができず、どうやらこれ以上の継続はなさそうです。

P2494R0 Relaxing range adaptors to allow for move only types

何かを保持する必要があるタイプのview型について、保持するものの型に対する要件をcopy_constructibleからmove_constructibleに弱める提案。

このような要件はviewコンセプトの当初の要求に基づいたものでしたが、その後viewコンセプトの要件は緩和され(P1456R1, P2325R3)、その変更が反映されていなかったようです。この提案では、その変更は<ranges>view全体に反映するものです。影響を受けるのは次のものです

  • single_view
  • transform_view
  • zip_transform_view
  • adjacent_transform_view
  • repeat_view(P2474)

同じことはP2483でも提案されていましたが、そちらはsingle_viewだけを対象としていたのと、copy_constructibleだけどcopy_assignableではない型をハンドルしていませんでした。この提案は対象をview全体に広げるとともにそのようなケースをハンドルしたものです。

この提案のSG9におけるレビューではC++20へのDRとすることが推奨されています(まだ未定ですが)。

P2498R0 Forward compatibility of text_encoding with additional encoding registries

std::text_encodingP1855)がIANA以外のレジストリに対応できるようにしておく提案。

現在のstd::text_encodingの文字エンコーディングはIANAレジストリに登録されているものだけを扱っており、他のものを考慮していません。しかし、IANAのリストは不完全であり、C++標準の参照先として適さない所があるため、今から将来的にIANA以外の文字エンコーディング方式のレジストリなどを参照できるようにしておこうとする提案です。

この提案では、std::text_encoding::idstd::text_encoding::iana_idに、std::text_encoding::mib()std::text_encoding::iana_mib()へ変更するとともに、クラスレイアウトについて将来的な拡張に備えるようにしておくことを推奨事項(recommended practice)として追記しておくことを提案しています。

P2499R0 string_view range constructor should be explicit

std::string_viewrangeコンストラクタをexplicitにする提案。

文字列はchar(あるいは他の文字型)の範囲として扱うことができますが、逆は必ずしも常に正しくありません。そのため、文字型の範囲からstd::string_viewへの変換が暗黙変換となっていると問題を起こす可能性があります。

char const t[] = "text";
std::string_view s1(t); // s1.size() == 4;

std::span<char const> tv(t);
std::string_view s2(tv); // s2.size() == 5;

このs1, s2はどちらも同じ範囲tから構築されていますが、結果として得られるstring_viewオブジェクトの意味するところは異なっています。s1const char*から構築するコンストラクタが選択され、文字列終端(\0)は含まれませんが、s2rangeコンストラクタによってspanから構築され、文字列終端も含めた範囲全体を参照します。

別の例として

extern void get_string(std::span<char> buffer);
extern void use_string(std::string_view str);

char buf[200];
get_string(buf);
use_string(buf);

このようなコードは良く書かれており、std::string_viewが配列の参照をとるコンストラクタを持たない理由でもあります。このコードが次のように書き換えられた時

extern void get_string(std::span<char> buffer);
extern void use_string(std::string_view str);

std::array<char, 200> buf;
get_string(buf);
use_string(buf); // oops

このコードはコンパイルされ実行可能で特に未定義動作も起こしませんが、実行結果が異なります。use_string(buf)の呼び出しではstd::arrayからstd::string_viewへの変換がrangeコンストラクタによって行われ、buf全体を参照する文字列となります。これはconst char*からの変換時とは異なっており、おそらくプログラマの意図とも異なります。std::string_viewrangeコンストラクタがexplicitであれば、この変換をコンパイルエラーとすることができます。

これらの理由から、この提案はstd::string_viewrangeコンストラクタにexplicitを追加することを提案しています。

P2501R0 Undo the rename of views::move and views::as_const

提案中のviews::moveviews::as_constの名前をリネームしないでおく提案。

views::moveviews::as_conststd::move, std::as_constと同じ名前をしており、使用した時に引数のviewそのものに作用するのかその要素に作用するのかが分かりづらくなるとして、views::all_moveviews::all_constに変更されました。

viewは常に怠惰な(lazyアルゴリズムであり、その効果は入力のrangeそのものではなく常にその要素に対して作用します。views::joinviews::join_with)はrangeに対して作用しているように見えるかもしれませんが、views::joinは入力の各要素(内側range)をフラット化して見ることができるviewを作成するだけです。範囲をjoinするのか要素をjoinするのかはAPIレベルのことではなく実装詳細ですが、joinの実装詳細もまた要素を結合することです。

views::filterは範囲をフィルタリングするviewであるという意見もあるようですが、filterは要素をフィルタリングすることで範囲のフィルタリングを行います。範囲とは要素の容器のようなもので、フィルタリングアルゴリズムは要素に適用されます。あるいは、範囲は説明のための実装詳細としてのみ存在し、述語は要素に適用されます。

これらのことは、views::moveviews::as_constにも当てはまっています。views::moveは各要素をmoveするviewであり、views::as_constは各要素をas_constするviewです。従って、views::moveviews::as_constはこのようなviewに対する正しい名前であり、それがなにをするかを端的に表しています。views::moveviews::as_constという名前は意図的なもので、怠惰なアルゴリズムの名前はそれがどのような操作を入力範囲の各要素に適用されるかをユーザーに伝えるために意図的に名前付けられています。

views::all_moveviews::all_constという名前は、このような原則に従っておらず、他のviewとの一貫性がありません。allは冗長であり、その名前は要素に何をするかを表現していません。このような名前はRangeライブラリのview設計原則にそぐわず一貫性のない名前付けになっています。

これらの理由から、この提案は名前を元に戻す(views::moveviews::as_constのままにする)ことを提案しています。

P2502R0 std::generator: Synchronous Coroutine Generator for Ranges

Rangeライブラリと連携可能なT型の要素列を生成するコルーチンジェネレータstd::generator<T>の提案。

この提案は、P1268をベースとして、その設計は継続しています。そのため、基本的なところは以前の記事を参照ください

この提案での変更は次の2点です

  1. テンプレートパラメータの並べ替え
  2. O(1)で破棄する必要についての懸念の解消

std::generatorは新旧どちらも3つのテンプレート引数をとります

// P1268
template<class Ref, common_reference_with<Ref> Value = remove_cvref_t<Ref>, class Allocator = void>
class generator;

// この提案
template<typename T, typename Allocator = void, typename U = void>
class generator {

  // generatorの範囲/イテレータの値型(value_type)
  using Value = conditional_t<is_void_v<U>, remove_cvref_t<T>, U>;

  // generatorの範囲/イテレータの参照型(reference_type)
  using Reference = conditional_t<is_void_v<U>,
                                  conditional_t<is_reference_v<T>, T, const T&>,
                                  T>;

  // generatorのpromise_type::yield_value()の引数型
  using Yielded = conditional_t<is_reference_v<Reference>, Reference, const Reference&>;
};

RefT)は返す値の型(iter_reference_t)、ValueU)は要素の型(iter_value_t)を表しており、ValueU)は指定されなければRefT)のremove_cvrefした型が要素型となります。

この提案では2番目と3番目のパラメータの位置が入れ替わった形で、それは要素型をTと異なる型にしたい場合よりもアロケータをカスタムしたいユースケースの方が多いと考えられるためです。また、そこからイテレータの値型/参照型を導出する部分も改善されており、これによってTに真の参照型を指定できるようになっています。

この提案のLEWGにおける最初のレビューでは、generator::Referenceの型が問題となっています。この提案ではgenerator<T>についてTが参照型ではない場合の型としてconst T&を提案していますが、follyやcppcoroではT&、range-v3ではTだったりしており、それをどうするか議論が紛糾しています。一応まだC++23を目指して作業されています。

P2504R0 Computations as a global solution to concurrency

P2300のsender/receiverモデルが、並行処理に関して大域的な解決策を構成できることを証明する文書

C++標準に並行性に関するモデルを追加することは重要な作業であり、低レベルな部分(構成可能性、エラー処理、効率、ユーザビリティなど)だけでなく高レベルでもセマンティクスの正しさを確保する必要があります(提案されたモデルの一般性)。低レベルな部分はLEWG/SG9の議論で十分にカバーされているため、この提案は高レベルセマンティクスである提案されたモデルの一般性の検証を行うものです。

この文書は次のような疑問に答えるものです。

  • sender/receiverはあらゆる種類の並行処理に関する問題に対処できるか?
  • sender/receiverはロックを使用する古典的なアプローチを排除できるか?
  • sender/receiverプログラムの並行性の側面を設計する際に基礎的な要素として使用できるか?
  • sender/receiverが並行処理に関する一般的な解決策となるように提案(P2300)に追加するものはあるか?

この文書では、低レベルな部分を抽象化した上で、高レベルなセマンティクスについて論理的な証明を与えることで、P2300の並行モデルの表現力を示すとともにこれらの疑問に答えています。

その上で、この文書は次のような推奨事項を述べています

  • P2300で示されているモデルをできるだけ早く採用するように努めること
  • senderの名前をasync computation(短縮系 computation)、receiverの名前をasync notification handlerに変更する
  • 計算の上により多くの並行性に関する抽象化を提供するための作業を開始する
    • リアライザ、パイプライン、タスクグラフ、リアクティブプログラミングなど

P2505R0 Monadic Functions for std::expected

std::optionalのMonadic interfaceをstd::expectedにも導入する提案。

P0798R8の採択によって、std::optionaltransform, and_then, or_elseの3つのモナド的操作のためのメンバ関数が追加されました。

std::expectedstd::optionalを発展させたものであり、よく似た役割と意味論を持つことから、意識的に共通のインターフェースとなるように設計されています。しかし現在のところ、std::expectedにはP0798の3つのモナド的操作は含まれていません。モナド的操作はstd::optionalと同様にstd::expectedに対しても恩恵があり、std::optionalとの一貫性向上のためにもtransform, and_then, or_elsestd::expectedにも追加しようとする提案です。

using time_expected = expected<boost::posix_time::ptime, std::string>;

time_expected from_iso_str( std::string time ) {
  try {
    ptime t  = boost::posix_time::from_iso_string( time );
    return t;
  }
  catch( std::exception& e ) {
    return unexpected( e.what() + " str was: "s + time);
  }
}

// for use with transform
ptime next_day( boost::posix_time::ptime t ) {
  return t + boost::gregorian::days(1);
}

// for use with or_else
void print_error( std::string error ) {
  cout << error << endl;
}

// valid iso string
const std::string ts( "20210726T000000" );


void before() {
  time_expected d = from_iso_str( ts );

  if (d) {
    ptime t = next_day( *d );
  } else {
    print_error( d.error() );
  }
}

void after() {
  auto d = from_iso_str( ts )
               .or_else( print_error )
               .transform( next_day ) ;
}

std::optionalと異なる点として、これらの関数に指定する処理はvoidを返すことができます。また、std::expectedstd::expected<void, E>が認められており、その場合はtransformが使用できなくなります。

using void_expected = expected<void, std::string>;

// for use with or_else
void print_error( std::string error ) {
  cout << error << endl;
}

void_expected doit(int x) {
    if (x < 5) return {}; // void return
    return unexpected(std::format("X must be less than 5 passed {}", x));
}

// chained in and_then
void_expected doit2() {
    return {};
}

int main() {
  auto res = doit(1).or_else( print_error ); // res.has_value() == true
  res = doit(5).or_else( print_error );      // call print_error
  res = doit(1).and_then( doit2 );           // call doit2

  res = doit(5).transform(doit(5)); // compile error
  res = doit(5).value_or({});       // compile error
}

std::expectedC++23に導入すべくLWGのレビュー中で、この提案もC++23入りを目指しています。

P2507R0 Only [[assume]] conditional-expressions

P1774で提案されている[[assume(expr)]]について、exprの部分に条件式(conditional-expression)だけを書けるようにする提案。

P1774は[[assume(expr)]]と言う構文で、コードの仮定(事前条件)をコンパイラに伝えることで最適化を促進するものです。現在の提案ではexprの部分にはassignment-expressionと言う構文を置くことができるようになっています。これは条件式(conditional-expression)だけでなく次の4つの構文を包含しています。

  1. 条件式(conditional-expression
  2. yield式(yield-expression
  3. throw式(throw-expression
  4. logical-or-expression assignment-operator initializer-clause

[[assume(expr)]]exprの結果がtrueとなる式を渡すことを想定しているため、明らかに2と3は不適格です。

4つ目の構文は簡単にはx = 0のような代入式です。logical-or-expressionは2項演算子を受理可能な構文で、assignment-operatorは複合代入演算子を含み、initializer-clause{}など任意の初期化式を置けます。これの有効性は判断しづらいですが、元の提案ではこのユースケースが示されておらず例やモチベーションの説明でも条件式が想定されていることと([[assume(expr)]]は仮定を伝えるものであるため)条件式が仮定を表現するのに最適であることから、4番目のような構文もやはり不適格であると思われます。これらのことから、exprの部分に1番目の条件式(conditional-expression)だけを書けるようにしようとする提案です。

現在の[[assume(expr)]]assignment-expressionを取るのは、既存実装(__builtin_assume(expr)など)とif/switchの慣行に従った結果のようです。

また、この提案の変更によって[[assume(x == 42)]][[assume(x = 42)]]のように間違えた場合をコンパイルエラーとして弾くことができるようになります。

P2508R0 Exposing std::basic-format-string

説明専用のstd::basic-format-string<charT, Args...>をユーザーが利用できるようにする提案。

std::basic-format-string<charT, Args...>はP2216で追加されたもので、フォーマット文字列のコンパイル時チェックを行うための型です。これは説明専用(exposition only)として追加されており、利用可能なのはstd::formatをはじめとする標準ライブラリの機能だけです。しかし、これをユーザーも利用したい場合があります。

template <typename... Args>
void log(std::format_string<Args...> s, Args&&... args) {
  if (logging_enabled) {
    log_raw(std::format(s, std::forward<Args>(args)...));
  }
}

これはlogging_enabledと言うフラグによってロギングするかを切り替えたい実装で、その判定と出力をlog()にまとめる事でstd::formatと同様の使用感でそれを行おうとするコードです。このような場合にstd::basic-format-string(上記コード中のstd::format_string)をフォーマット文字列を受け取る引数として使用したくなりますが、現在それはユーザーに解放されていません。実装を再利用すれば同様のものを整える事はできるでしょうが、標準ライブラリの実装詳細に依存することになるので移植性の担保が面倒です。

この提案は、std::basic-format-string<charT, Args...>およびその特殊化を説明専用ではなくすことで、フォーマット文字列のコンパイルチェックを単体の機能としてユーザーが任意に利用できるようにしようとするものです。

std::basic-format-string<charT, Args...>が説明専用になっているのは、P2216がC++20へのDRだったためその影響範囲を制限しようとしていたことと、将来的により良いコンパイル時フォーマット文字列チェック方法が実装可能となった場合に変更可能であること、の2つを意識したためのようです。後者は特に、constexpr引数(P1045R1)や衛生的なマクロ(P1221R0)などなど将来的な言語機能を見越したもののようです。ただ、C++23にこのようなものはなく、上記の問題についての現在可能なソリューションはstd::basic-format-string<charT, Args...>を利用可能とすることだけです。

P2509R0 A proposal for a type trait to detect value-preserving conversions

算術型について、その値を保持する変換を検出するための型特性を追加する提案。

例えば次のように、算術型をテンプレートパラメータで受け取ってメンバとして保持して何かするクラスを考えます。

template <class Rep> // Repは算術型あるいはそれと同等の型を想定
class Quantity {
  Rep value;

  ...
};

このような型はstd::complex, std::chrono::durationや在野のQAngleなどに見ることができます。このような型は異なる特殊化の間で相互に変換可能としたい場合が多く、そのために変換コンストラクタが用意されます。

template <class Rep>
class Quantity {
  Rep value;

public:

  template<typename Rep2>
  // explicit(...) or requires ...
  Quantity(const Quantity<Rep2>& other);
};

このような変換コンストラクタは、情報の損失や未定義動作を防止するために何らかの形で制約されるはずです。例えば

  1. std::complex<From> -> std::complex<To>の変換はFrom -> Toの変換が縮小変換となるとき、explicit指定される
  2. QAngle<From> -> QAngle<To>の変換はToFromの値を正確に表すことができないとき、explicit指定される
  3. std::chrono::durationは、時間の刻み間隔の処理やtreat_as_floating_pointを利用して変換コンストラクタをオーバーロード候補から外す

これらのアプローチにはそれぞれ次のようなメリットとデメリットがあります。

  1. 「縮小変換」を制約することでそのセマンティクスを定義についての問題を回避できる(標準の規定を参照すればいい)
    • 一方、直感に反する場合がある。例えばint -> long doubleの変換は常に縮小変換となる
  2. 暗黙変換によって情報が欠落させないと言う考え方に沿って、変換によって値が保持されることを制約すれば、より期待に一致する結果を得られる
    • 一方、これはプラットフォーム間の非互換性によって移植性を損ねる場合がある。例えば、MSVC ABIではlong double -> doubleの変換は情報を失わない(実質同じ型)が、Itanium ABIではそうではない
  3. アドホックなアプローチでは最大限の柔軟さを得られる
    • 一方、正しく使用するには面倒な場合がある。例えば、カスタム浮動小数点数型を使用する場合にtreat_as_floating_pointの特殊化を忘れるなど。

現在の標準ライブラリはケース1,2の実装を支援するための機能を提供していません(3はアドホックであるため一般実装を提供することは困難)。1はP0870R4で提案され作業中です。この提案は2の実装を支援する機能、すなわちFrom -> Toの変換によって情報の欠落が発生することを検出するためのユーティリティ(型特性)を追加しようとするものです。

template <class Rep>
class Quantity {
  Rep value;

public:

  template<typename Rep2>
  explicit(std::is_value_preserving_convertible_v<Rep, Rep2> == false)  // Rep -> Rep2の変換で値が保持されないならばexplicit
  Quantity(const Quantity<Rep2>& other);
};

提案されているのはstd::is_value_preserving_convertible<From, To>というもので、From -> Toへの変換が可能かつFromが表現可能なすべての値がToでも正確に表現される場合にtrueとなります。正確に表現される(exactly represented)のような言葉は規格用語であり、すでに浮動小数点数型の変換の文脈で使用されているものです。この表現では操作的な定義を定めるのではなく、変換における意味論だけを定義しています。

このような値が正確に保持される変換は縮小変換とは異なるものです。例えばlong double -> doubleへの変換は常に縮小変換ですが、MSVC ABIではlong double, doubleは全く同じ表現を持っているため実際には情報の欠落は発生しません。標準が定義する縮小変換は型の意味論と関係性によって定義されていますが、この提案の言う変換は実際の(実装固有の)量を考慮するものです。

P2510R0 Formatting pointers

std::formatについて、ポインタ型のフォーマットを充実させる提案。

std::formatは現在でもpを用いる事でポインタ値を出力することはできます。

int i = 0;
std::format("{:p}", i);  // 0x00007ffe0325c4e4
std::format("{}", &i);   // NG

しかしこれ以上のフォーマット指定を行うことはできず、ポインタ型を直接指定したり、整数型に用意されているようなフォーマットを利用したければreinterpret_castする必要があります。

int i = 0;
std::format("{:#018x}", reinterpret_cast<uintptr_t>(&i));  // 0x00007ffe0325c4e4
std::format("{:#018X}", reinterpret_cast<uintptr_t>(&i));  // 0X00007FFE0325C4E4
std::format("{:#Lx}", reinterpret_cast<uintptr_t>(&i));    // 0x7ffe_0325_c4e4

この提案は、std::formatがポインタ型を直接受け取れるようにすると共に、0(0パディング)、type(型に応じたデータ表示方法の指定)、Lロケール指定フォーマット)をポインタ型のフォーマットでサポートするものです。

現在 この提案
format("{:018}", ptr);  // 不明瞭(LWG3644の後ではNG)
format("{:P}", ptr);    // NG
format("{:L}", ptr);    // NG

format("{:-}", ptr);    // 不明瞭(LWG3644の後ではNG)
format("{:#}", ptr);    // 不明瞭(LWG3644の後ではNG)
format("{:018}", ptr);  // 0x00007ffe0325c4e4
format("{:P}", ptr);    // 0X00007FFE0325C4E4
format("{:L}", ptr);    // 0x7ffe_0325_c4e4

format("{:-}", ptr);    // 不明瞭(LWG3644の後ではNG)
format("{:#}", ptr);    // 不明瞭(LWG3644の後ではNG) 

type指定は大文字のPでこれを指定するとポインタ値の16進出力を大文字で出力します。省略すると従来のp相当で小文字で出力されます。

おわり

この記事のMarkdownソース