文書の一覧
全部で29本あります。
- N4917 Working Draft, Standard for Programming Language C++
- N4918 Editors’ Report - Programming Languages – C++
- N4919 Programming Languages - C++
- N4920 Working Draft, C++ Extensions for Library Fundamentals, Version 3
- N4921 Editor’s Report: C++ Extensions for Library Fundamentals, Version 3
- N4922 INCITS C++/WG21 agenda: 7-12 November 2022, Kona, HI US
- P0543R2 Saturation arithmetic
- P0792R11 function_ref: a non-owning reference to a Callable
- P0957R9 Proxy: A Polymorphic Programming Library
- P0987R1 polymorphic_allocator instead of type-erasure
- P1030R5 std::filesystem::path_view
- P1985R3 Universal template parameters
- P2348R3 Whitespaces Wording Revamp
- P2495R1 Interfacing stringstreams with string_view
- P2586R0 Standard Secure Networking
- P2587R3 to_string or not to_string
- P2588R1 Relax std::barrier phase completion step guarantees
- P2603R1 member function pointer to function pointer
- P2620R2 Improve the wording for Universal Character Names in identifiers
- P2623R2 implicit constant initialization
- P2631R0 Publish TS Library Fundamentals v3 Now!
- P2636R0 References to ranges should always be viewable
- P2637R0 Member visit and apply
- P2638R0 Intel's response to P1915R0 for std::simd parallelism in TS 2
- P2639R0 Static Allocations
- P2640R0 Modules: Inner-scope Namespace Entities: Exported or Not?
- P2641R0 Checking if a union alternative is active
- P2642R0 Padded mdspan layouts
- P2643R0 Improving C++ concurrency features
- おわり
N4917 Working Draft, Standard for Programming Language C++
N4918 Editors’ Report - Programming Languages – C++
↑の変更点をまとめた文書。
7月の会議で採択された提案とコア言語/ライブラリのIssue解決が適用されています。
N4919 Programming Languages - C++
C++23のCommittee Draft
N4920 Working Draft, C++ Extensions for Library Fundamentals, Version 3
次期標準ライブラリ機能候補の実装経験を得るためのTSである、Library Fundamental TS v3のドラフト文書。
N4921 Editor’s Report: C++ Extensions for Library Fundamentals, Version 3
↑の変更点を記した文書。
この版での変更は、typoの修正などです。
N4922 INCITS C++/WG21 agenda: 7-12 November 2022, Kona, HI US
11月に行われる予定の、WG21全体会議のアジェンダ。
P0543R2 Saturation arithmetic
整数の飽和演算を行うライブラリ機能の提案。
以前の記事を参照
このリビジョンでの変更は、提案する関数を<numeric>
に配置するとともにフリースタンディング機能として指定したことです。
P0792R11 function_ref: a non-owning reference to a Callable
Callableを所有しないstd::function
であるstd::function_ref
の提案。
以前の記事を参照
- P0792R6 function_ref: a non-owning reference to a Callable - [C++]WG21月次提案文書を眺める(2022年01月)
- P0792R8 function_ref: a non-owning reference to a Callable - [C++]WG21月次提案文書を眺める(2022年02月)
- P0792R9 function_ref: a non-owning reference to a Callable - [C++]WG21月次提案文書を眺める(2022年05月)
- P0792R10 function_ref: a non-owning reference to a Callable - [C++]WG21月次提案文書を眺める(2022年06月)
このリビジョンでの変更は、提案する文言の修正のみです。
この提案はLWGの時間がなかったため、C++26をターゲットとしています。
P0957R9 Proxy: A Polymorphic Programming Library
静的な多態的プログラミングのためのユーティリティ、"Proxy"の提案。
以前の記事を参照
- P0957R5 Proxy: A Polymorphic Programming Library - WG21月次提案文書を眺める(2022年02月)
- P0957R6 Proxy: A Polymorphic Programming Library - WG21月次提案文書を眺める(2022年03月)
- P0957R7 Proxy: A Polymorphic Programming Library - WG21月次提案文書を眺める(2022年04月)
- P0957R8 Proxy: A Polymorphic Programming Library - WG21月次提案文書を眺める(2022年06月)
このリビジョンでの変更は、proxy
に適したポインタ型の制約の追加、proxiable
コンセプトの修正、proxy::invoke()
にconst
修飾を付加した、などです。
P0987R1 polymorphic_allocator
instead of type-erasure
std::function
にstd::pmr::polymorphic_allocator
によるアロケータサポートを追加する提案。
std::function
のアロケータサポートはそれが実装されなかったことからC++14で削除されており、現在のstd::function
はアロケータのカスタマイズができなくなっています。一方、Library Fundamental TS v3にあるstd::experimental::function
にはアロケータを受け取るコンストラクタとアロケータを型消去して保持するためのユーティリティが存在しています。
この提案は、そのLFTSv3にあるstd::experimental::function
のアロケータサポートを専用の型消去アロケータではなくC++20で導入されたstd::pmr::polymorphic_allocator<>
で置き換えるものです。
現在のLFTSv3にあるstd::experimental::function
のコンストラクタは次のようになっています。
template<class F, class A> function(allocator_arg_t, const A&, F);
ここのF, A
の2つのパラメータはクラステンプレートには現れていません。ここでは2つの型の型消去が必要となり実装が複雑になります。また、現在のLFTSv3にある型消去アロケータはそれを受け取る他のオブジェクト(ここではstd::experimental::function
)の領域内にうまく配置(スペースの節約)することができるようにより複雑になっています。
この提案はこれを次のように置き換えます
template<class F> function(allocator_arg_t, const pmr::polymorphic_allocator<>&, F);
また同時に、現在使用しているアロケータを取得するためのインターフェースも追加します。
polymorphic_allocator<> get_allocator() const noexcept;
std::pmr::polymorphic_allocator<>
を用いることでアロケータの型消去と複雑な型消去アロケータが不用になり実装が簡単になります。また、この提案ではこのstd::pmr::polymorphic_allocator<>
の保持方法を特に指定しないため、単にメンバとして保持するのではなく型消去のための領域に保持するなどの効率化が図れます。
P1030R5 std::filesystem::path_view
パス文字列を所有せず参照するstd::filesystem::path_view
の提案。
以前の記事を参照
このリビジョンでの変更は
- ベースとなるワーキングドラフトの更新
- 機能テストマクロ
__cpp_lib_filesystem
の値を更新 c_str
クラスの名前をrendered_path
に変更rendered_path
クラスに(not_)zero_terminated_rendered_path
関数を追加- ナル終端の指定をテンプレートパラメータに移動した、
rendered_path::c_str()
を追加 rendered_path
クラスのアロケータカスタムのサポートをSTLアロケータに限定path_view::render()
を追加- 各クラスに
hash_value()
を追加
などです。
P1985R3 Universal template parameters
任意の型、テンプレートテンプレート...、非型テンプレートパラメータなど、テンプレートの文脈で使用可能なものを統一的に受けることのできるテンプレートパラメータ(Universal template parameter)の提案。
以前の記事を参照
R2での変更は、template auto
パラメータに関する共変/反変性に関する懸念と議論の追加、などです。
このリビジョンでの変更は
- 何が検討され、何が提案されているのかが明確になるように提案を書き直し
- 提案のサンプルを拡充
- ユニバーサルエイリアスを追加
- 変数テンプレートテンプレートパラメータを追加
- コンセプトテンプレートパラメータを追加
- ユニバーサルテンプレートパラメータ(UTP)を依存名であると指定
ユニバーサルエイリアスとはこの提案によって導入される、テンプレートで使用できるもののエイリアスのことです。template auto identifier = template-argument ;
のような構文で定義します。
template<template auto U> struct box { template auto result = U; // ユニバーサルエイリアス }; template<template<template auto> template auto Map, template<template auto...> template auto Reduce, template auto... Args> template auto map_reduce_best = Reduce<Map<Args>...>; // ユニバーサルエイリアス
これは型であったり値であったりしますが、コンパイル時の静的なものです。
コンセプト/変数のテンプレートテンプレートパラメータは、既存の型のテンプレートテンプレートパラメータを拡張した構文によって導入されます。
template <auto N> // 非型テンプレートパラメータ template <template </*...*/> typename> // 型テンプレートテンプレートパラメータ template <template </*...*/> auto> // 変数テンプレートテンプレートパラメータ template <template </*...*/> concept> // コンセプトテンプレートテンプレートパラメータ
コンセプトテンプレートテンプレートパラメータによって、コンセプトの制約対象をある型ではなく型のグループのように指定することができるようになります
// あるコンセプトCを満たす要素からなるrangeである template <typename R, template<typename> concept C> concept range_of = ranges::input_range<R> && C<remove_cvref_t<ranges::range_reference_t<R>>>; // 整数の範囲のみ受ける void f(range_of<std::integral> auto&& r); // ムーブ可能な要素による範囲のみ受ける void f(range_of<std::movable> auto&& r);
UTPとコンセプトテンプレートパラメータによって、このようなコンセプトは型と型のグループの両方を制約することができるようになります
// プライマリユニバーサルテンプレート template <typename R, template auto T> constexpr bool is_range_of = delete; // コンセプトによる特殊化 template <typename R, template <typename> concept C> constexpr bool is_range_of<R, C> = C<R>; // 型による特殊化 template <typename R, typename T> constexpr bool is_range_of<R,T> = std::is_same_v<R, T>; // ある型TもしくはコンセプトTを満たす型の要素からなるrangeである template <typename R, template auto T> concept range_of = is_range_of<std::remove_cvref_t<std::ranges::range_reference_t<R>>, T>; // 整数の範囲のみ受ける void f(range_of<std::integral> auto&& r); // ムーブ可能な要素による範囲のみ受ける void f(range_of<std::movable> auto&& r); // char型の範囲を受け取る(この時の順序は・・・?) void f(range_of<char> auto&& r);
この3つの機能はもともと別の提案にあったものですが、UTP及び既存テンプレートと一貫性を保った機能として導入するために、この提案に統合されました。
この提案では、UTP構文を統一的にtemplate auto
としていますが、これはプレースホルダであり名前及びキーワードについては議論中です。EWGでの決定次第では、新しいキーワードが導入される可能性もあります。
P2348R3 Whitespaces Wording Revamp
ユニコードの仕様に従う形で、改行と空白を明確に定義する提案。
以前の記事を参照
- P2348R0 Whitespaces Wording Revamp - [C++]WG21月次提案文書を眺める(2021年04月)
- P2348R1 Whitespaces Wording Revamp - [C++]WG21月次提案文書を眺める(2021年09月)
- P2348R2 Whitespaces Wording Revamp - [C++]WG21月次提案文書を眺める(2021年10月)
このリビジョンでの変更は、提案する文言の修正です。
P2495R1 Interfacing stringstreams with string_view
std::stringstream
がstd::string_view
を受けとれるようにする提案。
以前の記事を参照
このリビジョンでの変更は
- LWG Issue 2496に合わせて提案内容を調整
- 互換が無い、もしくはABI破壊を招く代替手段についての記述を削除
const Char T*
とopenmode
、アロケータを受け取るコンストラクタを削除
std::istringstream
のコンストラクタに欠けていた制約を追加- よく聞かれる質問とその回答についてのセクションを追加
などです。
これによって、以前のサンプルで許可されるようになっていた文字列リテラルとopenmode
、アロケータを渡す例は禁止されるようになります。
const std::ios_base::openmode mode; const std::allocator<char> alloc; const std::string str; // mystringはstring_viewに暗黙変換可能だとする const mystring mstr; std::stringstream s0{""}; // ok std::stringstream s1{"", alloc}; // ng -> ok -> ng std::stringstream s2{"", mode, alloc}; // ng -> ok -> ng
P2586R0 Standard Secure Networking
Networking TSに代わる、セキュアネなットワークライブラリの提案。
この提案は次のような特徴があります
また、この提案は現在のところ、非同期やコルーチン対応を含んでいないため(asioで問題となった)P2300とはあまり関連がありません。
HTTPSでwebページを取得するサンプル
// 接続先のホスト static constexpr string_view test_host = "github.com"; // HTTPリクエストペイロード static constexpr string_view get_request = "GET / HTTP/1.0\r\nHost: github.com\r\n\r\n"; // ライブラリが推奨する、現在のプラットフォームのデフォルトTLSソケットソースを取得 // tls_socket_source_registryには異なるプロパティによって様々なTLSソケットソースが登録される // tls_socket_source_ptrはスマートポインタ型ではあるが、必ずしもヒープ領域のオブジェクトを指していない // 参照カウントシングルトンであったり、静的なシングルトンであったりして、動的確保を要求しない // この提案の他の部分でも同様 tls_socket_source_ptr tls_socket_source = tls_socket_source_registry::default_source().instantiate().value(); // TLSソケットソースから、多重化可能な接続ソケットを作成する // 多重化可能とは、1つのソケットによって異なるスレッドから複数のI/Oを実行可能であることを意味する // 例えば、Linuxではnon-blocking、WindowsではOVERLAPPED tls_byte_socket_ptr sock = tls_socket_source->multiplexable_connecting_socket(ip::family::any).value(); { // 接続先ホストの443番ポートへ接続する、タイムアウトは5秒 // デフォルトでは、ローカルシステムの証明書ストアを使用して接続先ホストの証明書を検証する // この関数は利便性のためのAPIであり、接続の各ステップは個別のAPIによって順番に実行していくこともできる result<void> r = sock->connect(test_host, 443, std::chrono::seconds(5)); if (r.has_error()) { if(r.error() == errc::timed_out || r.error() == errc::host_unreachable || r.error() == errc::network_unreachable) { std::cout << "\nNOTE: Failed to connect to " << test_host << " within five seconds. Error was: " << r.error().message() << std::endl; return; } r.value(); // throw the failure as an exception } } // ここで出力される文字列は実装定義、カンマ区切りであることを提案する std::cout << "\nThe socket which connected to " << test_host << " negotiated the cipher " << sock->algorithms_description() << std::endl; // ホストへのHTTP/1.0リクエスト内容を記述するための定数バッファの作成 // tls_socket_handleではこれはstd::span<const byte> tls_socket_handle::const_buffer_type get_request_buffer(reinterpret_cast<const llfio::byte*>(get_request.data()), get_request.size()); // HTTP/1.0リクエストをホストへ送信する size_t written = sock->write({get_request_buffer}).value(); // 結果のテスト(事後条件) TEST_REQUIRE(written == get_request.size()); // リクエスト結果(githubフロントページ)を取得 // HTTP/1.0の動作として、すべてのデータが送信されると接続が閉じられ、read()は0を返す std::vector<byte> buffer(4096); size_t offset = 0; for (size_t nread = 0; (nread = sock->read({{buffer.data() + offset, buffer.size() - offset}}, std::chrono::seconds(3)).value()) > 0;) { offset += nread; if (buffer.size() - offset < 1024) { buffer.resize(buffer.size() + 4096); } } buffer.resize(offset); // 取得した結果の出力(最初の1024バイト分) std::cout << "\nRead from " << test_host << " " << offset << " bytes. The first 1024 bytes are:\n\n" << std::string_view(reinterpret_cast<const char*>(buffer.data()), buffer.size()).substr(0, 1024) << "\n" << std::endl; // TLS接続をシャットダウンし、ソケットを閉じる sock->shutdown_and_close().value();
提案には他にも、多重化されたTLSサーバのサンプルと、サードパーティーのソケット実装をTLSでラップするサンプルが記載されています。
この提案は、昨年の秋ごろにLEWGにおいてC++標準ネットワークライブラリとしてasioの追求を(一旦)停止することを決定した後に、LEWGの数人のメンバから、LEWGの歴史的な懸念事項に応える標準ネットワークの提案を考案するように依頼されて書かれたもののようです。
この提案の内容は著者自身によって参照実装がなされており、そこではLLFIOというライブラリの一部としてGCC/clang/MSVC等C++14以上のコンパイラによってx86/x64/ARM環境で動作することが確かめられているようです。ただし、LLFIOというライブラリそのものは数年の実装・実用経験を持っているものの、この参照実装はこの提案のためにかかれたもので実装経験としては弱いとも注記されています。
P2587R3 to_string
or not to_string
std::to_string
の浮動小数点数出力を修正する提案。
以前の記事を参照
- P2587R0
to_string
or notto_string
- WG21月次提案文書を眺める(2022年05月) - P2587R1
to_string
or notto_string
- WG21月次提案文書を眺める(2022年07月) - P2587R2
to_string
or notto_string
- WG21月次提案文書を眺める(2022年08月)
このリビジョンでの変更は、LEWG投票の結果を追記した事です。
P2588R1 Relax std::barrier phase completion step guarantees
std::barrier
のバリアフェーズ完了時処理が、同じバリアで同期する任意のスレッドから起動できるようにする提案。
以前の記事を参照
このリビジョンでの変更は、SG1での投票結果を追加したことと実装へのリンクを追加したことです。
SG1での議論と投票の結果では、この提案の方向性で進めることとそれをDRにすることに合意が取れているようです。
P2603R1 member function pointer to function pointer
メンバ関数ポインタから、基底クラスの関数を明示的に呼び出せるようにする提案。
以前の記事を参照
このリビジョンでの変更は
member_function_pointer_to_free_function_pointer
からto_free_function_pointer
へ名前を変更to_free_function_pointer
をconsteval
関数とした- 関数ポインタのサポート
- 関数ポインタが渡された場合に何もしない
などです。ただし、この提案はまだ標準への文言を含んでいないため、これらの事は文章による説明に対して適用されています。
P2620R2 Improve the wording for Universal Character Names in identifiers
ユニコード文字名によって指定するユニバーサルキャラクタ名(名前付文字エスケープ)を識別子に使用した時の制限を解除する提案。
以前の記事を参照
- P2620R0 Lifting artificial restriction on universal character names - WG21月次提案文書を眺める(2022年07月)
- P2620R1 Lifting artificial restriction on universal character names - WG21月次提案文書を眺める(2022年08月)
このリビジョンでこの提案は、当初の提案を放棄しています。
この提案はユニバーサルキャラクタ名(UCN)を識別子に使用した時の振る舞いを明確化しようとするものでした。しかし、キーワード文字列中にUCNが含まれてしまう場合にその扱いをどうするかの良いモデルが無く、マクロ名やプリプロセッシングディレクティブ、文脈依存キーワードを考慮するとそのようなモデルの構築はより困難になります。それを許可することも拒否することも実装の負担と過度の複雑さにつながるため望ましくなく、当初の目的(識別子としてUCNを使用した際の挙動の明確化)を諦めることになったようです。
ただし、この提案の文言が行っていたUCNの記述の整理の部分についてはそのまま取り入れたいということで意見が一致し、この提案は現在それだけを含んでいます。この内容については、何らかの機能追加や変更をするものではなく、単純に規格書の記述の整理のみです。
P2623R2 implicit constant initialization
一時オブジェクトへの参照によるダングリング発生を削減する提案。
以前の記事を参照
- P2623R0 implicit constant initialization - WG21月次提案文書を眺める(2022年07月)
- P2623R1 implicit constant initialization - WG21月次提案文書を眺める(2022年08月)
このリビジョンでの変更は
- 「Other Anonymous Things」セクションの追加
- ラムダ式とコルーチンにおける即時ダングリングについて(この提案によって解決可能)
- 「Summary」セクションの文書等の調整
- 「Frequently Asked Questions」セクションの追加
- 全体的な文書の調整
- 根本的な欠陥についての説明を追加
- 一時オブジェクトの生存期間が含む完全式の終わりまで、という規則がそもそも役に立っているのか?について
などです。
C++において言語機能が匿名オブジェクトを生成する場合があります。例えば、ラムダ式とコルーチンです。
// ラムダ式をその場で呼び出し [&c1 = "hello"s](const std::string& s) // c1はダングリングしない { return c1 + " "s + s; }("world"s); // ラムダ式を変数へ保存 auto lambda = [&c1 = "hello"s](const std::string& s) // c1はダングリング { return c1 + " "s + s; } // ... lambda("world"s); // UB
// 引数をconst参照で受けている // コルーチン本体は構文的な一度の関数呼び出しの後で何度も実行されうる generator<char> each_char(const std::string& s) { for (char ch : s) { co_yield ch; } } int main() { for (char ch : each_char("hello world")) { // std::stringの一時オブジェクトが作成され、ダングリングする std::print(ch); } }
この2つの問題はいずれも、この提案の一時オブジェクトの寿命を囲むスコープに紐づける(延長する)ことによって解決されます。
P2631R0 Publish TS Library Fundamentals v3 Now!
Library Fundamental TS v3を正式に発効するよう促す提案。
現在のLibrary Fundamental TSはv2が正式に発効された最後のもので、C++14をベースとして2016年に発行されました。発効後の6年間、LWGはこのv2をベースとして機能の追加や調整などを行っており、それはLFTSv3としてドラフト文書になっています。しかし、それはまだ正式なTSとして発行されていません。
この提案は、LFTSv3のベースをC++20に改訂するとともにLFTS関連の作業をいったん終了させ、C++23 DISよりも前にLFTSv3を正式なTSとして発効することを目指すものです。
LFTSv2からはany
やoptional
、string_view
など多くのものが既にC++標準に導入されている一方で、新しく追加されたものはscope_exit
やunique_resource
など少数で、かなり規模としては小さくなっています。
P2636R0 References to ranges should always be viewable
ムーブオンリーなrange
の左辺値参照をviewable_range
となるようにする提案。
例えば次のようなコードは、当初のC++20とその実装において全ての種類のforward_range
に対して有効でした
void foobar(ranges::forward_range auto && r) { auto v = r | views::take(3); // この|でエラーが起こりうる }
しかし、その後のP2415による変更の後、ムーブオンリーなrange
の左辺値参照の入力に対してコンパイルエラーを起こすようになります。例えばfoobar(std::views::all(std::vector<int>{1, 2, 3, 4}));
などとすると観測できます。
<ranges>
のパイプライン演算子(|
)は左辺にくる入力に対してviewable_range
であることを要求しますが、ムーブオンリーなrange
の左辺値参照はviewable_range
とならないため(ならなくなったため)にエラーとなります。
より詳しくみてみると
まず左辺値参照からのview
の構築の場合
{ string_view s{"foobar"}; auto v = s | views::take(3); // sはdecay_copyされる } { string s{"foobar"}; auto v = s | views::take(3); // sはref_viewで参照される } { Generator g{}; // コピー不可なrange auto v = g | views::take(3); // sはref_viewで参照される } { GeneratorView g{}; // viewとなるGenerator auto v = g | views::take(3); // error }
ただし、4つ目のコードはP2415以前の仕様では正常にコンパイルされます。
次に左辺値参照から構築されたview
を|
でRangeアダプタと接続する場合
{ string_view s{"foobar"}; auto v1 = s | views::take(3); // sはdecay_copyされる auto v2 = v1 | views::transform(/**/); // v1はdecay_copyされる } { string s{"foobar"}; auto v1 = s | views::take(3); // sはref_viewで参照される auto v2 = v1 | views::transform(/**/); // v1はdecay_copyされる } { Generator g{}; // コピー不可なrange auto v1 = g | views::take(3); // sはref_viewで参照される auto v2 = v1 | views::transform(/**/); // v1はdecay_copyされる } { GeneratorView g{}; // viewとなるGenerator auto v1 = g | views::take(3); // error auto v2 = v1 | views::transform(/**/); }
エラーの理由は1つ前と同様です。
次に、右辺値range
からのview
の構築の場合
{ auto v = string_view{"foobar"} | views::take(3); // string_viewはdecay_copyされる } { auto v = string{"foobar"} | views::take(3); // stringはowning_viewに保存される } { auto v = Generator{} | views::take(3); // Generatorはowning_viewに保存される } { auto v = GeneratorView{} | views::take(3); // GeneratorViewはdecay_copy(ムーブ)される }
そして、右辺値range
から構築されたview
を|
でRangeアダプタと接続する場合
{ auto v1 = string_view{"foobar"} | views::take(3); // string_viewはdecay_copyされる auto v2 = v1 | views::transform(/**/); // v1はdecay_copyされる } { auto v1 = string{"foobar"} | views::take(3); // stringはowning_viewに保存される auto v2 = v1 | views::transform(/**/); // error } { auto v1 = Generator{} | views::take(3); // Generatorはowning_viewに保存される auto v2 = v1 | views::transform(/**/); // error } { auto v1 = GeneratorView{} | views::take(3); // GeneratorViewはdecay_copy(ムーブ)される auto v2 = v1 | views::transform(/**/); // error }
ここの2~4番目のv2
はムーブオンリーなview
です。
これらの4パターンのコード例中でエラーとなっているのは、ムーブオンリーな左辺値view
をRangeアダプタに入力しようとしているためです。この提案は、これらのエラーを修正しようとするものです。
この提案による修正は、左辺値ムーブオンリーrange
がviewable_range
となるようにviewable_range
の定義を修正するとともに、views::all
が左辺値ムーブオンリーrange
に対してはref_view
を生成するようにします。
namespace std::ranges { template<class T> concept viewable_range = range<T> && ((view<remove_cvref_t<T>> && constructible_from<remove_cvref_t<T>, T>) || //(!view<remove_cvref_t<T>> && (is_lvalue_reference_v<T> || (movable<remove_reference_t<T>> && !is-initializer-list<T>)))); is_lvalue_reference_v<T> || (movable<remove_reference_t<T>> && !is-initializer-list<T>)); }
P2637R0 Member visit
and apply
std::visit
やstd::apply
などをメンバ関数として追加する提案。
std::visit
は1つのCallableと1つ以上のstd::variant
オブジェクトをとって、std::variant
オブジェクトが実際に保持している型に応じたディスパッチを行う可変長関数テンプレートです。しかし、std::visit
の多くのユースケースは1つのstd::variant
による単項のディスパッチです。その場合、メンバ関数として実装してあったほうが使いやすいように思えます。
std::visit
が非メンバ関数として定義されている理由の一つは、const
と値カテゴリの適切な転送を行うための最適な方法だったことがあります。これをメンバ関数で行おうとすると、const
有無と値カテゴリによって4つのオーバーロードが必要となってしまい、実装が複雑化します。
ところが、C++23ではまさにこの問題を解決する機能であるDeducing this
(P0847R7)が導入されたため、そのようなハンドリングを1つのメンバ関数のみで行うことができるようになっています。
この提案は、Deducing this
を利用してstd::visit
等の非メンバ関数テンプレートをメンバ関数として追加しようとするものです。
std::visit
と同じ理由から非メンバ関数として定義されているものにstd::apply
があり、std::visit
のインターフェースに合わせるためだけに非メンバ関数として定義されているものにstd::visit_format_arg
があります。この提案ではこれらのものも対象にしています。この提案で追加するメンバ関数と追加対象は次のものです
.visit()
std::variant
std::basic_format_arg
.apply()
std::pair
std::tuple
std::array
std::ranges::subrange
例えばstd::variant::visit()
は次のように簡単に実装可能です
namespace std { template <class... Types> class variant { public: ... // Deducing thisによるメンバvisit()の実装例 template <class Self, class Visitor> requires convertible_to<add_pointer_t<Self>, variant const*> constexpr auto visit(this Self&& self, Visitor&& vis) -> decltype(auto) { return std::visit(std::forward<Visitor>(vis), std::forward<Self>(self)); } }; }
P2638R0 Intel's response to P1915R0 for std::simd parallelism in TS 2
Parallelism TS v2にある、std::simd
に対するintelのフィードバック文書。
std::simd<T, ABI>
はクラステンプレートであり、各種CPUの持つSIMD命令に対する抽象化レイヤとして機能するデータ並列処理に特化したクラスです。
std::simd
に対しては[]
や+ - * /
などの各種演算子がオーバーロードされており、std::simd
オブジェクトに対する演算をコンパイル時にプラットフォームのSIMD命令に変換することを意図しています。std::valarray
と異なるのはおそらく、第二テンプレート引数でABI(使用するCPU命令種別等)に対する指定を行えることで、固定長配列やCPUに応じた可変長(コンパイル時定数)配列の指定や、ABI境界での安全性とパフォーマンスのバランスを制御することができるようになっています。
using std::experimental::native_simd; using Vec3D = std::array<native_simd<float>, 3>; // 3要素ベクトルの内積 native_simd<float> scalar_product(Vec3D a, Vec3D b) { return a[0] * b[0] + a[1] * b[1] + a[2] * b[2]; }
このコードはプラットフォームのCPUに応じたSIMD命令によって並列に計算され、要素数を変更しようとした時でも用意に行えます。
例えばこのコードをSSEを用いて書くと次のようになります
using Vec3D = std::array<__m128, 3>; // 3要素ベクトルの内積 __m128 scalar_product(Vec3D a, Vec3D b) { return _mm_add_ps(_mm_add_ps(_mm_mul_ps(a[0], b[0]), _mm_mul_ps(a[1], b[1])), _mm_mul_ps(a[2], b[2])); }
これはCPU命令と一対一対応した関数を用いており、要素数の変更が難しく、異なるISAへの移植性がありません。
std::simd
はフィードバックと実装経験を得るためにParallelism TS v2に配置されており、GCC特化の実装経験があります。
P1915R0はそこでの経験と議論の過程から得られたいくつかの設計上の疑問点や選択肢について記載するとともにそのフィードバックを募るものでした。この文書は、それに対するintelによる回答です。
P2639R0 Static Allocations
定数初期化されるstatic
変数において、コンパイル時動的メモリ確保を行えるようにする提案。
C++20から、定数式で動的メモリ確保が行えるようになりましたが、そのメモリ領域を実行時へ持ち越すにはいくつかの問題があったためそれはとりあえず禁止とされ、コンパイル時に動的に確保されたメモリ領域はコンパイル時に解放されなければなりません。また、それが許可されたとしてもそのメモリ領域は不変でなければならず(実行時には静的ストレージに配置される)、constexpr std::vector
オブジェクトに対して実行時に要素の挿入・削除や変更は行えないものになる予定です。それをしようとすると実行時オブジェクトへコピーする必要があるでしょう。
一方、実行時に静的ストレージに配置される変数で、初期化が定数式で行われる場合があるものがあります。それは、初期化式が定数式で実行可能なstatic/thread_local
変数で、例えば定数で初期化されているグローバルの整数型変数などが該当します。この場合に行われる初期化のことを(静的変数に対する)定数初期化と呼びます。
この提案は、定数初期化が実行される場合にのみコンパイル時に確保されたメモリ領域を実行時に持ち越すことを可能とするとともに、それを実行時に変更することも許可しようとするものです。
// コンパイル時に実行可能とする constexpr auto compute_my_vector() -> std::vector<int>; constinit std::vector<int> my_vector = compute_my_vector(); // 現在、エラー // この提案後、定数初期化される int main() { my_vector.insert(10); // この提案後、ok my_vector.pop_back(); // この提案後、ok }
my_vector.insert(10)
では、追加のメモリが必要になった場合は通常の実行時の動的確保が行われ、静的に確保された領域は解放されます。この場合の解放とは実際には何もせず、その領域が実際に解放されるのはプログラム終了時になるでしょう。
このように、静的ストレージで確保されているメモリをdelete
する際に問題となるのは、ユーザーによってoperator delete
がオーバーロードされうることです。静的ストレージでdelete
された領域へのアドレスがユーザー定義operator delete
に渡されると未定義動作となります。
この提案では、それを検出するためにstd::is_static_allocation()
という関数を提案しています。
namespace std { bool is_static_allocation(void* address); }
これはaddress
の領域が静的に確保されたものであるのかを判別するものです。ユーザー定義operator delete
ではこれを用いて本当にメモリの解放を行うかを判断することを意図しています。とはいえこれは既存のユーザー定義operator delete
コードに変更を強いるものです。
既存のコードへの影響を抑えるために、確保領域前後にメタデータを仕込むためのツールであるstd::static_allocation_wrapper
と、静的に確保された領域についての情報を取得するためのstd::static_allocation_info
を提案しています。
例えば次のようなユーザー定義operator delete
がある時
void operator delete(void* ptr) throw() { AllocationManager* manager = static_cast<AllocationManager**>(ptr)[-1]; manager->deallocate(ptr); }
std::is_static_allocation()
は次のように使用します
void operator delete(void* ptr) throw() { if (std::is_static_allocation(ptr)) return; AllocationManager* manager = static_cast<AllocationManager**>(ptr)[-1]; manager->deallocate(ptr); }
std::static_allocation_wrapper
等は次のように使用します
// static_allocation_wrapperはユーザーが定義する template<std::size_t size, std::size_t alignment, bool array> struct std::static_allocation_wrapper { // there needs to be padding here for alignment > sizeof(void*) static_assert(alignment <= sizeof(void*)); AllocationManager* manager = nullptr; // this needs to be initialized, otherwise default construction // will not result in a constant expression std::byte storage alignas(alignment) [size] = {}; const void* construct_at() const { return storage; } }; // プログラム開始時に呼ばれるものとする void setup_static_allocation_manager(AllocationManager* manager) { // std::static_allocationsは確保された静的領域全ての情報をもつ、static_allocation_infoのrange for (const auto& alloc_info : std::static_allocations) { // ユーザー定義のstatic_allocation_wrapperで領域がラップされているかを取得 if (!alloc_info.user_wrapped()) { // generate error here continue; } // ユーザーのアロケータ(AllocationManager)をセットする static_cast<AllocationManager**>(alloc_info.wrapper_begin())[0] = manager; } } // 変更は必要ない void operator delete(void* ptr) throw() { AllocationManager* manager = static_cast<AllocationManager**>(ptr)[-1]; manager->deallocate(ptr); }
- C++20 コンパイル時初期化を強制する
constinit
キーワードを追加 - cpprefjp - C++20 可変サイズをもつコンテナのconstexpr化 - cpprefjp
- P2639 進行状況
P2640R0 Modules: Inner-scope Namespace Entities: Exported or Not?
モジュール内で名前空間スコープに直接宣言を持たずに導入されるものについて、そのリンケージをどうするかを規定しようとする提案。
例えばクラス内で宣言されたfriend
関数のように、名前空間スコープのエンティティでありながら名前空間スコープに直接宣言が現れないものがあります。モジュール内で、そのようなエンティティが名前空間スコープで宣言されている場合、外部リンケージもしくはモジュールリンケージを持つとされています。一方で、friend
関数はモジュール本文内にはありますが名前空間スコープに宣言がなく、直接的にはexport
できないため、規定に厳密に照らすとモジュールリンケージを持つことになります。
// Friend.cpp export module Friend; export class X { friend int Frob (X *); // このリンケージは? }; // User1.cpp import Friend; int V = Frob ((X *)nullptr); // 呼び出せるはず?
friend
関数の役割や意味を考えると、この場合に適切なのは外部リンケージを与えることであるように思えます。
この規格の矛盾についてIssueが開かれて(DR2588)おり、クラス内friend
関数はそれが定義である場合にのみ属する(囲む)クラスのリンケージに従う、のような解決に向かおうとしています。
しかしその場合でも、クラスは定義だけをエクスポートしないことができるので、問題は完全に解決していません。
// モジュールのインターフェース export module Friend; // 前方宣言 export class X; // モジュールの実装単位 module Friend; // 定義、エクスポートされていない class X { friend int Frob (X *); // このリンケージは? };
さらには、同様の問題を持つものがこのクラス定義内friend
関数以外にも見つかったため、この問題の影響範囲は大きくなってしまいました。すなわち、宣言が再宣言もしくは後の宣言で定義される状況で、その宣言が新しい名前空間スコープのエンティティを導入する場合、そのエンティティのリンケージはどのように決定されるべきなのか?という問題に一般化されます。
この提案は、そのような事例について紹介するとともに、その解決を図るものです。
エンティティのリンケージはコード生成(シンボル名生成、名前マングリング)において重要となるため、少なくともその時点までにエンティティのリンケージは判明している必要があり、モジュールリンケージと外部リンケージではシンボル名(マングリング名)が異なる可能性があるためリンケージ種別の決定は重要な問題となります。
上記クラス定義内friend
関数の他には例えば、クラスのメンバ型
export module Struct; export struct Y { struct Z1 *p; // このリンケージは? }; void Toto (Z1 *) {}; // Totoのシンボル名にはY::Z1が含まれるため、リンケージが判明している必要がある
関数引数
export module FnParm; export void FnParm (struct Z2 *); // #1 Z2のリンケージは? void Toto (Z2 *) {}; // Cでは、ここのZ2は#1のものとは異なる型
関数のデフォルト引数
export module DfltFnArg; export void Fn (void * = (struct Z3 *)0); // Z3のリンケージは? void Corge (Z3 *) {}
このようなことは、以前にexport
された宣言の再宣言において起こるかもしれません。
export module DfltMemArg; export struct S2 { void Fn (void *); }; void S::Fn (void * = (struct Z4 *)0) {} // Z4のリンケージは? void Beans (Z4 *) {} namespace B { export void Fn (void *); } void B::Fn (void * = (struct Z5)0); // Z5のリンケージは? void Beans2 (Z5 *) {};
非型テンプレートパラメータ(NTTP)
export module NTTP; export template<struct NTTP *> class T1; // NTTPのリンケージは? void TUse1 (NTTP *) {}
スコープなしenum
通常スコープなしenum
は定義を分割(再宣言)できませんが、基底型を指定することで宣言と定義を分けることができます。
export module E; export enum E2 : int; enum E2 : int { B }; // ok、Bのリンケージは?
この他にも、非型テンプレートパラメータのデフォルト引数、変数の初期化式、using
宣言、decltype
、言語リンケージによる同様の例と、それによる名前探索への影響についてが文書には記載されています。
この提案はこれらの解決を明確に提示してはいませんが、モジュール本文内においてはこれらの宣言を全て禁止(ill-formedと)するか、それができない場合は(上記のようなエンティティが属する)宣言に字句的にexport
が存在するかどうかによってそのリンケージを決定する(つまり、再宣言においてこのようなエンティティが現れても、その宣言がexport
を伴っていなければモジュールリンケージとする)、のどちらかを提案しているようです。また、この解決はC++20に対するDRとすることを提案しています。
P2641R0 Checking if a union alternative is active
定数式において、union
のどのメンバがアクティブメンバかを調べるためのstd::is_active_member()
の提案。
sizeof(Optional<bool>) == 1
となるように最適化された型を作成したいとします。bool
は2パターンの値しか取りませんが、1バイトのサイズを持つので、残りの7ビット分を使用することでこのような型は作成可能です。例えば次の2つの実装が考えられます。
1つはunion
を用いたもの
struct OptBool { union { bool b; char c; }; OptBool() : c(2) { } OptBool(bool b) : b(b) { } auto has_value() const -> bool { return c != 2; } auto operator*() -> bool& { return b; } };
もう一つは再解釈を用いたもの
struct OptBool { char c; OptBool() : c(2) { } OptBool(bool b) { new (&c) bool(b); } auto has_value() const -> bool { return c != 2; } auto operator*() -> bool& { return (bool&)c; // *reinterpret_cast<bool*>(&c) } };
これはどちらも未定義動作を含みません。operator*
の読み取りでは事前条件が満たされている限り実際にはbool
のオブジェクトの値を読み取っており、.has_value()
においてはchar
型の左辺値(式)からはあらゆる型のオブジェクトのバイト表現を読みだすことが許可されているため、このような再解釈は常に合法となります。
ただ、これをconstexpr
対応させようとするといくつかの問題があります。
1つ目の実装の.has_value()
では、union
の非アクティブメンバ読み取りが定数式では常に許可されないためエラーとなります。
2つ目の実装では、まずコンストラクタのplacment newが問題となり、これは定数式で実行できません。このためにC++20でstd::construct_at
が追加されたのですが、この引数はbool
のオブジェクトを構築したい場合bool*
を渡さなければなりません。
次に、operator*
においては再解釈キャストが定数式で実行できません。
2つ目の実装の再解釈を定数式で許可するには標準の規定もそうですが実装のコストが高くなります。現在のC++コンパイラは定数式においてある領域にあるオブジェクトの型を追跡していますが、現在はそれは常に単一の型になっているところを複数の型を許可するようにしなければならないためです。
逆に、1つ目のunion
による実装の問題点を解決することは簡単にできます。なぜなら、現在のC++コンパイラは定数式におけるunion
オブジェクトのアクティブメンバを常に追跡しているためです。その際重要なのは、今回のようなケースにおいては非アクティブメンバを読みだす必要はなく、どちらのメンバがアクティブメンバかどうかを知るだけで良いということです。前述のように、定数式を実行中のコンパイラはそれを知っているため、コンパイラに問い合わせるだけでこれを知ることができるはずです。
struct OptBool { union { bool b; char c; }; constexpr OptBool() : c(2) { } constexpr OptBool(bool b) : b(b) { } constexpr auto has_value() const -> bool { if consteval { return std::is_active_member(&b); // 定数式ではbがアクティブメンバであるかを問い合わせる } else { return c != 2; // 実行時は今まで通り } } constexpr auto operator*() -> bool& { return b; } };
このstd::is_active_member()
のような関数を追加するだけでこれは達成でき(コア言語の変更を必要としない)、これは実装の負担もかなり小さいはずです。この提案は、このstd::is_active_member()
を標準に追加しようとするものです。
std::is_active_member()
は引数にunion
メンバへのポインタを取り、そのメンバがアクティブメンバである場合にtrue
を返す関数です。
namespace std { template<class T> consteval bool is_active_member(T*); }
コンパイラがunion
のアクティブメンバを追跡しているのはコンパイル時のみであり、当然実行時にそれを行っている主体はいない(あるいはそれを強制できない)ため、この関数はコンパイル時にしか実行できないようにconsteval
関数となっています。
P2642R0 Padded mdspan layouts
std::mdspan
でpadding strideをサポートするためのレイアウト指定クラスを追加する提案。
strideとは、配列における単位要素のサイズ(ある要素の先頭から次の要素の先頭までの長さ、通常バイト単位)のことを言います。strideは次元ごとに指定できて、例えば2次元行列だと単位要素は要素列あるいは行となり、1次元のstrideは1要素ごとのサイズですが、2次元のstrideはある行から次の行(あるいは列)までの長さになります(そして、3次元行列の3次元の単位要素は2次元行列になります)。strideは、1要素中の有効なデータのサイズと要素のサイズが異なる場合、すなわち1要素に無効な部分(パディング)が含まれている場合に配列要素を適切に引き当てるために使用されます。それは例えば、SIMD命令を使用する際にそのレジスタ幅に合わせるために無効な要素を追加したり、24bit画像において1ピクセルを32bitのデータとして扱うために8bit分を無視して扱うなど、主にメモリレイアウトの調整による処理の高速化のためにパディングが挿入されます。
多次元配列のメモリレイアウトにそのようなパディングが含まれる場合、strideを考慮したインデックス->アドレスの変換が必要となります。std::mdspan
ではそのためにstd::layout_stride
というクラスによってメモリレイアウトをカスタムすることができ、std::layout_stride
は次元ごとにstrideの値を保持しておいてインデックス->アドレスの変換に使用します。
しかし、そのようなパディングが挿入されるメモリレイアウトにおいては、必ずしも全ての次元でstrideの考慮が必要にならない場合があります。例えば、SIMD命令のレジスタ幅に合わせて配置したデータがパディングを含む場合、通常パディングは1度のSIMDで実行するデータ列の最後にだけ挿入されます。また、BLAS/LAPACKの行列を扱う関数においてはある行列の1部を部分行列として参照する際のアクセスのためにLeading dimensionと呼ばれる値を指定します。これは部分行列に対する親行列の行/列の要素数で、これはまさにstrideであり、この場合のパディングは行/列ごとにだけ挿入されています。
この提案のいうpadding strideとは、このように多次元配列の一部の次元だけがパディングを持つようなレイアウトにおけるstrideのことを言っています。
strideの考慮が必要なレイアウトはstd::layout_stride
を用いてstd::mdspan
で適切にハンドルでき、std::layout_stride
は次元ごとにstrideを指定できるためpadding strideにも対応可能です。しかし、padding strideの場合は何処か1つの次元だけがstrideを持つ、あるいは何処か1つ以上の次元はstrideの考慮が必要ないことがわかっており、std::layout_stride
では非効率である可能性があります。
この提案は、std::mdspan
及びstd::submdspan
でこのpadding strideを効率的にサポートするために、新しいレイアウト指定クラスlayout_left_padded
とlayout_right_padded
を追加するものです。名前にあるleft/right
とはleft
が列優先、right
が行優先のレイアウトであることを表しています(これはstd::layout_left, std::layout_right
と同様の意味)。
layout_left_padded
とlayout_right_padded
はstd::layout_stride
に対して次の2つの利点があり、これによる最適化が可能となります
- コンパイル時に少なくとも何処か1次元のデータはstrideが1(メモリ上で連続)であることを保証する
- コンパイル時にpadding strideの値がわかっていれば、それをメンバとして保持する必要がない
- 実行時の値であっても1つのstrideの値だけを保持すればいい(
std::layout_stride
は全ての次元のstrideを保持する)
- 実行時の値であっても1つのstrideの値だけを保持すればいい(
前述のように、この2つのクラスはBLSA/LAPACKのようなライブラリ(P1673)の入出力や、SIMDレジスタに渡すためにオーバーアラインされた配列をstd::mdspan
で取り扱うのに使用可能です。
int main() { // 1次元配列に配置された、4x4 2次元行列 int storage[] = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16}; // 4x4 2次元行列を参照するmdspan std::mdspan<int, std::extents<std::size_t, 4, 4>, std::layout_right> mat44(storage); for (auto i : std::views::iota(0, 4)) { for (auto j : std::views::iota(0, 4)) { std::cout << mat44[i, j] << ", "; } std::cout << "\n"; } // 2x2 2次元部分行列を参照するmdspanを作る // | 7, 8| // |11, 12| { // layout_strideを使用 using mapping = layout_stride::mapping<extents<std::size_t, 2, 2>>; std::mdspan<int, std::extents<std::size_t, 2, 2>, std::layout_stride> submat22(&mat44[1, 2], mapping{{}, std::array{4, 1}}); // ^^^^^^^^^^^^^^^^ // 次元ごとのstrideの指定 } { // layout_right_paddedを使用 std::mdspan<int, std::extents<std::size_t, 2, 2>, std::layout_right_padded<4>> submat22(&mat44[1, 2]); // ^ // 2次元目のstrideを指定 } }
この例は使用感しか示していませんが、layout_left_padded, layout_right_padded
を使用することでこのような場合のレイアウト指定を簡略化できるとともに、strideの指定が完全に定数となっていることがわかります。
std::mdspan
- cppreference- What does “stride” mean in image processing?
- 線形代数演算ライブラリBLAS とLAPACKの基礎と実践 (I) BLAS, LAPACK入門編
- GSL線形代数ルーチン読解: 2重対角化と3重対角化(2) LAPACK の LDA とは? - ぽんのブログ
- AVX2/AVX-512を用いたLennard-Jones系 ポテンシャルの力計算のSIMD化 - speakerdeck
- P2642 進行状況
この部分の10割は、以下の方のご協力によって成り立っています
P2643R0 Improving C++ concurrency features
C++20で追加された動機プリミティブ周りの機能を強化・改善する提案。
C++20では多くの並行処理のためのライブラリ機能(特に、同期プリミティブ)が追加されました
std::atomic
の.wait(), .notify_one(), .notify_all()
std::atomic<>
、std::atomic_flag
<semaphore>
<latch>, <barrier>
これらのものは多くの実装経験と長期の使用経験がありましたが、それでもいくつかのフィードバックがあり、改善の余地があるようです。
この提案は、これらの並行処理のためのライブラリ機能に対する実装者/ユーザーからのフィードバックを取りまとめて、その改善を促す提案です。
この提案の概要は次のようなものです
std::atomic::wait()
の時間を指定して待機するオーバーロードの追加する-
std::atomic
を用いて実装される動機プリミティブの実装を容易にするため - 他の同期プリミティブは時限待機関数を提供することが多く、その内部実装に
std::atomic
が使用されることが多い
-
- 値を返す
std::atomic::wait()
-
std::atomic::wait()
による待機が解除した後、多くの場合その値を読みに行く - しかし、
std::atomic::wait()
は起床のために値を読み込んでいるはずで、これを返すようにしたほうが効率的
-
- 次のどちらかによって、
std::atomic::wait()
のspurious pollingを回避する- .
std::atomic::wait()
には値の代わりに述語を渡す- アトミック値の等しさ以外の条件で待機している場合、
std::atomic::wait()
には囲むループがある -
std::atomic::wait()
が個別に何度も呼ばれると、実装は呼び出しの度に既に待機に時間を費やしていることを忘れてしまう(知ることができない) - これは待機戦略の決定に影響し、待機が非効率となりうる
- デフォルトでは、短期の待機戦略はしばらくの間
std::atomic
オブジェクトに対してポーリング(値の問い合わせ)を行うこと(処理のブロッキングを避けるため)
- デフォルトでは、短期の待機戦略はしばらくの間
- アトミック値の等しさ以外の条件で待機している場合、
- . 内部実装を制御するためのヒントとなる引数を渡す
-
std::atomic::wait()
の待機について事前の過程がある場合にそれを実装に伝達する - 例えば、待機開始してからしばらくの間に起床イベントが発生しないことがわかっている場合、長期の待機戦略をとることができるなど
-
- .
std::barrier
とstd::latch
の.wait()
に時間を指定して待機するオーバーロードを追加する- 他の同期プリミティブは時限待機関数を提供しているため、ここでも提供しない理由はない
なお、順番は優先度を表しています。
また、これらのうちのいくつかは、既存の標準ライブラリ実装において内部的に実装済みであるようです。
この提案では、1と2に対する変更のための文言を既に用意していて、1のために.try_wait(), .try_wait_for(), .try_wait_until()
関数を追加し、2のためにstd::atomic<T>::wait()
の戻り値型をvoid
からT
に変更しています(ただし、これはABI破壊を伴います)。.try_wait(), .try_wait_for(), .try_wait_until()
の関数はstd::atomic<T>
に対してstd::optional<T>
を返すことで時間切れで戻ったのか起床されたのかを判別できるようにしています。