文書の一覧 www.open-std.org
提案文書で採択されたものはありません。全部で21本あります。
- N4862 : Business Plan and Convener's Report
- P0288R6 : any_invocable
- P0881R6 : A Proposal to add stacktrace library
- P1787R5 : Declarations and where to find them
- P1875R1 : Transactional Memory Lite Support in C++
- P1949R5 : C++ Identifier Syntax using Unicode Standard Annex 31
- P2013R2 : Freestanding Language: Optional ::operator new
- P2053R1 : Defensive Checks Versus Input Validation
- P2079R1 : Parallel Executor
- P2096R2 : Generalized wording for partial specializations
- P2162R1 : Inheriting from std::variant (resolving LWG3052)
- P2187R4 : std::swap_if, std::predictable
- P2192R1 : std::valstat - function return type
- P2197R0 : Formatting for std::complex
- P2205R0 : Executors Review - Polymorphic Executor
- P2207R0 : Executors review: concepts breakout group report
- P2209R0 : Bulk Schedule
- P2210R0 : Superior String Splitting
- P2212R0 : Relax Requirements for time_point::clock
- P2213R0 : Executors Naming
- P2215R0 : "Undefined behavior" and the concurrency memory model
- 次
N4862 : Business Plan and Convener's Report
C++標準化委員会の全体的な作業の進捗状況や今後の予定などについての報告書。
おそらく、C++を利用している企業などに向けて書かれたものです。
P0288R6 : any_invocable
ムーブのみが可能で、関数呼び出しのconst
性やnoexcept
性を指定可能なstd::function
であるstd::any_invocable
の提案。
std::any_invocable
は次の点を除いてほとんどstd::function
です。
- ムーブのみ可能
std::unique_ptr
をキャプチャしたラムダのように、コピー不可能なCallableオブジェクトを受け入れられる
- 関数型に
const
/参照修飾やnoexcept
を指定可能const
性を正しく伝播できる
target_type()
およびtarget()
を持たない- 呼び出しには強い事前条件が設定される
- これによって、呼び出し時の
null
チェックが省略される
- これによって、呼び出し時の
#include <any_invocable> // 専用の新ヘッダ #include <functional> struct F { bool operator()() { return false; } bool operator()() const { return true; } }; int main() { std::cout << std::boolalpha; const std::function<bool()> func1{F{}}; const std::any_invocable<bool()> func2{F{}}; const std::any_invocable<bool() const> func3{F{}}; std::cout << func1() << '\n'; // false std::cout << func2() << '\n'; // false std::cout << func3() << '\n'; // true }
このようにconst
性を指定して正しく呼び出しが行えることで、並列処理においてスレッドセーフな呼び出しができるようになります。
他にも、noexcept
や参照修飾は次のように指定します。なお、volatile
修飾は指定することができません。
#include <any_invocable> struct F { int operator()(int n) const & noexcept { return n; } int operator()(int n) && { return n + 1; } }; int main() { std::any_invocable<int(int) const & noexcept(true)> func1{F{}}; std::any_invocable<int(int) && noexcept(false)> func2{F{}}; std::cout << func1(1) << '\n'; // 1 std::cout << func2(1) << '\n'; // 2 }
これらの修飾や指定は全て省略することもできます。
std::any_invocable
でも小さいオブジェクトで動的メモリ確保を避けるように規定されているので、std::function
に比べると若干パフォーマンスが良さそうです。
std::any_invocable
はすでにLWGでのレビューに入っていて、C++23に入る可能性が高そうです。
P0881R6 : A Proposal to add stacktrace library
スタックトレースを取得するためのライブラリを追加する提案。
このライブラリの目的はひとえにデバッグをより効率化することにあります。例えば次のような実行時アサーションメッセージを
boost/array.hpp:123:
T& boost::array<T, N>::operator[](boost::array<T, N>::size_type)
: Assertion '(i < N)&&("out of range")
' failed. Aborted (core dumped)
次のような出力にできるようにします
Expression '
i < N
' is false in function 'T& boost::array<T, N>::operator[](boost::array<T, N>::size_type)
': out of range.
Backtrace:
0# boost::assertion_failed_msg(char const, char const, char const, char const, long) at ../example/assert_handler.cpp:39
1# boost::array<int, 5ul>::operator at ../../../boost/array.hpp:124
2# bar(int) at ../example/assert_handler.cpp:17
3# foo(int) at ../example/assert_handler.cpp:25
4# bar(int) at ../example/assert_handler.cpp:17
5# foo(int) at ../example/assert_handler.cpp:25
6# main at ../example/assert_handler.cpp:54
7# 0x00007F991FD69F45 in /lib/x86_64-linux-gnu/libc.so.6
8# 0x0000000000401139
このライブラリはBoost.Stacktraceをベースに設計されています。スタックトレースの一行(すなわちスタックフレーム)はstd::stacktrace_entry
というクラスで表現され、std::stacktrace
というクラスが一つのスタックトレースを表現します。std::stacktrace
はほとんどstd::stacktrace_entry
のstd::vector
です。
#include <stacktrace> // このヘッダに定義される void f() { // 現在のスタックトレースを取得 auto st = std::stacktrace::current(); // スタックトレース全体の出力 std::cout << st << std::endl; // スタックトレースのイテレーション for (const auto& entry : st) { // 例えばソースファイル名だけを取得 std::cout << entry.source_file() << std::endl; } }
スタックトレースの取得はプラットフォーム毎にそこでのシステムコールやAPI呼び出しにマッピングされます。情報を取り漏らさないためにスタックトレースの取得サイズは可変長としているので動的メモリ確保が伴います。また、取得したスタックトレース情報のデコードはギリギリまで遅延されます。上記でいうと、std::stacktrace_entry::source_file()
が呼び出された時、あるいは内部でそれを呼び出すstd::stacktrace
の標準出力への出力時に取得した情報が逐次デコードされます。
提案によれば、コンパイラオプションによってABIを変化させずにこれらの関数が何もしないように制御する実装をサポート可能としているようで、これもBoost.Stacktraceから受け継いでいる機能です。
この提案は元々C++20に導入することを目指していたようで(間に合いませんでしたが)、すでにLWGでのレビューが一旦完了しており大きな問題もなさそうなので、C++23に入る可能性が高そうです。
P1787R5 : Declarations and where to find them
規格内でのscopeとname lookupという言葉の意味と使い方を改善する提案。
ほとんど規格用語の新規定義とそれを用いた既存の表現の変更で、主に名前解決に関連したバグが解決されますがユーザーにはほぼ影響はないはずです。
この提案によるコア言語の文言変更は多岐に渡りますが、これによって61(+19)個のCore Issueを解決できるとの事です。
P1875R1 : Transactional Memory Lite Support in C++
現在のトランザクショナルメモリTS仕様の一部だけを、軽量トランザクショナルメモリとしてC++へ導入する提案。
以前に紹介したP2066R2の元となったもので、提案の動機などが述べられています。P2062はこの提案を規格に反映させるための文言変更点だけをまとめたものです。
P1949R5 : C++ Identifier Syntax using Unicode Standard Annex 31
識別子(identifier)の構文において、不可視のゼロ幅文字や制御文字の使用を禁止する提案。
以前の記事を参照 onihusube.hatenablog.com
前回(R3)との変更点は絵文字の問題についての説明が追記されたこととGCC10.1で解決されたUTF-8文字を識別子へ使用できなかったバグについての言及が追記されたことです。
絵文字について
現在C++で絵文字の使用が可能なのは、たまたまC++で許可していたユニコードのコードポイント範囲に絵文字が割り当てられたためで、全てが使用可能であるわけではなく、FFFF
未満のコードポイントを持つものなど、一部の絵文字の使用は禁止されています。
例えばそこには女性記号(♀)が含まれています。これは結合文字と共に人を表すタイプの絵文字に作用してその絵文字の性別を変化させます。つまり、現在のC++の仕様では男性の絵文字を使用することは合法ですが、女性の絵文字を使用することはできないのです。
これは意図的なものではなく偶然の産物です。意図的にこのこと及びあらゆる絵文字(例えば、肌の色なども含めて)の使用を許可しようとすると、かなり多くの作業が必要となります。
この提案は識別子で使用可能な文字をUnicodeのUAX31規格を参照して決定するように変更するものであり、UAX31でも絵文字の利用については安定していないのでこの提案では全て禁止とする方向性のようです。
GCCのUTF-8文字関連のバグ
GCCは長らくUTF-8文字をソースコード中で使用することについてバグを抱えていたようで、GCC10.1でそれが解決されました。ClangとMSVCではすでに問題なく利用可能だったので、これによってUTF-8文字をソースコード中で使用することはほぼポータブルになります。このことは、この提案をより後押しするものです。
P2013R2 : Freestanding Language: Optional ::operator new
フリースタンディング処理系においては、オーバーロード可能なグローバル::operator new
を必須ではなくオプションにしようという提案。
以前の記事を参照 onihusube.hatenablog.com
このリビジョンでの変更は、グローバル::operator new
を提供しない場合でもplacment newは利用可能であることを明確にした事と、<new>
の宣言やポインタ安全性の文言などに変更はないことを明記した事です。この提案はほぼ規格書に文章を少し追記するだけのものです
EWGでのレビューの結果を次のリビジョンで反映させた上で、CWGに転送される見込みのようです。
P2053R1 : Defensive Checks Versus Input Validation
プログラムの動作の前提条件が満たされないケース2つにカテゴライズし、それらを区別することの重要性を説く報告書。
これは契約プログラミングサポートの議論のために、SG21(Contracts Study Group)のメンバーに向けて書かれたものです。
P2079R1 : Parallel Executor
ハードウェアの提供するコア数(スレッド数)に合わせた固定サイズのスレッドプールを提供するExecutorであるstd::execution::parallel_executor
の提案。
C++23を目指して進行中のExecutor提案(P0443R13)では、スレッドプールを提供するExecutorであるstatic_thread_pool
が唯一の標準Executorとして提供されています。
しかし、このstatic_thread_pool
はatach()
メンバ関数によってスレッドプールにスレッドを追加することができます。
static_thread_pool
がただ一つの標準Executorであることによって、プログラムの様々な場所(例えば外部dll内など)でとありあえずstatic_thread_pool
が使われた結果、static_thread_pool
はハードウェアが提供するスレッド数を超えて処理を実行してしまう可能性があります。これが起きると実行環境のシステム全体のパフォーマンス低下につながります。
これを防ぐために、ハードウェアの提供するスレッド数固定のスレッドプールを提供するparallel_executor
を、標準Executorのもう一つの選択肢として追加しようという提案です。
parallel_executor
はデフォルトではstd::thread::hardware_concurrency
に等しい数のスレッドを保持するように構築されます。その後で実行スレッド数の上限を設定することはできますが、ハードウェアの提供するスレッド数を超えて増やすことはできないようです。
std::thread::hardware_concurrency
- cpprefjp- [翻訳]P0443R13 A Unified Executors Proposal for C++ - 地面を見下ろす少年の足蹴にされる私
- P2079 進行状況
P2096R2 : Generalized wording for partial specializations
変数テンプレートの部分特殊化を明確に規定するように文言を変更する提案。
以前の記事を参照 onihusube.hatenablog.com
このリビジョンでは、CWGのレビューを受けて文言を調整したようです。
この提案が導入されると、変数テンプレートはクラステンプレートと同じことができる!という事が規格上で明確になります。C++14からそうだったのですが、あまりそのように認識されていないようで、おそらくそれは規格上で不明瞭だったことから来ているのでしょう・・・。
P2162R1 : Inheriting from std::variant (resolving LWG3052)
std::variant
を公開継承している型に対してもstd::visit()
できるようにする提案。
以前の記事を参照 onihusube.hatenablog.com
このリビジョンでの変更は、主要な3実装ではこの問題はどうなっているのかが詳細に記述されるようになっています。
それによれば、MSVC STLは完全に対応し、libc++はvalueless_by_exception
というメンバを上書きしているような型への対応に問題があるもののほぼ対応しており、libstdc++はstd::variant
が空になっているかのチェックがstd::variant
だけでしか動作しないためGCC9.1から無効化されているようです。
P2187R4 : std::swap_if, std::predictable
より効率的な条件付きswap
を行うためのstd::swap_if
と、その使用を制御するstd::predictable
の提案。
前回の記事を参照 onihusube.hatenablog.com onihusube.hatenablog.com
前回からの変更は、一部の関数にnoexcept
が付加されたことです。
P2192R1 : std::valstat - function return type
関数の戻り値としてエラー報告を行うための包括的な仕組みであるvalstatの提案。 onihusube.hatenablog.com
変更点は文書の文面を変更しただけの様です。
P2197R0 : Formatting for std::complex
std::format
にstd::comlex
のサポートを追加する提案。
// デフォルト std::string s1 = std::format("{}", 1.0 + 2i); // s == "(1+2i)" // iostream互換 std::string s2 = std::format("{:p}", 1.0 + 2i); // s == "(1,2)" // 精度指定 std::string s3 = std::format("{:.2f}", 1.0 + 2i); // s == "1.00+2.00i"
虚数単位にはi
が使用され、複素数の数値型T
のフォーマットは再帰的にT
のフォーマット指定(std::formatter<T>
)に委譲されます。虚数部が0.0
の時、虚数部を消して出力するのか0.0
を出力するのかはさらなる議論を必要としているようです。
P2205R0 : Executors Review - Polymorphic Executor
Executor提案(P0443R13)のPolymorphic Executor周りのレビューの結果見つかった問題についての報告書。
P0443のPolymorphic Executorには次のものが提案されています。
bad_executor
any_executor
prefer_only
bad_executor
は空のExecutorで、execute()
は常に例外を投げます。
any_executor
は簡単に言えばstd::function
のExecutor版で、任意のExecutorを型消去しつつ保持し、実行時に切り替える事が出来るラッパーなExecutorです。
prefer_only
はPolymorphic Executorにテンプレートパラメータでサポートするプロパティを指定する際に、そのプロパティのサポートが必須(require
)ではないことを示すのに使用するものです。
この文書はP0443のうちこれらのものをレビューし、見つかった問題点の報告と、修正案を提案するものです。
P2207R0 : Executors review: concepts breakout group report
Executor提案(P0443R13)のコンセプト周りのレビューの結果見つかった問題についての報告書。
P2209R0 : Bulk Schedule
P2181R0にて提案されているbulk_schedule
の設計についての補足となる報告書。
bulk_schedule
は遅延(任意のタイミングで)実行可能なbulk_execute
で、bulk_execute
はバルク実行を行うexecute
で、execute
は任意のExecutorを受けてそのExecutorの指す実行コンテキストで処理を実行するものです。また、バルク実行とはSIMDなどのように複数の処理をまとめて同時実行することです。つまり、bulk_schedule
はバルク処理に対応したExecutor上で、バルク処理を任意のタイミングで実行するためのものです。
using namespace std::execution; // バルク実行可能なExecutorを取得 bulk_executor auto ex = ...; // 処理のチェーンを構成する auto s1 = bulk_schedule(ex, [](){...}, 10); // 並列数10で2番目の引数の処理をex上で実行する auto s2 = bulk_transform(s1, [](){...}); // バルク実行の結果をバルクに変換する auto s3 = bulk_join(s2); // 各バルク実行の結果を1つに結合する // この時点では一連の処理は実行されていない // 一連の処理を実行し、完了を待機(ブロックする) sync_wait(s3);
execute
の遅延実行のためのものはschedule
が対応し、bulk_schedule
はバルク実行という点でschedule
と対称的に設計され、また対称的に使用可能となるようになっています。
// Executorを取得 executor auto ex = ...; // 処理のチェーンを構成する auto s1 = schedule(ex, [](){...}); // 2番目の引数の処理をex上で実行する auto s2 = transform(s1, [](){...}); // 実行結果を変換する auto s3 = join(s2); // 処理を統合する(この場合は意味がない) // この時点では一連の処理は実行されていない // 一連の処理を実行し、完了を待機(ブロックする) sync_wait(s3);
これらの対称性と抽象化を支えているのはsenderと呼ばれる抽象で、senderは未実行の一連の処理を表現するものです(サンプル中のs1, s2, s3
)。このsenderを用いる事で、処理のチェーンをどう実行するのかをユーザーやライブラリが定義するのではなく、実行環境となるExecutor自身が定義する事ができ、その実行環境において最適な方法によって安全に処理のDAGを実行する事ができます。
bulk_schedule
によって返されるバルク処理を表現するsenderは、そのバルク処理の完了を通知できる必要があります。すなわち、バルク処理のうち一つが終わった時の通知とは別にバルク処理の全体が終わったことを通知できなければなりません。そうでないと、バルク処理を安全にチェーンする事ができません。バルク処理の実行順は通常不定であるので、このことにもsenderによる抽象が必要です。
そのために、バルク処理を表現するsenderにはset_next
操作(これはexecute
などと同じくカスタマイゼーションポイントオブジェクトです)が追加されます。senderに対するset_next
の呼び出しによって1つのバルク処理全体が完了したことを通知し、それを受けた実行環境はそのsenderにチェーンされている次の処理を開始する事ができます。
そして、そのようなsenderを表現し制約するためのコンセプトとしてmany_receiver_of
コンセプトを追加します。many_receiver_of
コンセプトによって制約される事で、バルク処理ではない普通の処理をバルク処理にチェーンする事(あるいはその逆)は禁止され、コンパイルエラーとなります。
- A Unified Executors Proposal for C++ | P0443R13
- P2181R0 Correcting the Design of Bulk Execution
- P2181R0 : Correcting the Design of Bulk Execution - [C++]WG21月次提案文書を眺める(2020年6月) - 地面を見下ろす少年の足蹴にされる私
P2210R0 : Superior String Splitting
現状のviews::split
の非自明で使いにくい部分を再設計する提案。
views::split
を使った次のようなコードはコンパイルエラーになります。
std::string s = "1.2.3.4"; auto ints = s | std::views::split('.') | std::views::transform([](auto v){ int i = 0; std::from_chars(v.data(), v.data() + v.size(), &i); return i; })
なぜなら、views::split
すると得られるこのv
はforward_range
(forward iteratorで構成されたrangeオブジェクト)なので、data()
やsize()
などというメンバ関数はなく、そのイテレータはポインタですらありません。ついでに言うとそのv
はcommon_range
(begin()
とend()
のイテレータ型が同じrange)でもありません。
文字列に対するviews::split
の結果として直接得られるのは、分割後のそれぞれの文字列のシーケンスとなるrangeオブジェクトです(これを構成するイテレータを外部イテレータと呼びます)。そして、それをデリファレンスして得られるのが分割された1つの文字列を示すrangeオブジェクトで、これが上記のコードのv
にあたります(こちらを構成するイテレータは内部イテレータと呼びます)。内部イテレータはforward iteratorであり、文字列を直接参照するポインタではありません。
例えば次のように文字列を分割したとき
string s = "abc,12,cdef,3"; auto split = s | std::views::split(",");
生成されるrangeの様子は次のようになります。
これを踏まえると、正しくは次のように書かなければなりません。
std::string s = "1.2.3.4"; auto ints = s | views::split('.') | views::transform([](auto v){ auto cv = v | views::common; // イテレータ型と番兵型を合わせる return std::stoi(std::string(cv.begin(), cv.end())); }); }
主に次の理由により、このような事になっています。
views::split
はその処理を可能な限り遅延させるviews::split
の後に任意のrangeアルゴリズムをチェーンさせられるviews::split
は文字列に限らず任意の範囲について動作する
しかし、文字列の分割という基本的な操作は頻繁に必要になるのに対して、文字列以外の範囲の分割というユースケースは滅多に必要にならず、既存の多くの文字列処理のアルゴリズム(std::from/to_chars
やstd::regex
など)の多くは少なくとも双方向イテレータ(bidirectional range)を要求します。
これらのことから、views::split
の現在の仕様は一般化しすぎており、文字列の分割という観点からはとても使いづらいので再設計が必要である、という主張です。
主に次のように変更する事を提案しています。
- forward rangeまたはより強い範囲を
split
すると、同じ強さの部分範囲が得られるさらに、P1391 Range constructor forstd::string_view
が採用されれば、そこから文字列への変換も容易になるstd::string_view
にC++20ですでにRange constructorが追加されていました・・・
- input rangeでしかない範囲の分割に関しては現状通り
split_view
(views::split
の返すrange)はconst-iterableではなくなるbegin()
の呼び出しで単にイテレータを返す以上のことをする(しなければならい)ようになる
この変更はC++20でviews::split
を用いた既存のコードを壊すことになりますが、これによって得られる文字列分割での利便性向上という効用は互換性を破壊することによるコストを上回る、と筆者の方は述べています。
この部分は以下の方のご指摘によって構成されています。
P2212R0 : Relax Requirements for time_point::clock
std::chrono::time_point
のClock
テンプレートパラメータに対する要件を弱める提案。
std::chrono::time_point
は時間軸上の一点を指定する型で、その時間基準(始点(epoch)と分解能)を指定するために時計型と時間間隔を表す2つのテンプレートパラメータを受け取ります。うち1つ目の時計型Clock
にはCpp17Clock要件という要求がなされています。これは、std::chrono::system_clock
やstd::chrono::steady_clock
などと同じ扱いができることを要求しています。
C++20ではローカル時間を表す型としてstd::chrono::local_t
が導入されました。これは、std::chrono::time_zone
とともに用いることで任意のタイムゾーンにおけるローカルの時刻を表現することができるものです。
local_t
に対する特殊化std::chrono::time_point<local_t, Duration>
も提供されますが、local_t
は空のクラスでありCpp17Clock要件を満たしていません。規格ではlocal_t
だけを特別扱いする事でtime_point
を特殊化することを許可しています。
このlocal_t
のように、時計型ではないが任意の時間を表す型を定義し、その時間表現としてtime_point
を特殊化することは有用なのでできるようにしよう、という提案です。
例えば次のような場合に便利であると述べられています
- 状態を持つ時計型を扱いたい場合(非
static
なnow()
を提供したい) - 異なる
time_point
で1日の時間を表現したい場合- 年月日のない24時間のタイムスタンプで表現される時刻を扱う
- 手を出せないシステムが使用している
time_point
を再現したい場合 - 異なるコンピュータ間でタイムスタンプを比較する
この変更がなされたとしても既存のライブラリ機能とそれを使ったコードに影響はなく、local_t
が既にあることから実装にも実質的に影響はないだろうとのことです。
std::chrono
- cpprefjp- Cpp17Clock
- Why does the C++ standard require the
Clock::now
function to bestatic
? - stackoverflow - C++ Time Without Date? - stackoverflow
P2213R0 : Executors Naming
Executor提案(P0443R13)で提案されているエンティティの名前に関する報告書。
P0443R13のエンティティ名及びP1897R3で提案されているアルゴリズム名のうち一部について、代わりとなりうる名前とその理由が書かれています。
P2215R0 : "Undefined behavior" and the concurrency memory model
out-of-thin-airと呼ばれる問題の文脈における、未定義動作とは何かについての報告書。
out-of-thin-airとは定式化されたメモリモデルから導かれる1つの起こり得る振る舞いのことです。さも虚空から値を読みだしているかのように見えることからこの名前がついているようです。
int main() { int r1, r2; std::atomic_int x = 0; std::atomic_int y = 0; std::thread t1{[&]() { r1 = x.load(std::memory_order_relaxed); y.store(r1, std::memory_order_relaxed); }}; std::thread t2{[&](){ r2 = y.load(std::memory_order_relaxed); x.store(r2, std::memory_order_relaxed); }}; t1.join(); t2.join(); // r1 == 1, r2 == 1, x == 1, y == 1 // となることがありうる(現実的にはほぼ起こらない) return 0; }
このように、コード中どこにも表れていないはずの値が読み出されることが起こりうることが理論的に導かれます。この事がout-of-thin-air(またはThin-air reads)と呼ばれています。
C++ではメモリモデルにこの事を禁止する条項を加える事でout-of-thin-airの発生を禁止しています。しかし、メモリモデルの定式化を変更する事でこれが起きないようにしようという試みが続いているようです(Javaはそうしている)。
この文書はおそらく、そのような定式化のためにout-of-thin-airの文脈における未定義動作という現象をしっかりと定義しようとするものです。
- メモリモデルとThin-air read - yohhoyの日記
- N3132 Mathematizing C++ Concurrency: The Post-Rapperswil Model
- N4323 Out-of-Thin-Air Execution is Vacuous
この部分は以下の方のご指摘によって構成されています。
次
多分2週間後くらい