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

文書の一覧

全部で74本あり、SG22(C/C++相互互換性に関する研究グループ)のCの提案を除くと73本になります。

採択された文書

ここにあるのは10月の全体会議でワーキングドラフト入りが承認された提案です。ただし、今月提案文書の改定がないものもあるのでこれで全部ではありません。

P0798R8 Monadic operations for std::optional

std::optionalmonadic interfaceのサポートを追加する提案。

以前の記事を参照

R7(未公開)の変更は

  • 機能テストマクロの変更
  • 各関数の制約の調整

R8の変更は

  • transformの受け取る関数の戻り値型にremove_cvを適用
  • or_elseから事前条件を削除
  • transformがコピー省略行えるように調整

などです。

P1147R1 Printing volatile Pointers

標準ストリームにおいて、volatileポインタの<<による出力をできるようにする提案。

標準出力ストリームを使用してvolatileポインタを出力すると、予期しない値が出力されます。

int main() {
           int* p0 = reinterpret_cast<         int*>(0xdeadbeef);
  volatile int* p1 = reinterpret_cast<volatile int*>(0xdeadbeef);

  std::cout << p0 << std::endl; // 0xdeadbeef
  std::cout << p1 << std::endl; // 1
}

標準出力ストリームに対するストリーム出力演算子<<)では、const void*(非volatileポインタ)の出力を行うオーバーロードはあり、普通のポインタはこれを使用してアドレスが出力されます。しかし、volatileポインタはこのオーバーロードを使用できず(CV修飾が合わないため)、ポインタ->boolの変換を介してbool値として出力されます。

この提案は、operator<<(const volatile void*)オーバーロードを追加することによってこの意図しない変換を防止し、volatileポインタを適切に出力できるようにしようとするものです。

P1272R4 Byteswapping for fun&&nuf

バイトスワップ(バイトオーダー変換)のための関数std::byteswap()の提案。

以前の記事を参照

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

P2077R3 Heterogeneous erasure overloads for associative containers

連想コンテナに対して透過的な要素の削除と取り出し方法を追加する提案。

以前の記事を参照

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

P2314R4 Character sets and encodings

規格文書中の ~ character setという言葉を明確に定義し直す提案。

以前の記事を参照

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

  • P1949の変更を適用する形で更新
  • CWGなどからのフィードバックによる提案する文言の改善

などです。

P2415R2 What is a view?

viewコンセプトの要件を緩和する提案。

以前の記事を参照

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

P2418R2 Add support for std::generator-like types to std::format

std::generator-likeな型に対する<format>のサポートを追加する提案。

以前の記事を参照

このリビジョンでの変更は、提案する文言の改善(主にFormatter要件を要求するところの調整)です。

P2432R1 Fix istream_view, Rev 1

std::views::istream_view<T>()の他のviewとの非一貫性を正す提案。

以前の記事を参照

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

P2450R0 C++ Standard Library Issues to be moved in Virtual Plenary, Oct. 2021

今回の会議で採択された標準ライブラリについてのIssue報告とその解決。

  1. 2191. Incorrect specification of match_results(match_results&&)
  2. 2381. Inconsistency in parsing floating point numbers
  3. 2762. unique_ptr operator*() should be noexcept
  4. 3121. tuple constructor constraints for UTypes&&... overloads
  5. 3123. duration constructor from representation shouldn't be effectively non-throwing
  6. 3146. Excessive unwrapping in std::ref/cref
  7. 3152. common_type and common_reference have flaws in common
  8. 3293. move_iterator operator+() has incorrect constraints
  9. 3361. safe_range<SomeRange&> case
  10. 3392. ranges::distance() cannot be used on a move-only iterator with a sized sentinel
  11. 3407. Some problems with the wording changes of P1739R4
  12. 3422. Issues of seed_seq's constructors
  13. 3470. convertible-to-non-slicing seems to reject valid case
  14. 3480. directory_iterator and recursive_directory_iterator are not C++20 ranges
  15. 3498. Inconsistent noexcept-specifiers for basic_syncbuf
  16. 3535. join_view::iterator::iterator_category and ::iterator_concept lie
  17. 3554. chrono::parse needs const charT* and basic_string_view<charT> overloads
  18. 3557. The static_cast expression in convertible_to has the wrong operand
  19. 3559. Semantic requirements of sized_range is circular
  20. 3560. ranges::equal and ranges::is_permutation should short-circuit for sized_ranges
  21. 3561. Issue with internal counter in discard_block_engine
  22. 3563. keys_view example is broken
  23. 3566. Constraint recursion for operator<=>(optional<T>, U)
  24. 3567. Formatting move-only iterators take two
  25. 3568. basic_istream_view needs to initialize value_
  26. 3570. basic_osyncstream::emit should be an unformatted output function
  27. 3571. flush_emit should set badbit if the emit call fails
  28. 3572. copyable-box should be fully constexpr
  29. 3573. Missing Throws element for basic_string_view(It begin, End end)
  30. 3574. common_iterator should be completely constexpr-able
  31. 3580. iota_view's iterator's binary operator+ should be improved
  32. 3581. The range constructor makes basic_string_view not trivially move constructible
  33. 3585. Variant converting assignment with immovable alternative
  34. 3589. The const lvalue reference overload of get for subrange does not constrain I to be copyable when N == 0
  35. 3590. split_view::base() const & is overconstrained
  36. 3592. lazy_split_view needs to check the simpleness of Pattern
  37. 3593. Several iterators' base() const & and lazy_split_view::outer-iterator::value_type::end() missing noexcept
  38. 3595. Exposition-only classes proxy and postfix-proxy for common_iterator should be fully constexpr

P2462R0 Core Language Working Group “ready” issues for the October, 2021 meeting

今回の会議で採択されたコア言語についてのIssue報告とその解決。

  1. 1249. Cv-qualification of nested lambda capture
  2. 1724. Unclear rules for deduction failure
  3. 1726. Declarator operators and conversion function
  4. 1733. Return type and value for operator= with ref-qualifier
  5. 2484. char8_t and char16_t in integral promotions
  6. 2486. Call to noexcept function via noexcept(false) pointer/lvalue
  7. 2490. Restrictions on destruction in constant expressions
  8. 2491. Export of typedef after its first declaration
  9. 2496. ref-qualifiers and virtual overriding

その他の文書

N4896 PL22.16/WG21 agenda: 4 October 2021, Virtual Meeting

2021年10月4日 08:00 (北米時間)に行われたWG21本会議のアジェンダです。

C++23のための3回目の全体会議です。

N4897 WG21 admin telecon meeting: September 2021

WG21の各作業部会の管理者ミーティング。

前回から今回の会議の間のアクティビティの報告がされています。

N4898 WG21 2021-10 Virtual Meeting Minutes of Meeting

2021年10月4日(北米時間)に行われた、WG21全体会議の議事録。

CWG/LWG/LEWGの投票の様子などが記載されています。

N4899 WG21 admin telecon meetings: 2022

次回以降のWG21の各作業部会の管理者ミーティング。

次は2022年1月24日に予定されています。

N4900 WG21 virtual plenary meeting(s): 2022

次回以降のWG21全体会議の予定表。

次は2月7日に予定されています。

N4901 Working Draft, Standard for Programming Language C++

C++23ワーキングドラフト第5弾。

N4902 Editors' Report - Programming Languages - C++

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

10月の会議で採択された提案とコア言語/ライブラリのIssue解決が適用されています。

P0009R13 MDSPAN

多次元配列に対するstd::spanである、mdspanの提案。

以前の記事を参照

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

  • P2299R3の内容の適用
  • 提案する文言の修正
  • LEWGのガイダンスに従って、設計に関する議論を追記
  • ランク0mdpsnarequired_span_sizeのレイアウト修正
  • 少なくとも1つの要素数指定が0であるmdspanlayout_stride::required_span_sizeを修正
  • operator[]を多次元配列アクセスに使用
  • spanへの文言の参照を解消
  • layout_strideストライドとユニークレイアウトのための変換コンストラクタを追加
  • mdspanにポインタとエクステントからのコンストラクタを追加
  • layout policy mappingnothrow move constructible/assignableの要件を追加
  • accessor policynothrow move constructible/assignableの要件を追加
  • accessor policyaccessor policy pointerの要件を追加
  • mdspan/submdspanが何も例外を投げないという指定を削除

などです。

P0627R6 Function to mark unreachable code

到達不可能なコード領域であることを示す、std::unreachable()の提案。

以前の記事を参照

このリビジョンでの変更は、提案する文言の機能テストマクロ表現を修正したことです。

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

P1169R3 static operator()

関数呼び出し演算子operator())を、静的メンバ関数として定義できるようにする提案。

以前の記事を参照

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

P1467R5 Extended floating-point types and standard names

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

以前の記事を参照

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

  • 文言と設計に関する文書を個別のセクションに分割
  • C言語との互換性に関するドキュメントの追記
    • 2つの言語での同じ浮動小数点数型を指す型名について
    • 2つの言語の間の拡張浮動小数型の変換(usual arithmetic conversion)の違いについて
  • 可変引数関数に渡した時のdoubleへの昇格を削除
  • <format>周りの文言に関する説明を追記(不要だった)
  • I/Oストリームに、long double以下の幅を持つ拡張浮動小数点数型のサポート追加
  • <charconv>/<cmath>の文言についての背景の追記
  • よく知られている浮動小数点数に対応する型エイリアスの名前を、std::floatN_tとする事を決定

などです。

P1642R7 Freestanding Library: Easy [utilities], [ranges], and [iterators]

[utility]<ranges><iterator>から一部のものをフリースタンディングライブラリに追加する提案。

前回の記事を参照

このリビジョンでの変更は、<atomic>がフリースタンディングであり続けるように文言を調整したこと、std::unreachableの追加に関する議論を追記したことです。

この提案では、<utility>全体をフリースタンディングとしていますが、std::unreachable<utility>に追加される予定のため衝突しています。std::unreachableをフリースタンディングとすることに対する投票が行われたようですが、反対する人はいなかったようです。

P1673R5 A free function linear algebra interface based on the BLAS

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

このリビジョンでの変更は、mdspan()に代わって[]を使用するようになったため、その変更を反映したことです。

どうやらこの提案はC++26を目指すことになったようです。

P1854R1 Conversion to literal encoding should not lead to loss of meaning

文字列リテラルエンコーディングを実行時エンコーディングに変換する際、文字表現が失われる場合をコンパイルエラーとする提案。

文字列リテラルエンコーディングソースコードエンコーディング)と実行時エンコーディングが異なるケースは比較的よく発生し、その時の振る舞いは実装定義とされています。

#include <cstdio>

int main() {
  puts("こんにちは");
}

このコードをUTF-8で保存し、実行時エンコーディングをAsciiとしてコンパイルすると、MSVCでは警告のみでコンパイルが通るのに対してGCCではエラーとなり、その際MSVCはAscii内の代替文字(?)で文字を置換しています。

この事はC++プログラムの移植性を損ねており、文字列は意味や目的を持つ文書であるので実装がその意味を変えるべきではなく、このようなエンコーディングの縮小変換が起こる場合をill-formedと規定しようとする提案です。

この提案ではまた、複数の文字によって1文字を構成するユニコード文字(é🇯🇵など)がマルチキャラクリテラルとして読み取られて意図しない1文字になる場合をエラーとする事も同時に提案しています。

int main() {
  [[maybe_unused]]
  char c = '🇯🇵';
}

マルチキャラクリテラルに含まれる文字は基本リテラル文字集合の要素のみ、と規定する事でこのような文字がコンパイルエラーととなるようにします。これによって、結合文字などの不可視文字が排除されるため見た目の曖昧さが解消され、マルチキャラクリテラルの結果がintに収まるようになります。

P1885R8 Naming Text Encodings to Demystify Them

システムの文字エンコーディングを取得し、識別や出力が可能なライブラリを追加する提案。

以前の記事を参照

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

などです。

P2012R2 Fix the range-based for loop, Rev2

現在のrange-based forに存在している、イテレーション対象オブジェクトの生存期間にまつわる罠を修正する提案。

以前の記事を参照

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

P2066R10 Suggested draft TS for C++ Extensions for Minimal Transactional Memory

現在のトランザクショナルメモリTS仕様の一部だけを、軽量トランザクショナルメモリとしてC++へ導入する提案。

以前の記事を参照

このリビジョンの変更点は、LWGのフィードバックに対応して文言を修正した事です。

この提案は全体会議で承認され、「Minimal Transactional Memory TS」として発行されることになります。

P2248R2 Enabling list-initialization for algorithms

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

以前の記事を参照

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

  • 理論的根拠について詳しく追記
  • 出力イテレータを使用するアルゴリズムのデフォルトvalue_typeについての説明の追記
  • テスト実装のリンク追加
  • *_scan系数値アルゴリズムについてのデフォルトを修正

などです。

P2249R2 Mixed comparisons for smart pointers

スマートポインターの比較演算子に生ポインタとの直接比較を追加する提案。

以前の記事を参照

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

P2255R2 A type trait to detect reference binding to temporary

一時オブジェクトが参照に束縛されたことを検出する型特性を追加し、それを用いて一部の標準ライブラリの構築時の要件を変更する提案。

以前の記事を参照

このリビジョンでの変更は、機能テストマクロの追加、std::make_from_tupleをこの提案に沿って修正、提案する文言の変更などです。

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

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

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

R1での変更は、ローカルなテスト実装を追記したこと、CとC++の提案する文言を分離したこと、フィードバックを受けた議論を追加したことです。

R2での変更は、さらに議論を追記したことなどです。

この提案はCとC++の両方に対して提出されていますが、Cの方の投票では一旦否決されているようです。

P2291R3 Add Constexpr Modifiers to Functions to_chars and from_chars for Integral Types in Header

std::to_chars, std::from_charsを整数変換に関してconstexprにする提案。

以前の記事を参照

このリビジョンでの変更は、ヘッダの肥大化防止やコンパイル時間削減のためコンパイラ組み込み命令で実装されうることについて追記したことです。

P2300R2 std::execution

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

以前の記事を参照

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

  • 即時実行senderアルゴリズムの削除
  • connectカスタマイゼーションポイントとsender_traits<>を拡張して、awaitable(コルーチンPromise型)をtyped_senderとして扱えるようにした
  • as_awaitable(), with_awaitable_senders<>を追加して、コルーチン型がコルーチン内でsenderを待機可能にした
  • sender/awaitableの相互作用について説明を追記
  • sender/recieverのキャンセルサポートの設計についての説明を追記
  • 単純なsenderアダプタアルゴリズムの例を示すセクションを追加
  • 単純なschedulerの例を示すセクションを追加
  • 数独ソルバ、並行再帰ファイルコピー、エコーサーバー、の例を追記
  • bulkアルゴリズムforward progress保証を改善
  • 様々なsenderを使用して非同期シーケンスを表現する方法を説明するセクションの追加
  • senderを使用して、部分的に成功を表す方法を説明するセクションの追加
  • senderファクトリjust_error, just_doneを追加
  • senderアダプタdone_as_optional, done_as_error
  • sender/recieverの製品

などです。

今月の文書にもやりあっているのが見られますが、NetworkingTSのベースとなるExecutorとしてこの提案のschedulerがふさわしくないことから、議論が紛糾しているようで、進捗が芳しくなさそうです・・・。

P2322R5 ranges::fold

rangeアルゴリズムであるranges::foldの提案。

以前の記事を参照

このリビジョンでの変更は、プロジェクションを削除したこと、foldl_while系の操作を削除したこと、全てのオーバーロードがプロジェクションを取らないようになったこと、各関数の名前をfold_left, fold_left_first, fold_right, fold_right_lastに暫定的に変更したことなどです。

プロジェクションは、初期値を最初の要素から取得するタイプ(fold_left_first/fold_right_last)の場合に、プロクシオブジェクトを返すイテレータ(例えばzip)を適切に扱うには、余分なコピーが避けられない問題を回避するために削除されました。

このリビジョンの提出後、名前はfold_left, fold_left_first, fold_right, fold_right_lastで正式に決定し、LEWGの投票で合意が取れればLWGに転送される予定です。

P2324R1 Labels at the end of compound statements (C compatibility)

複合ステートメントcompound statement)の末尾にラベルを置けるようにする提案。

このリビジョンでの変更は、3つ提案していた文言を1つに絞ったこと、SG22での投票結果を記載したこと、実装経験について追記したこと、Cと同様の暗黙のnullステートメントを追加できるようにしたこと、などです。

P2327R1 De-deprecating volatile compound operations

非推奨となったvolatile値に対する複合代入演算子を再考する提案。

このリビジョンでの変更は、bitwise演算子の複合代入演算子|= &= ^=)の非推奨解除のみを提案するようにしたことです。

この提案はEWGの議論を通過し、CWGに送られるためのEWGの投票待ちです。なお、C++20へのDRとなるようです。

P2347R2 Argument type deduction for non-trailing parameter packs

関数テンプレートの実引数型推論時に、引数リスト末尾にないパラメータパックの型を推論できるようにする提案。

以前の記事を参照

このリビジョンでの変更は、テスト実装を使用してより広いコンテキスト(クラス、関数テンプレートのアドレス取得、エイリアステンプレート、部分特殊化)でこの推論が上手くいくかの調査の結果を追記したことです。調査では、特に問題は見つからなかったようです。

P2348R2 Whitespaces Wording Revamp

ユニコードの仕様に従う形で、改行と空白を明確に定義する提案。

以前の記事を参照

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

この提案はSG16とEWGのレビューを通過し、CWGへ送るためのEWGの投票待ちをしています。

P2350R2 constexpr class

constexpr対応クラスの簡易構文の提案。

以前の記事を参照

このリビジョンでの変更は、例を追加したこと、EWGでの投票の結果を記載したこと、constexprクラスのstaticメンバ変数はconstexprとなることを明確にしたこと、constexpr(false)が必要な理由と不要な理由を記載したこと、などです。

メンバ関数の一部がCのAPIに依存していたりするとその関数はconstexpr化不可能ですが、そのクラスをconstexprクラスにしてしまうとそのようなメンバ関数が1つあるだけでエラーとなってしまいます。そのため、選択的に非cosntexprにするためにconstexpr(false)が必要だ、というフィードバックが寄せられたようです。ただし、その場合でもクラス外で定義することで回避可能であったり、P2448R0が採択されればその問題を回避できるとして、この提案ではconstexpr(false)を提案していません。

P2361R3 Unevaluated strings

コンパイル時にのみ使用され、実行時まで残らない文字列リテラルについての扱いを明確化する提案。

以前の記事を参照

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

  • プリプロセッシングトークンはコンテキストに依存してはならないことからunevaluated-stringプリプロセッシングトークンとしないように文言を改善した
  • 文字列リテラルの評価中にその文字列をnull終端することで、unevaluated-stringがnull終端されていないことを明確化
  • literal-operator-idの文言にunevaluated-stringを適用
  • externの文言にunevaluated-stringを適用し、リンケージ指定がユニコード文字を示すことを明確化
  • asmステートメントでの数値エスケープシーケンスの許可

などです。

P2384R1 2021 Spring Library Evolution Poll Outcomes

2021年の春(4月から6月にかけて)に行われた、LEWGの全体投票の結果。

以前の記事を参照。

このリビジョンの変更は引用する文書が間違っていたのを修正しただけです。

P2387R2 Pipe support for user-defined range adaptors

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

以前の記事を参照

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

この提案はSG9とLEWGのレビューを終え、LWGに送られるための投票待ちをしています。C++23入りを目指しています。

P2388R3 Minimum Contract Support: either No_eval or Eval_and_abort

契約が破られた時に継続しないコントラクトサポートを追加する提案。

以前の記事を参照

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

  • 構文について、属性との混同、Cとの潜在的な非互換性について追記
  • 契約モード名をNo_eval、Eval_and_abort*に変更
  • "function parameter"とすべきところを"function argument"としていたところを修正
  • 事後条件で非const非参照仮引数を使用できるようにする解決策の案として、暗黙const扱いにする例を追加
  • セキュリティの懸念に対処するため、violation handlerが標準出力にメッセージを出力することを推奨する文言を削除
  • 事後条件で参照されている関数引数をconst_castした場合に未定義動作となる説明を追記
  • 契約条件部分式ごとの副作用削除についての議論の追記
  • 実装可能性についてのセクションを追加
  • 契約違反時にstd::terminate()ではなく、std::abort()を呼び出す理由について追記
  • 契約条件がimmediate contextにないことを明確にした
  • 構文の選択、副作用の説明、プログラミングモデルの有効性、に関する未解決の問題の追加

などです。

P2400R2 Library Evolution Report: 2021-06-01 to 2021-09-20

2021年6月から9月にかけての、LEWGでのミーティングについてのまとめ。

どれくらいミーティングを行ったのか、おおまかな機能単位についての進行状況、レビューを行った提案についての議論の状況などが記載されています。

P2408R2 Ranges iterators as inputs to non-Ranges algorithms

非Rangeアルゴリズムイテレータに対する名前付き要件を、イテレータコンセプトで置き換える提案。

以前の記事を参照

このリビジョンでの変更は、タイトルの変更、input iteratoroutput iteratorの要件についてイテレータコンセプトを使用するようにしていた部分の削除、mutable iteratorに関する議論の追加、zip_viewイテレータなどについての説明の追記。

この提案はSG9のレビューを通過し、LEWGへ転送されました。

P2417R1 A more constexpr bitset

std::bitsetconstexpr対応させる提案。

以前の記事を参照

このリビジョンでの変更は、設計についての議論と実装経験についてを追記したことです。

MSVC STLbitset実装をベースにcosntexpr化を行い、実装できることと、既存のテストをパスすることを確かめたようです。

P2435R1 2021 Summer Library Evolution Poll Outcomes

2021年の夏(7月から9月にかけて)に行われた、LEWGの全体投票の結果。

以前の記事を参照。

このリビジョンの変更は引用する文書が間違っていたのを修正しただけです。

P2445R0 forward_like

クラス型のメンバ変数について、const性も含めた正しい完全転送を行うstd::forward_likeの提案。

これはDeducing Thisによって頻出するであろうコードを正しく書くためのユーティリティです。

template<typename T>
struct wrap {
  T v;

  template<typename Self>
  auto value(this Self&& self) -> decltype(auto) {
    return std::forward<Self>(self).v; // あってる?
  }
};

この.value()メンバ関数thisself)の値カテゴリに応じてメンバvの返す型(値カテゴリ)を変化させたいわけです。C++20まではstd::optional.value()のようにconstと参照修飾によって4つのオーバーロードに分けていましたが、C++23以降はDeducing Thisによってthisに対応する引数を明示的に取れるようになった事から1つの関数にまとめることができるようになります。

その際、thisの状態によってメンバを完全転送するにはおおよそ上記のように書くことになるのですが、メンバが参照である場合やthisconstである場合に正しい結果になりません。次の表は上記のように書いた時の.value()の戻り値型がthisの状態とメンバの状態によってどうなるかを示したものです

thisself メンバ(v std::forwad(self).v
&&
& &
&& &&
const const &&
const & const &
const && const &&
const const &&
& const const &
&& const const &&
const const const &&
const & const const &
const && const const &&
& &
& & &
&& & &
const & &
const & & &
const && & &
&& &
& && &
&& && &
const && &
const & && &
const && && &
const & const &
& const & const &
&& const & const &
const const & const &
const & const & const &
const && const & const &
const && const &
& const && const &
&& const && const &
const const && const &
const & const && const &
const && const && const &

表中の空白はconstも参照修飾もない状態、すなわち単なる値として宣言されている場合です(thisの場合はコピーされている、すなわちprvlaueの状態)。

これを見ると、std::forward<Self>(self).vのようなコードが問題ないのは、vが非参照である時くらいのものです。メンバが参照である場合はthisconstが正しく伝わっておらず、メンバがconst参照の場合はthisの値カテゴリ(特に右辺値&&)が正しく伝播されていません。

std::forward_likeはこの伝播を正しく行い完全転送するためのもので、std::forwardとよく似ています。forward_likeを使うと先ほどのコードは次のように書き換えられます

template<typename T>
struct wrap {
  T v;

  template<typename Self>
  auto value(this Self&& self) -> decltype(auto) {
    return std::forward_like<Self>(self.v);
  }
};

次の表は、先ほどの表にforward_likeの結果を追記したものです

this メンバ forwad forward_like
&& &&
& & &
&& && &&
const const && const &&
const & const & const &
const && const && const &&
const const && const &&
& const const & const &
&& const const && const &&
const const const && const &&
const & const const & const &
const && const const && const &&
& & &&
& & & &
&& & & &&
const & & const &&
const & & & const &
const && & & const &&
&& & &&
& && & &
&& && & &&
const && & const &&
const & && & const &
const && && & const &&
const & const & const &&
& const & const & const &
&& const & const & const &&
const const & const & const &&
const & const & const & const &
const && const & const & const &&
const && const & const &&
& const && const & const &
&& const && const & const &&
const const && const & const &&
const & const && const & const &
const && const && const & const &&

std::forwadの時と比較すると、メンバが参照である場合にも正しく(理想的に)constと値カテゴリが伝播しているのが分かります。Deducing Thisで可能になることを考えればこのようなコードは良く書かれることが予想され、forward_likeの必要性はDeducing Thisの提案中でも指摘されていました。

std::forward_likeは簡単には次のように実装されます。

// T->Uへ参照(値カテゴリ)を伝播する
template <typename T, typename U>
using __override_ref_t = std::conditional_t<std::is_rvalue_reference_v<T>,
                                            std::remove_reference_t<U>&&, U&>;

// T->Uへconstをコピーする
template <typename T, typename U>
using __copy_const_t = std::conditional_t<std::is_const_v<std::remove_reference_t<T>>,
                                          U const, U>;

// forward_likeの結果型を求める
template <typename T, typename U>
using __forward_like_t = __override_ref_t<T&&, __copy_const_t<T, std::remove_reference_t<U>>>;


template <typename T>
[[nodiscard]]
constexpr auto forward_like(auto&& x) noexcept -> __forward_like_t<T, decltype(x)> {
  return static_cast<__forward_like_t<T, decltype(x)>>(x);
}

各メタ関数のT*thisの、Uはメンバの型が来ます。すなわち、なるべくthisの値カテゴリを利用してメンバの値カテゴリを指定しつつthisconstを伝播しようとするものです。

値カテゴリによって振る舞いを変化するラムダ式によるコールバックのサンプル

auto callback = [m=get_message(), &scheduler](this auto&& self) -> bool {
  return scheduler.submit(std::forward_like<decltype(self)>(m));
};

callback();             // retry(callback)
std::move(callback)();  // try-or-fail(rvalue)

この例のように、Deducing thisによってキャプチャしているラムダを扱う際にforward_likeは特に重要となります。

間接的な所有権を持つクラスにおける、所有するメンバの転送のサンプル

struct fwd {
  std::unique_ptr<std::string> ptr;
  std::optional<std::string> opt;
  std::deque<std::string> container;

  auto get_ptr(this auto&& self) -> std::string {
    if (ptr) {
      return std::forward_like<decltype(self)>(*ptr);
    }
    return "";
  }

  auto get_opt(this auto&& self) -> std::string {
    if (opt) {
      return std::forward_like<decltype(self)>(*m);
    }
    return "";
  }

  auto operator[](this auto&& self, size_t i) -> std::string {
    return std::forward_like<decltype(self)>(container[i]); // dequeは右辺値用[]を持たない
  }
};

この例では、ポインタを介した所有などによってthisconst性および値カテゴリの伝播が断ち切られているケースでも、Deducing Thisとforward_likeによって望ましい転送を実現しています。

P2447R0 std::span and the missing constructor

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

現在のstd::spanにはstd::initializer_listを受け取るコンストラクタがなく、次のようなコードがエラーとなります。

void foo(std::span<const int>);

int main() {
  foo({1, 2, 3}); // error
}

この提案はstd::spanにはstd::initializer_listを受け取るコンストラクタを追加してこれがコンパイルできるようにしようとするものです。

この提案のモチベーションは、従来std::vectorを利用していた関数引数をstd::spanに置き換えた時に発生する問題を解決することにあります。

// C++17までのインターフェース
void foo(const std::vector<int>&);

// C++20からはspanを使い書き換えたい
void foo(std::span<const int>);

int main() {
  foo({1, 2, 3}); // C++17まではOK、C++20からはエラー
}

この変更が適用されると{}によって簡単にダングリングstd::spanを作れてしまうようになりますが、現状でもダングリングspanは割と簡単にできてしまい、std::spanviewである以上それは避けられないため、std::initializer_listを取れるようにすることのメリットの方が大きいという主張です。

また、std::initializer_listコンストラクタは{}初期化時に最優先で選択されることから、この変更は破壊的変更となります。しかし、std::initializer_listコンストラクタに制約を設けることでこの影響は低減でき、破壊的となるのは非常にレアケースとなるようです。

const std::vector<int>;

// この変更の後でも、これらの振る舞いは変化しない
auto sp1 = std::span{v};
auto sp2 = std::span{v.begin(), v.end()};
auto begin = v.data();
auto end = begin + v.size();
auto sp3 = std::span{begin, end};

// この提案以前はvoid*1つのspan
// この提案の後では、void*2つのspan
void* vp = nullptr;
span<void* const> sp4{&vp, &vp+1};

// この提案以前はany1つのspan
// この提案の後では、any2つのspan
std::any a;
std::any* ap = &a;
span<std::any> sa{&ap, &ap+1};

std::spanの主な用途の一つは関数引数で使用することです。その場合、spanの要素型をテンプレートパラメータにすることはできないためspanの要素型は明示的に指定されているはずです。従って、void*などの特殊な要素型を考慮する必要は通常ないため、破壊的変更はやはり問題とならないとのことです。

P2448R0 Relaxing some constexpr restrictions

constexpr関数がすべての引数について定数実行不可能となる場合でも、コンパイルエラーにしないようにする提案。

例えば、次のコードはC++20まではエラーとなりますが、C++23以降(P2231R1実装後)は定数実行可能となるためエラーになりません。

constexpr void h(std::optional<int>& o) {
  o.reset();  // C++20まで非constexpr
}

現在の仕様では、constexpr関数がそのあらゆる引数について定数実行可能ではない場合コンパイルエラー(もしくは診断不要のill-formed)とする事が求められています。そのため、constexpr関数内で非constexpr関数を呼び出す場合は常にコンパイルエラーとなります。

これを適応的にコンパイルするため、機能テストマクロを使用して次のようにすることができます

#if __cpp_lib_optional >= 202106
constexpr
#endif
void h(std::optional<int>& o) {
    o.reset();
}

しかし、この方法は適切でしょうか?

std::optionalのこの例に見えるように、ある関数がある時点でconstexprではなかったとしても将来的にconstexprになる可能性があります。constexprのルールはC++のバージョンごとに拡張されているため、それまでconstexpr対応できなかった処理でも、あるバージョンからはconstexpr化することができるようになり得ます。しかし、標準ライブラリのサポートはそれが可能になったタイミングから遅れてそれに対応し、在野のライブラリはさらに遅れるのが常です。またこのような機能テストマクロが非標準のものについて提供されることはほぼなく、在野のライブラリを使用している場合、このような対応は手作業で行う必要があります。

このような診断は定数式で実行可能なものが大きく制限されていたC++11の時代には有用だったのかもしれませんが、より多くのものが定数式で実行可能となった現在(C++23)の環境には即しておらず、constexprでの実行可否の診断は実際に定数式で実行する時にまで遅延しておく方が良いでしょう。この提案は、そのような規定を取り除こうとするものです。

P2451R0 2021 September Library Evolution Poll Outcomes

2021年の秋に行われた、LEWGの全体投票の結果。

以下の5つの提案が投票にかけられ、LWGに転送されることが可決されています。また、その際に寄せられたコメントが記載されています。

P2460R0 Relax requirements on wchar_t to match existing practices

wchar_tエンコーディングについての実態になじまない制約を取り除く提案。

現在のC++標準におけるwchar_tエンコーディングに関する規定は、ワイド文字エンコーディングのすべての文字を単一のコード単位としてエンコーディングする必要がある(すなわち、すべての文字がwchar_t1つに収まらなければならない)ことを要求しています。例えばWindowsではそれはUTF-16であり、UTF-16サロゲートペアを持つためその要件に違反していることになります。

さらに、wchar_tの値はサポートされているロケールで指定された拡張文字セットの個別の文字を表現できることも要求されているため、実行字文字集号がUTF-8の場合はすべてのユニコード文字をwchar_t1つで表現可能であることが求められ、実質的に2バイトエンコーディングを排除しています。

これらのことは実際の実装(主にWindows)に沿っておらず、C++を多様なOS上での開発に適さないものにしてしまっています。そのため、実装の慣行に従う形でこれを修正するのがこの提案の目的です。

この提案による変更は実装ですでに行われていることを標準化するだけなので、実装及びプログラマに影響はありません。

P2461R0 Closure-based Syntax for Contracts

属性likeな構文に代わるコントラクト構文の提案。

提案されている構文はpre, post, assertの3つの文脈依存キーワードに続いて、ラムダ式(に近い構文)によって契約条件を記述します。pre/postは後置戻り値型の後ろのrequires節と同じ位置に書き、assertは関数本体内に書きます。

auto plus(auto const x, auto const y) -> decltype(x + y)
  pre { x > 0 }
  pre {
    // オーバーフローのチェック
    // ここに書けるものは条件式だけなので、&&で結合する
    (x > 0 && y > 0 ? as_unsigned(x) + as_unsigned(y) > as_unsigned(x) : true) &&
    (x < 0 && y < 0 ? as_unsigned(x) + as_unsigned(y) < as_unsigned(x) : true)
  }
  // retはauto&&と宣言したかのような引数、戻り値が渡される
  post (ret) { ret == (x + y) }
{
  assert { x > 0 }; // この構文は現在有効(assert構造体の集成体初期化)
  auto cx = x;
  return cx += y;
}

ただし、この提案は純粋に構文のみを提案するもので、P2388の意味論をほぼ踏襲しており、P2388およびMVP(Minimum Viable Product)と呼ばれるコントラクトの最小セットに異を唱えるものではありません。この提案は、P2388で示されているいくつかの問題を解決可能な構文を探索し、提案するものです。

構文定義は次のようになっています

correctness-specifier:
    correctness-specifier-keyword correctness-specifier-introducer(opt) correctness-specifier-body

correctess-specifier-keyword:
    pre | post | assert

correctness-specifier-introducer:
    lambda-introducer(opt) return-value-id(opt)

return-value-decl:
    ( identifier )

correctness-specifier-body:
    { conditional_expression }

pre, post, assertの後にはラムダ式(のような)構文を置くことができて、ラムダキャプチャも機能します。省略された場合は[&]から始まったものとみなされています。ただし、現在合意が取れているMVPと呼ばれるコントラクトの最小セットに従う場合、ラムダ導入子は常に省略されなければなりません。 correctess-specifier-keywordに続く{}の中には条件式のみを書くことができて、これはrequires節と同じです。

ラムダ導入子を許可しなかったとしても、この構文ではP2388で導入される未定義動作を減らすことができるようです。

ラムダ導入子を許可すると、コピーキャプチャによってP2388に存在する事後条件で非const引数を参照できないという問題を解決することができます。これは事後条件から参照される実引数がタイミングによって変化しうることで条件の意味が変わってしまうことを防止するために現在(MVP)では禁止されています。

P2463R0 Slides for P2444r0 The Asio asynchronous model

P2444の解説スライド。

ASIOの非同期モデルとExecutor、非同期処理グラフの構成などが作者自らによって解説されています。

P2464R0 Ruminations on networking and executors

P0443のExecutorを使用するNetworking TSを標準化すべきではないという提案。

この提案で問題とされているのはNetworking TSの完了ハンドラがExecutorそのものがエラーを起こさないと仮定している事、およびP0443のexecutorにエラーや処理の成否を通知するチャネルが無いこと、Networking TSのExecutorと完了ハンドラによる非同期モデルはsender/recieverと比較するとジェネリックでも構成可能でも無いことです。

Networking TS(ASIO)の非同期処理ではI/O処理の継続作業を完了ハンドラで行います。完了ハンドラではAssociated Executorを取得し利用することでそのI/O処理や完了ハンドラの実行されているコンテキストに継続作業を投入することができます。このAssociated ExecutorはP0443のexecutorではありますがschedulerではなく、処理のスケジューリングに伴うエラーや、先行する処理の失敗を継続処理に通知する方法がありません。そして、Associated Schedulerのようなものを取得するAPIはなく、非同期処理グラブの構成もその場しのぎ的な書き方しかできません。また、実行コンテキストをAssociated Executor以外のExecutorのコンテキストに変更する方法もありません。

Executorライブラリの導入後、幾つものExecutorが作成されることになり、そこではExecutorの表現力不足に伴う問題を解決する事を繰り返し行う必要が出てきます。Executorはワークランナーの抽象化でしかなく、汎用的なエラー処理/検出の方法を提供するものではありません。従って、Networking TSのおよびP0443のexecutorとは、schedulerである必要があります。schedulerexecutorと同等の能力を持ちながら、recieverによる成否とスケジュールエラーの通知チャネル、senderによる非同期処理グラフのジェネリックな構成をサポートしています。

その場合、Networking TSの非同期処理は完了ハンドラの代わりにsenderを受け取るようになるでしょう。senderはあらかじめ構成された非同期処理のグラフを示す非同期エージェントであり、Networking TSから提供されるschedulerをそのまま使用することも、別のschedulerに実行コンテキストをスイッチすることもできます。さらに、senderによる構成では、前の処理およびスケジューリング段階でエラーが発生している場合後続の処理は実行されず、そのエラーをハンドリングする方法が提供されます。そして、それらの処理はジェネリックに構成することができ、特定のschedulerおよび特定の継続操作に依存せず、完了ハンドラのようにアドホック的ではありません。

// 擬似コードによる非同期I/Oと継続の例
void run_that_io_operation(scheduler auto sched,
                           sender_of<network_buffer> auto wrapping_continuation)
{
  // 特定のI/O操作
  launch_our_io();
  // I/O結果の取得
  auto [buffer, status] = fetch_io_results_somehow(maybe_asynchronously);
  // ユーザーの指定した継続を、ユーザーの指定したschedulerで実行する
  run_on(sched, queue_onto(wrapping_continuation,
                           make_it_queueable(buffer, status)));
}

ここでは、schedulerの具体的な型もsenderwrapping_continuation)の具体的な型も知る必要はありません。この関数の作者が詳しく知る必要があることはI/O操作に関することのみです。継続ラッパ(wrapping_continuation)が何をするかも知る必要がなく、それおよびそれがどこで実行されるのかを決めるのはこの関数を利用するユーザーです。

ユーザーはsenderの先に自由に継続を構成でき、そのユーザーがラップした処理を使用する別のユーザーもまた同様の方法によって継続をカスタマイズすることができます。この関数の作者は上位のユーザーが何をしたのかを知ることなくI/O処理を実行し、schedulersenderを投入でき、上位ユーザーはI/Oが何をしたのか継続をどう実行したのかを知ることなく、その結果から変換された指定した継続を実行することができます。

多数の実行戦略(executor/scheduler)とさらに多数の継続(完了ハンドラやsender)をサポートするために、Networking TSは多数のもの(追加の関数の変種)を必要としますがP2300(sender/reciever)では1つだけですみます。

P0443のexecutorはこれができません。executorはあらゆる種類の失敗やエラーを通知する方法を持ちません。そして、Networking TSでの入力実行戦略はexecutorのみです。

この提案が示しているのは次の2つの方向性です。

  1. Networking TSのExecutorモデルをP0443からP2300へ変更する
  2. C++23 Networking TSの標準化よりも、sender/reciever(P2300)の開発に集中する
    • ネットワークライブラリはその完成後にそれにフィットするものを標準化する

筆者の方は2つ目を推しているようです。

P2465R0 Standard Library Modules std and std.all

P2465R1 Standard Library Modules std and std.compat

標準ライブラリモジュールについて最小のサポートをC++23に追加する提案

これは以前のP2412R0を受けて、具体的にその内容を詰めるものです。

P2412R0のLEWGのレビューにおいて問題となった最大の事項は、import std;stdモジュール)がstd名前空間ではなくグローバル名前空間にあるものを含むかどうかという点でした。現在のC++標準では、<meow.h>::meowを確実に提供しstd::meowを提供するかは分かりません、<cmeow>::meowstd::meowの両方を確実に提供します。これは説明も対処も厄介な問題であり、またグローバル名前空間の暗黙の汚染をもたらします。標準ライブラリモジュールはこの問題を解決する唯一の機会だと言えます。

ただ、stdモジュールがグローバル名前空間にあるものを提供しない場合、既存のコードの標準モジュール対応が妨げられてしまいます。

そこでこの提案では、2つの標準ライブラリモジュールを提供することでユーザーに選択肢を与え、この問題に対処します。

  • import std;
    • C++標準ライブラリの全てをインポートする。グローバル名前空間を清潔に保ちたい場合
  • import std.compat;
    • std+Cライブラリヘッダの部分をインポートする。C由来のものがグローバル名前空間にインポートされる。既存コードベースの移行時など
    • import std;よりも重くなる可能性があるが、C互換ヘッダはサイズが小さいためそこまで問題にならないはず

stdstd.compatの違いはグローバル名前空間に何かをインポートするかどうかだけです。stdモジュール使用時、グローバル名前空間は清浄に保たれます。例えば、<cmath>std名前空間にもグローバル名前空間にもほぼ同じオーバーロードが定義されますが、import std;の場合はstd::~だけが使用可能となり、import std.compat;の時はそれに加えて::~のものも使用可能になります。

この提案は現在の内容でLWGに転送するために、次のLEWG全体投票にかけられます。

P2466R0 The notes on contract annotations

C++契約プログラミングのビジョンと設計目標について解説した文書。

関数コントラクト(function contract)という概念とコントラクト注釈(contract annotation)の関係を説明し、コントラクト注釈に求められることや役割について説明されています。また、事後条件で関数の非参照const引数を使用する場合の問題点と解決策の考察が行われています。

P2468R0 The Equality Operator You Are Looking For

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

次のコードはコンパイラによって扱いが変化します。

struct S {
  bool operator==(const S&) { return true; }  // (1) 非constメンバ関数
  bool operator!=(const S&) { return false; } // (2) 非constメンバ関数
};

int main() {
  bool b = S{} != S{};  // #1
}

#1の箇所ではユーザー定義!=(2)と(1)から導出された!=の2つが候補として上がります。Sの右辺値との比較でベストマッチものはないのでSの値への変換がどちらでも行われ、メンバ関数const修飾がなされていないことによってどちらの候補も最適とみなされ、その扱いがコンパイラによって変化しています。

  • GCC : (1)を使用する(警告なし)
    • 変換シーケンスの前に、パラメータ型が一致する複数の候補がある場合に生成された候補を無視する
  • clang : (1)を使用する(警告あり)
  • MSVC : (1)を使用する(警告なし)
    • 生成された候補を削除する

godbolt

現在の比較演算子a @ b)の自動導出に伴うオーバーロード解決のルールでは、@そのもの->生成された正順の式(a @' b)->生成された逆順の式(b @' a)の順で優先順位が高くなるというルールがあるのですが、このケース(@!=)では変換なしでベストマッチする!=が存在しないため、定義されているものと生成されたものとで優先順位が同等になってしまっているようです。

コンパイラから見ると先ほどの例は、次のようなコードと同じことをしています

struct S {
  friend bool f(S, const S&) { ... }
  friend bool f(const S&, S) { ... }
};
bool b = f(S{}, S{});

この提案では、現在の厳格なルールをMSVCにて実装(出荷されているMSVCの実装とは異なる)し、59のコードベースの調査を行なっています。その結果、20のプロジェクトでこの問題によるコンパイルエラーが観測されました。

template <typename T>
struct Base {
  bool operator==(const T&) const;
};

struct Derived : Base<Derived> { };

bool b = Derived{} == Derived{};

このケースでは、派生クラスから基底クラスへの変換が発生し、同様の問題に陥っています。これは受け入れられるべきです。

template <bool>
struct GenericIterator {
  using ConstIterator = GenericIterator<true>;
  using NonConstIterator = GenericIterator<false>;

  GenericIterator() = default;
  GenericIterator(const NonConstIterator&); // (1)

  bool operator==(ConstIterator) const;
};
using Iterator = GenericIterator<false>;

bool b = Iterator{} == Iterator{};

このケースは、IteratorGenericIterator<false>)の比較を暗黙変換コンストラクタ(1)を通すことによって、ConstIteratorGenericIterator<true>)の比較として実行しています。暗黙変換が行われることによって同様の問題が起きています。これは意図的であるので受け入れられるべきです。

struct Iterator {
  Iterator();
  Iterator(int*);

  bool operator==(const Iterator&) const; // (1)
  operator int*() const;  // (2)
};

bool b = nullptr != Iterator{};

このケースは、(2)によってIteratorからポインタへ暗黙変換することで、!=を提供しようとしています。暗黙変換が行われることによって同様の問題が起きています。しかしこの場合、逆順の==使用時にも暗黙変換によって組み込み==が使用されてしまい、より最適なはずのnullptrからIteratorを構築して==(1)が使用されないという問題があります。このケースはリジェクトされるべきです。

using ubool = unsigned char;

struct S {
  operator bool() const;
};
ubool operator==(S, S); // (1)

ubool b = S{} != S{};

これは厳密には先程までの問題と関係がありません。この例がエラーとなるのは、(1)の==の戻り値型がboolではないからです。!=の導出に使用される==の戻り値型は(cv)boolでなければならず、そうでない場合オーバーロード解決で選ばれるとコンパイルエラーとなります。

GCCの実装では、オーバーロード解決において変換を考慮する前に、候補集合の中に同じパラメータ型を持つ候補があり片方が生成された候補である場合に、その生成された候補を以降のオーバーロード解決で無視することで以前の振る舞いを維持しようとしています。このGCC実装をMSVCにも実装して再度テストしたところ、コンパイルに失敗したのは10プロジェクト(-10)となりました(上記の受け入れたい/リジェクトしたいケースは意図通りとなっている)。

この提案では、GCCの実装をベースとして実装の自由度も考慮して、「生成された演算子候補について、同じスコープで宣言された同じシグネチャを持つ別の候補が存在し、その候補が生成されていない(ユーザー定義である)候補である場合、生成された演算子オーバーロード候補から除外される」、というルールをオーバーロード解決のルールに追加することを提案しています。それによって、最初の例のようなコードは曖昧ではなくなり、上記のアクセプトしたい既存コード例はコンパイルされリジェクトしたいコード例はコンパイルされない、という振る舞いを手に入れることができます。

P2469R0 Response to P2464: The Networking TS is baked, P2300 Sender/Receiver is not.

P2464R0 Ruminations on networking and executors」の主張に反論する文書。

ASIO(NetworkingTS、以下ASIOとは=NetworkingTSも意味する)の定義する非同期モデルにおけるExecutorとは、非同期処理のtail callのカスタマイゼーションポイントであるという前提を説明し、P2300の抽象化はschedulerの具体的実装を知っていなければ一般的なアルゴリズムを構成できず、P2300は非同期モデルを定義するものではなく非同期処理グラフを作成するためのDSLに過ぎず、ASIO/NetworkingTSの非同期モデルはP2300のDSLを実装することができるより基礎的なモデルであると主張しています。

P2470R0 Slides for presentation of P2300R2: std::execution (sender/receiver)

P2300の紹介・解説スライド。

P2300のschedulersender/recieverによるアルゴリズムの紹介とそれによる非同期処理グラフの構成、thenアルゴリズムの実装解説、コルーチンとsenderの親和、senderにおけるキャンセルなどについて解説されています。

また、P2300の実装(libunifex)がFacebookにて広く実運用されているほか、NVIDIAがその実現にコミットしていくことや、Bloomberg社内でも好印象を得ていることが紹介されています。

P2471R0 NetTS, ASIO and Sender Library Design Comparison

P2471R1 NetTS, ASIO and Sender Library Design Comparison

ASIOとNetworkingTSとP2300(P0443)の設計を比較する文書。

それぞれのライブラリのモデルや要求することなどをコンセプトによって表現し、表にまとめて比較しています。

P2472R0 make_function_ref: A More Functional function_ref

function_refに適応的に型消去させるためのヘルパ関数make_function_ref()を追加する提案。

function_refは関数呼び出し可能なものに対するviewであり、主として関数引数で任意のCallableなものを受け取るときに使用します。そこでは、テンプレートによる受け取りとstd::functionによる受け取りのいいとこ取りのような性質を利用できます。

// テンプレートによる受け取り
// 関数のシグネチャなどの情報がなく、追加の制約が必要になる
// 可読性が低下し、複雑さが増大する
template<typeanme F>
void f(F&& f);

// std::functionによる受け取り
// 所有権が不明(`std::reference_wrapper`を保持しているかもしれない)、空かもしれない
// 型消去のための追加のオーバーヘッド(動的確保など)が必要となる
void f(std::function<bool(int)> f);

// function_refによる受け取り
void f(function_ref<bool(int)> f);

function_refを使用することによって、ステートフルなラムダを含めたあらゆるCallableを受け取れるようにしながら、求められている関数の情報が最小の形で引数型に表示され、追加のオーバーヘッドを回避できます。そして、function_refviewであり常に所有権を持ちません。

function_refは次のように実装されます。

template<typename F>
class function_ref;

template<typename R, typename... Args>
class function_ref<R(Args...)> {
  void* erased_object;
  R(*erased_function)(Args...);

public:

  template<typename F>
  function_ref(F&&);

  // ...
};

function_refはメンバとして2つのポインタを持ち、erased_objectは渡された関数のオブジェクトそのもの(ファンクタなど)のポインタを、erased_functionはその関数呼び出し演算子のポインタを保持します。

function_refはファンクタ/ステートフルラムダ、関数ポインタ/ステートレスラムダ、メンバ関数ポインタ、の3種類のCallableを保持することになりますが、2つのメンバがフルに活用されるのはファンクタ/ステートフルラムダの時だけで、それ以外のケースではerased_objectは有効利用されていません。メンバ関数ポインタに対するthisやフリー関数の最初の引数などを束縛した上でfunction_refに渡すにはラムダ式などでラップする必要がありますが、erased_objectを有効利用すればその必要なく一部の引数を型消去しながら束縛することができそうです。

このような単一関数に対する実行時ポリモルフィズムは、C#のデリゲートなどOOPの世界でよく見られ、C++においてもコミュニティから類似のもののアイデアがいくつか提示されています。それらは共通して、function_refの2つのメンバを直接指定して初期化するコンストラクタによって実装可能です。

この提案はその実現のため、function_refにその2つのポインタメンバを直接初期化可能なコンストラクタを追加した上で、その初期化をサポートするmake_function_ref()ヘルパ関数を追加するものです。

template<typename F>
class function_ref;

template<typename R, typename... Args>
class function_ref<R(Args...)> {
  void* erased_object;
  R(*erased_function)(void*, Args...);

public:

  template<typename F>
  function_ref(F&&);

  // 追加するコンストラクタ
  function_ref(void* obj, R(*func)(void*, Args...))
    : erased_object{obj}
    , erased_function{func}
  {}

  // ...
};


template<auto mf, typename T>
  requires std::is_member_function_pointer<decltype(mf)>::value
auto make_function_ref(const T& obj) {
  return function_ref(std::addressof(obj), mf);
}

template<auto mf>
  requires std::is_member_function_pointer<decltype(mf)>::value
auto make_function_ref() {
  return function_ref(mf);
}

template<auto f>
  requires is_function_pointer<decltype(f)>::value
auto make_function_ref() {
  return function_ref(f);
}

// 他多数のオーバーロード

make_function_refはNTTPとして(メンバ)関数ポインタを、関数引数で束縛対象のオブジェクトを受け取り、それらを指定してfunction_refの2つのメンバを初期化します。例えば次のように利用します

struct bar {
  void baz(bool b, int i, float f) {
      std::cout << "bar::baz" << std::endl;
  }

  void baznoe(bool b, int i, float f) noexcept {
      std::cout << "bar::baznoe" << std::endl;
  }
};

void third_party_lib_function1(tl::function_ref<void(bool, int, float)> callback) {
  callback(true, 11, 3.1459f);
}

void application_function_ref(function_ref<void(bar*, bool, int, float)> callback) {
  bar b;
  callback(b, true, 11, 3.1459f);
}

void free_baz1(bool b, int i, float f) {
  std::cout << "free_baz1" << std::endl;
}

int main() {
  bar b;

  // ステートフルラムダ(make_function_refがない場合の渡し方)
  third_party_lib_function1([&b](bool b1, int i, float f){ b.baz(b1, i, f); });
  // メンバ関数ポインタとthis(上記と同等な渡し方)
  third_party_lib_function1(make_function_ref<&bar::baz>(b));
  // 関数ポインタのみ
  third_party_lib_function1(make_function_ref<free_baz1>());

  // メンバ関数ポインタのみ(thisは後から渡す)
  application_function_ref(make_function_ref<&bar::baz>());
  application_function_ref(make_function_ref<&bar::baznoe>());
}

P2473R0 Distributing C++ Module Libraries

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

この提案はP2409R0をベースとして、そこで挙げられている要件に沿うような規則を定めようとするものです。

パッケージマネージャを使用してビルド済みのライブラリを配布する環境では、ライブラリのコンテンツはディスク上のファイルとして表されているはずです。この提案はC++モジュールをそれらのビルド済みファイルとして配布できるようにするための規則を定めることを目的としています。

この提案の目標は以下の3点で、それ以外の点は目的としていません

  1. モジュールの発見可能性
    • ビルドシステムとパッケージマネージャ間で決定されたモジュール検索パスが与えられた時、それを用いてシステムで利用可能なモジュールを見つけるための規則を定める
  2. モジュールインターフェースのパース手順
    • C++モジュールでは、モジュールの解析とそれを使用する翻訳単位の区別が導入されていること、中間のバイナリモジュールインターフェースファイルが相互運用不可能であることから、この提案ではモジュールインターフェースソースファイルのパース方法を支持するメカニズムを定義する
  3. 依存関係の検出を最適化する規則
    • モジュールの依存関係グラフ構築の際に、プリプロセッサの実行を回避することは望ましい最適化の一つであるが必須ではない。この提案は、その選択をした実装者のための規約を定める

提案している規則は以下のようなものです。

モジュール名はファイル名と対応する

import宣言で使用されるモジュール名は次の規則によってファイル名に変換される。

モジュール名 検索ルートからの相対パス
foo foo.ixx
foo.bar foo/bar.ixx
foo.bar:baz foo/bar.part/baz.ixx

これは、モジュールのインストールプロセスがユーザーに変わって翻訳することを想定している。これにより、ビルドシステムの外部からモジュールを消費(importに対応した読み込み)する際に決定論的な探索が可能となる。

モジュールを消費する手順

モジュールを消費する手順はモジュールインターフェースファイルとともに(同じディレクトリに)配置され、拡張子は.meta-ixx-info

このファイルは次のキーを持つJSONオブジェクトとしてエンコードされる

  • include_path : インターフェース単位ファイルのプリプロセス時に必要な#include対象ファイルのリスト。標準ヘッダ類は含まれない。
  • definitions : 事前定義マクロのキーと値のペア。
  • imports : 対応するモジュールインターフェース単位によってインポートされているモジュール名のリスト。これは、ビルドシステムがモジュール単位を解析して依存関係を把握するのを回避するためのオプションのフィールド。
  • _VENDOR_extension : ベンダーはビルドシステムで使用される可能性のあるメタデータ拡張機能を指定するためにこのフィールドを使用できる

この規則の前提として、プリプロセス時に適用されるオプション以外のオプションは、モジュールを消費する翻訳単位とモジュールインターフェース単位のパースの間で一律に適用されなければならない。それは例えば、言語バージョンを指定するフラグやABI互換に影響するフラグなど。

また、モジュールを消費する翻訳単位からのインクルードパスとマクロ定義はインポートされるモジュールを消費する際に使用しないことも想定している。

バイナリモジュールインターフェースファイルの配布

バイナリモジュールインターフェース(BMI)の相互運用可能な範囲は非常に限られているが、コードがほぼ同じコンパイラによって生成される環境では重要な最適化となる。例えば、ほとんどのGNU/Linuxディストリビューションが該当する。ただし、BMIファイルがあるコンパイラに適用可能かどうかが識別可能である必要がある。したがって、このルールではコンパイラはサポートする互換性と同じくらい一意の識別子を提供する必要がある。

その識別子は、.bmi.vendor.idのようなパターンで用意する。例えば、g++がUUID-v4を使用してBMIファイルを再利用可能であることを表明するには、foo.barモジュールの場合ファイル名は「foo/bar.bmi.g++.20734238-4fc7-4725-bf22-be9700326774」になる。

この規則では、このような識別子のうち、コンパイラは1つの入力形式だけをサポートすることを想定している。コンパイラはファイル名の有効な拡張子となる任意の識別子を使用でき、複数の識別子パターンをサポート可能だが、ここでそれを規定すると探索の複雑さが増大する。

検索順序

モジュールの探索時は、最初にコンパイラ固有のBMI(標準ライブラリのものなど)を探索してから2回目の探索で.ixxファイルと.meta-ixx-infoファイルを探索できるはず。これにより、異なるビルドシステム間で使用するためにBMIファイルをキャッシュするローカルオーバーレイツールを作成できる。

BMIファイルの存在を最適化しない

ビルドシステムが現在のビルドと互換性のあるBMIファイルの存在を確認した時、依存関係の探索をやめて直接それを消費することが有効な最適化だが、そうすると、ビルドシステムは連携するツールからモジュールのインターフェースのパースを再現する方法の詳細を隠蔽してしまう。したがって、相互運用性を維持したいビルドシステムはモジュールインターフェースをパースする必要がないと判断した場合でもモジュールインターフェースのパースを継続して、その情報を現在のコンパイルデータベースのように利用する必要がある。

Discovery tooling

ライブラリとして提供されるモジュールの推移的な関係を解決するために、全ての必要なモジュールを解析するものが必要となる。そのために、c++-modules-configと呼ばれるツールを提案する。

このツールはモジュールの探索パス、BMIファイルの識別子、解決が必要なモジュール名のリストを入力とし、ビルドに関係するモジュールの完全なリスト、インターフェースのソース、インターフェースをパースするのに必要な手順、およびモジュールの依存関係を出力する。このツールは再帰的な探索を行い、ビルドシステムが消費する必要のあるシステムによって提供される全てのモジュールのパースを計画するのに十分な記述を返す。これにより、利用可能だが消費される予定のないモジュールは除外される。

このツールの出力はP1689R4のフォーマットと互換性を持たせる必要があり、またこの出力はmodule_config.jsonという名前で、現在compile_commands.jsonが保存されている場所と同じ場所に保存される必要がある。

P2475R0 WG21 2021-10 Virtual Meeting Record of Discussion

2021年10月4日(北米時間)に行われた、WG21全体会議のデイスカッション議事録。

CWG/LWG/LEWGの投票時のディスカッションの様子が記録されています。

P2477R0 Allow programmer to control and detect coroutine elision by static constexpr bool should_elide() and

コルーチンの動的メモリ確保を避ける最適化を制御し、起こったことを検出するAPIを追加する提案。

コルーチンはサスペンドと再開をサポートするためにその状態(コルーチンステータス)を保存しておく領域を必要とします。現在のC++コルーチンでは、この領域の確保は動的メモリ確保(operator new)によって行われ、それはコストがかかります。

それを軽減するために、HALOと呼ばれる動的メモリ確保を回避する最適化がデザインされており、これをcoroutine elisionと呼びます。clang/LLVMではそれはCoroElideとして実装されています。C++標準では名前付けされていないもののcoroutine elisionは許可されています(つまり必須ではない)。

これはコンパイラの最適化であり、必ず行われるわけではありません。そのため、コルーチンを利用するプログラマはこの最適化を制御することも、それが行われたことを察知することもできません。

また、組み込みのようにリソースが限られている環境ではcoroutine elisionを無効化したうえでpromise_type::operator newが静的ストレージのバイト配列からの領域を返すようにすることが好まれることがあるようです。しかし、coroutine elisionを無効化する方法もまた標準では提供されていません。

この提案では、coroutine elisionを制御するためのpromise_type::should_elide()coroutine elisionを検出するためのstd::coroutine_handle<>::elided()の2つの関数を追加することを提案しています。これによって、プログラマcoroutine elisionを制御し検出できるようになります。

should_elide()は次のようにcoroutine elisionの実行についてをコンパイラに指示します

  • promise_typeのスコープでshould_elide()という関数を見つけることができ、コンパイル時に評価した結果が
    • trueに評価された場合 : coroutine elisionが保証される
    • falseに評価された場合 : coroutine elisionが行われないことが保証される
  • should_elide()が見つからない、コンパイル時に評価できない場合
    • 従来通り(coroutine elisionコンパイラの裁量で行われる)

should_elide()promise_typeメンバ関数として実装されるもので、promise_typeはコルーチンの動作をカスタムするカスタマイゼーションポイントとしてユーザーが定義します。したがって、should_elide()もまた必要であればユーザーが定義するものであり、それはconstexpr関数として定義しなければなりません。

elided()std::coroutine_handleメンバ関数として追加され、coroutine elisionが行われていた時にtrueを返し、行われていない場合にfalseを返します。

namespace std {
  template<>
  struct coroutine_handle<void> { 
    // ...

    bool elided() const noexcept; // 追加
    
    // ...
  };

  template<class Promise>
  struct coroutine_handle<Promise> {
    // ...
    
    bool elided() const noexcept; // 追加
    
    // ...
  };
}

P2478R0 _Thread_local for better C++ interoperability with C

C++thread_local変数は動的初期化とデストラクタ呼び出しを伴うことができます。その初期化は、スレッド起動時に非ブロック(名前空間スコープ)変数に対して行われるか、最初に変数が使用されるときに行われる可能性があります。その主な実装は、変数が使用される場所に関数を挿入し、その関数が呼ばれることで非ブロック変数の遅延初期化を実行します。

そのような関数とその呼び出しによって実行時にサイズと時間のオーバーヘッドがかかります。これは変数が動的初期化もデストラクタ呼び出しも必要ないことが分かっていれば回避可能です。

このことは、単にオーバーヘッドがかかるだけではなく、Cの_Thread_localとの非互換を生み出しています。Cの_Thread_localC++thread_localとしての使用に必要なもの(初期化・破棄に必要な追加の関数など)を提供しません。このため、C_Thread_local変数をC++コードから参照しようとするとリンクエラーを起こすことがあります。

また、C++で定義された動的初期化されるか非トリビアルなデストラクタをもつ静的変数(非スレッドローカル)にCからアクセスすることは、C++から同じものにアクセスするのと比べて危険ではありません。なぜなら、主要な実装はプログラム起動時にそれらの初期化を行うためです。しかし、同様に宣言されているスレッドローカルな変数にアクセスした場合は同じことは言えません。Cからのアクセスは初期化前の変数を観測する可能性が高くなります。

この提案はC23に対して、_Thread_localthread_localキーワードで置き換えるのをやめるように提案するものです。上記のような問題があるため、安易にキーワードをC++と共通化してしまうことは危険です。一方、C++が上記問題の回避を行いたい場合にCと同様のセマンティクスで_Thread_localを使用する方向性を示しています。

P2479R0 Slides for P2464

P2464R0 Ruminations on networking and executorsの紹介スライド。

後半ではP2469に対する反論も行われています。

P2480R0 Response to P2471: "NetTS, Asio, and Sender library design comparison" - corrected and expanded

P2471R1の訂正や追記を行う文書。

P2471R1はASIO/NetrworkingTSとP2300との比較を行っている文書ですが、その内容について不正確な部分を修正したり追記したりする文書です。

P2481R0 Forwarding reference to specific type/template

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

std::tuple<Ts...>にはstd::tuple<Us...>からの変換コンストラクタが存在します。それは、Us...のそれぞれが対応するTs...のそれぞれに変換可能であれば利用可能となりますが、std::tuple<Us...>の状態によって4つの定義を必要とします。

template <typename... Ts>
struct tuple {
  template <typename... Us>
      requires sizeof...(Ts) == sizeof...(Us)
            && (is_constructible_v<Ts, Us&> && ...)
  tuple(tuple<Us...>&);

  template <typename... Us>
      requires sizeof...(Ts) == sizeof...(Us)
            && (is_constructible_v<Ts, Us const&> && ...)
  tuple(tuple<Us...> const&); // (1)

  template <typename... Us>
      requires sizeof...(Ts) == sizeof...(Us)
            && (is_constructible_v<Ts, Us&&> && ...)
  tuple(tuple<Us...>&&);      // (2)

  template <typename... Us>
      requires sizeof...(Ts) == sizeof...(Us)
            && (is_constructible_v<Ts, Us const&&> && ...)
  tuple(tuple<Us...> const&&);
};

これは簡易な例でしかないので正確ではありません。重要なことは、tuple<Us...>constの有無と値カテゴリの組み合わせで2x2=4つのコンストラクオーバーロードが必要となるうえ、制約もそれに応じて微妙に細部を調整しなければなりません。

これはかなり冗長なコードであるうえ、これらがオーバーロードであることから、ある適切なコンストラクタがその制約によって適切に候補から外れた時でも、別のコンストラクタが呼ばれることがあります。

void f(tuple<int&>);
auto g() -> tuple<int&&>;

void h() {
  f(g()); // ok!
}

これは、tuple<int&&> -> tuple<int&>の構築を行っているもので、int&& -> int&のような変換は不可能なので失敗してほしいし失敗するはずです。しかし実際には、(2)のコンストラクタが制約を満たさないために除外された後、(1)のコンストラクタが利用されます。g()の戻り値は右辺値であり、(1)のコンストラクタのtuple<Us...> const&でも受けることができて、その制約中のUs const&int&& const & -> int&となってint& -> int&の変換が可能であることから(1)のコンストラクタを呼び出してしまいます。

この問題を回避するためには、コンストラクタテンプレートを1つだけにする必要があります。必要なことは、tuple<Us...>を指定しながら、CV・参照修飾を推論してもらうことです。現在のところそれはできず、上記のように書くかテンプレートをこね回す必要があります。

template <typename... Ts>
struct tuple {
  template <typename Other>
      requires is_specialization_of<remove_cvref_t<Other>, tuple>
            && /* ???? */
  tuple(Other&& rhs);
};

しかし、このコンストラクタテンプレートの制約を正しく書く事は困難で、いい解決策はありません。結局、最低4つの似たようなオーバーロードを用意するのが一番簡易となります。

これと同じ問題は、std::getでも見ることができます。

template <size_t I, typename... Ts>
auto get(tuple<Ts...>&) -> tuple_element_t<I, tuple<Ts...>>&;

template <size_t I, typename... Ts>
auto get(tuple<Ts...> const&) -> tuple_element_t<I, tuple<Ts...>> const&;

template <size_t I, typename... Ts>
auto get(tuple<Ts...>&&) -> tuple_element_t<I, tuple<Ts...>>&&;

template <size_t I, typename... Ts>
auto get(tuple<Ts...> const&&) -> tuple_element_t<I, tuple<Ts...>> const&&;

これも一つの関数テンプレートにまとめることで記述を削減でき先ほどのような問題を回避できますが、やはりその制約を正しく書くことが困難で、std:tupleの派生型に対して機能しなくなってしまいます。

std::optional::transformのようなメンバ関数deducing thisを組み合わせた時にも、別の問題が浮かび上がります。

template <typename T>
struct optional {
  template <typename F>
  constexpr auto transform(F&&) &;

  template <typename F>
  constexpr auto transform(F&&) const&;

  template <typename F>
  constexpr auto transform(F&&) &&;

  template <typename F>
  constexpr auto transform(F&&) const&&;
};

このような冗長なコードは、deducing thisによって次のようにひとまとめにできます。

template <typename T>
struct optional {
  template <typename Self, typename F>
  constexpr auto transform(this Self&&, F&&);
};

しかし、今度はこのSelfの対象とする範囲が広すぎます。ここではstd::optionalの派生型を対象としたくはなく、やってほしいことはCV・参照修飾の推論だけです。

これらの問題は、テンプレートにおいてある程度型を指定しながらでもCV・参照修飾の推論が可能となってくれれば解決することができます。この文書では、いくつかの構文の候補を提示するとともにより良いアイデアを募っています(まだ提案には至っていません)。

以下の例では次のようなユーティリティを使用しています

#define FWD(e) static_cast<decltype(e)&&>(e)

template <bool RV, typename T>
using apply_ref = std::conditional_t<RV, T&&, T&>;

template <bool C, typename T>
using apply_const = std::conditional_t<C, T const, T>;

template <bool C, bool RV, typename T>
using apply_const_ref = apply_ref<RV, apply_const<C, T>>;

template <typename T, typename U>
using copy_cvref_t = apply_const_ref<
  is_const_v<remove_reference_t<T>>,
  !is_lvalue_reference_v<T>,
  U>;

T auto&&

これはコンセプトによるプレースホルダ型の制約(range auto&& r)を発展させて、コンセプトを指定する部分により具体的な型を指定できるようにしたものです

template <typename... Ts>
struct tuple {
  template <typename... Us>
  tuple(tuple<Us...> auto&& rhs)
    requires sizeof...(Us) == sizeof...(Ts)
          && (constructible_from<
                Ts,
                copy_cvref_t<decltype(rhs), Us>
              > && ...);
};
template <typename T>
struct optional {
  template <typename F>
  auto transform(this optional auto&& self, F&& f) {
      using U = remove_cv_t<invoke_result_t<F,
          decltype(FWD(self).value())>;

      if (self) {
        return optional<U>(invoke(FWD(f), FWD(self).value()));
      } else {
        return optional<U>();
      }
  }
};
  • 利点
    • 簡潔にやりたいことを実現・表現できる
  • 欠点
    • 正確な型を取得するにはdecltype(param)としなければならない
      • このため、requires節を関数の後ろにしか書けなくなる
    • concept autoの記法と矛盾する(この構文は変換を行わない)

T&&&

先ほどの、T auto&&の代わりにT&&&を導入するものです。(サンプルは省略)

  • 利点
    • tuple<Us...>&&&は派生型からの変換を行うことができる
  • 欠点
    • T auto&&と同じ
    • T&&&という参照トークンそのもの

const(bool)

noexcept(bool)explicit(bool)のように、const&&を条件付きにするものです。

template <typename... Ts>
struct tuple {
  template <typename... Us, bool C, bool RV>
    requires sizeof...(Us) == sizeof...(Ts)
          && (constructible_from<
                Ts,
                apply_const_ref<C, RV, Us>
              > && ...)
  tuple(tuple<Us...> const(C) &&(RV) rhs);
};
template <typename T>
struct optional {
  template <typename F, bool C, bool RV>
  auto transform(this optional const(C) &&(RV) self, F&& f) {
    using U = remove_cv_t<invoke_result_t<F,
        // apply_const_ref<C, RV, T>
        decltype(FWD(self).value())>;

    if (self) {
      return optional<U>(invoke(FWD(f), FWD(self).value()));
    } else {
      return optional<U>();
    }
  }
};
  • 利点
    • const性と参照修飾だけを指定していることが明確になる
    • requires節の配置自由度が保たれる
  • 欠点

Q修飾子

これは全く新しい形のテンプレートパラメータを導入するもので、これをqualifier(修飾子)と呼びます。これは、エイリアステンプレートを推論します

template <typename... Ts>
struct tuple {
  template <typename... Us, qualifiers Q>
    requires sizeof...(Us) == sizeof...(Ts)
          && (constructible_from<Ts, Q<Us>> && ...)
  tuple(Q<tuple<Us...>> rhs);
};
template <typename T>
struct optional {
  template <typename F, qualifiers Q>
  auto transform(this Q<optional> self, F&& f) {
    using U = remove_cv_t<invoke_result_t<F, Q<T>>>;

    if (self) {
      return optional<U>(invoke(FWD(f), FWD(self).value()));
    } else {
      return optional<U>();
    }
  }
};

ここでは、Q<T>QTはそれぞれ別々に推論されますが、Qは次のエイリアステンプレートのいずれかとして推論されます

template <typename T>
using Q = T&;

template <typename T>
using Q = T const&;

template <typename T>
using Q = T&&;

template <typename T>
using Q = T const&&;
  • 利点
    • 推論したCV・参照修飾を適用(利用)するのが容易
    • 正確な型の取得が容易
  • 欠点
    • 斬新な構文であり、奇妙
    • Qの名前が問題となる
    • constだけを推論したいときに、誤用の可能性がある

作者の方はこれらの解決策に納得してないらしく、より良い構文を募るためにこの文書を書いたようです。

おわり