文書の一覧
全部で36本あります。
- N4912 2022-11 Kona hybrid meeting information
- N4913 PL22.16/WG21 agenda: 25 July 2022, Virtual Meeting
- P0543R1 Saturation arithmetic
- P0792R9 function_ref: a non-owning reference to a Callable
- P0901R9 Size feedback in operator new
- P1021R6 Filling holes in Class Template Argument Deduction
- P1255R7 A view of 0 or 1 elements: views::maybe
- P1642R9 Freestanding Library: Easy [utilities], [ranges], and [iterators]
- P1673R8 A free function linear algebra interface based on the BLAS
- P1674R2 Evolving a Standard C++ Linear Algebra Library from the BLAS
- P1774R7 Portable assumptions
- P1967R6 #embed - a simple, scannable preprocessor-based resource acquisition method
- P2286R8 Formatting Ranges
- P2429R0 Concepts Error Messages for Humans
- P2445R1 forward_like
- P2460R1 Relax requirements on wchar_t to match existing practices
- P2472R3 make function_ref more functional
- P2510R3 Formatting pointers
- P2513R2 char8_t Compatibility and Portability Fix
- P2542R2 views::concat
- P2551R1 Clarify intent of P1841 numeric traits
- P2558R1 Add @, $, and ` to the basic character set
- P2577R1 C++ Modules Discovery in Prebuilt Library Releases
- P2577R2 C++ Modules Discovery in Prebuilt Library Releases
- P2580R0 Tuple protocol for C-style arrays T[N]
- P2581R0 Specifying the Interoperability of Binary Module Interface Files
- P2582R0 Wording for class template argument deduction from inherited constructors
- P2584R0 A More Composable from_chars
- P2585R0 Improving default container formatting
- P2587R0 to_string or not to_string
- P2588R0 Relax std::barrier phase completion step guarantees
- P2589R0 static operator[]
- P2590R0 Explicit lifetime management
- P2591R0 Concatenation of strings and string views
- P2592R0 Hashing support for std::chrono value classes
- P2593R0 Allowing static_assert(false)
- おわり
N4912 2022-11 Kona hybrid meeting information
2022年11月のWG21全体会議の周知文書。
2022年11月の全体会議が対面都オンラインの両方同時開催になることが決定されたようで、主にその開催場所についての情報が記載されています。
N4913 PL22.16/WG21 agenda: 25 July 2022, Virtual Meeting
2022年6月の全体会議のアジェンダ。
P0543R1 Saturation arithmetic
整数の飽和演算を行うライブラリ機能の提案。
C++の整数型による計算時、その計算結果がオーバーフロー(その型の最大値/最小値を超えた値になる)する場合、符号付き整数型は未定義動作となり、符号なし整数型は2Nを法としたモジュロ演算によって結果が取得されます。
int main() { std::uint32_t un1 = std::uint32_t(4294967295) + 1u; // 0 std::uint32_t un2 = 0u - 1u; // 4294967295 std::int32_t sn1 = std::int32_t(2147483647) + 1; // UB std::int32_t sn2 = -2147483648 - 1; // UB }
いずれの場合でも、場合によってはオーバーフローするときに上限・下限値で値を止めてほしいときがあります。そのためのよく知られた方法が飽和演算(saturation arithmetic)で、計算結果がオーバーフローする場合に表現可能な範囲内に収める形で結果を返します。
C++には今までこの飽和演算を行う標準的な方法がありませんでしたが、この提案はそれを追加しようとするものです。
提案文書より、サンプルコード。
#include <saturation> // 新ヘッダに配置 int main() { int x1 = add_sat(3, 4); // 7 int x2 = sub_sat(INT_MIN, 1); // INT_MIN unsigned char x3 = add_sat(255, 4); // 3、引数はint型 unsigned char x4 = add_sat<unsigned char>(255, 4); // 255 unsigned char x5 = add_sat(252, x3); // error、2つの引数の型は同一でなければならない unsigned char x6 = add_sat<unsigned char>(251, x1); // 255、2つ目の引数x1はint -> unsigned charへ変換されている }
追加されるのは四則演算に対応する4つの関数と、飽和的なキャストを行うstd::saturate_cast()
の5つです。
namespace std { template<class T> constexpr T add_sat(T x, T y) noexcept; template<class T> constexpr T sub_sat(T x, T y) noexcept; template<class T> constexpr T mul_sat(T x, T y) noexcept; template<class T> constexpr T div_sat(T x, T y) noexcept; template<class T, class U> constexpr T saturate_cast(U x) noexcept; }
std::saturate_cast()
は整数型T
から整数型U
へのキャストを行うものですが、T
の値がU
で表現できない(オーバーフローする)場合に最大値か最小値に丸めて値を返す形でキャストします。
また、これらの関数テンプレートは任意の整数型(符号有無にかかわらず)において使用可能です。
P0792R9 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月)
このリビジョンでの変更は、プライマリテンプレートを可変長テンプレートで宣言するようにしたこと(将来の拡張のため)と、function_ref
のオブジェクトをconstinit
で初期化できるようにした(Callableを参照で受け取るコンストラクタをconstexpr
化した)ことなどです。
この提案は現在、LEWGからLWGへ進むための投票待ちをしています。
P0901R9 Size feedback in operator new
::operator new
が実際に確保したメモリのサイズを知ることができるオーバーロードを追加する提案。
以前の記事を参照
- P0901R7 Size feedback in operator new interface - [C++]WG21月次提案文書を眺める(2020年11月)
- P0901R8 Size feedback in operator new interface - [C++]WG21月次提案文書を眺める(2021年01月)
このリビジョンでの変更は、CWGのレビューなどを受けて提案する文言を変更したことです。ただし、それによってEWGの確認が必要な問題が発生しているようです。
P1021R6 Filling holes in Class Template Argument Deduction
C++17で導入されたクラステンプレートの実引数推定(CTAD)について、欠けている部分を埋める提案。
ここで提案されているのは、集成体初期化時のCTAD、エイリアステンプレートでのCTAD、継承コンストラクタからのCTADの3つで、最初の二つはC++20で可能になっています。
この提案のこのリビジョンでの変更は、最後の継承コンストラクタからのCTADの提案を行う文書へのリンクを追記したことです(P2582R0、下の方で解説)。
P1255R7 A view of 0 or 1 elements: views::maybe
std::optional
やポインタ等のmaybeモナドな対象を、その状態によって要素数0か1のシーケンスに変換するRangeアダプタviews::maybe
の提案。
以前の記事を参照
このリビジョンでの変更は
- リスト内包表記サポートのために対象を全てのオブジェクト型に拡張
borrowed_range
に関する議論の追記- オブジェクトを値で保持していない場合は
views::meybe
はborrowed_range
になる- ポインタあるいは
reference_warapper
で保持している場合
- ポインタあるいは
- オブジェクトを値で保持していない場合は
- パイプラインで参照が使用される例を追加
- プロクシリファレンスのサポートの追加
const
伝播に関する議論のセクションを追加(未決定)
などです。
P1642R9 Freestanding Library: Easy [utilities], [ranges], and [iterators]
[utility]、<ranges>
、<iterator>
から一部のものをフリースタンディングライブラリに追加する提案。
前回の記事を参照
- P1642R3 Freestanding Library: Easy [utilities], [ranges], and [iterators] - [C++]WG21月次提案文書を眺める(2020年6月)
- P1642R4 Freestanding Library: Easy [utilities], [ranges], and [iterators] - [C++]WG21月次提案文書を眺める(2020年7月)
- P1642R5 Freestanding Library: Easy [utilities], [ranges], and [iterators] - [C++]WG21月次提案文書を眺める(2020年12月)
- P1642R6 Freestanding Library: Easy [utilities], [ranges], and [iterators] - [C++]WG21月次提案文書を眺める(2021年06月)
- P1642R7 Freestanding Library: Easy [utilities], [ranges], and [iterators] - [C++]WG21月次提案文書を眺める(2021年10月)
- P1642R8 Freestanding Library: Easy [utilities], [ranges], and [iterators] - [C++]WG21月次提案文書を眺める(2022年04月)
このリビジョンでの変更は
- 提案する文言の改善
- hidden friendをフリースタンディングとして扱うように
std::bind
のプレースホルダをフリースタンディングに含むことを明確化allocation_result
とallocate_at_least
を追加wistream_view
とviews::istream
を対象から削除<memory>
サポートのため<algorithm>
からin_out_result
を追加
などです。
P1673R8 A free function linear algebra interface based on the BLAS
標準ライブラリに、BLASをベースとした密行列のための線形代数ライブラリを追加する提案。
以前の記事を参照
- P1673R3 A free function linear algebra interface based on the BLAS - [C++]WG21月次提案文書を眺める(2021年04月)
- P1673R4 A free function linear algebra interface based on the BLAS - [C++]WG21月次提案文書を眺める(2021年08月)
- P1673R5 A free function linear algebra interface based on the BLAS - [C++]WG21月次提案文書を眺める(2021年10月)
- P1673R6 A free function linear algebra interface based on the BLAS - [C++]WG21月次提案文書を眺める(2021年12月)
- P1673R7 A free function linear algebra interface based on the BLAS - [C++]WG21月次提案文書を眺める(2022年04月)
このリビジョンでの変更は
- コレスキーTSQRの例で
Triangle
とR[0,0]
を修正 - BLASが入力行列に
UPLO
を適用するのに対して、返還される可能性のある入力行列に対してTriangle
を適用する理由の説明の追加 - 必要ない場合に
layout_transpose
を使用しないように、既知のすべてのレイアウトに対してtransposed
を最適化- 転置後レイアウトのストライド計算を修正
symmetric_matrix_rank_k_update
とhermitian_matrix_rank_k_update
制約とmandates内の行列のエクステントを修正transposed
の戻り値のconst
性の曖昧さの解消scaled
の戻り値のconst
性の曖昧さを解消し、その要素型を元のmdspan
の要素型に戻すのではなく、積の型にするaccessor_conjugate
とaccessor_scaled
からdecay()
メンバ関数を削除(mdspan
の要件ではなくなったため)accessor_conjugate
とconjugated
がユーザー定義複素数型に対して正しく機能することを確認し、その戻り値型のconst
性の曖昧さを解消した- typoの修正
などです。
P1674R2 Evolving a Standard C++ Linear Algebra Library from the BLAS
C++標準ライブラリに提案する線形代数ライブラリの設計に関して記述した文書。
- P1674R1 Evolving a Standard C++ Linear Algebra Library from the BLAS - [C++]WG21月次提案文書を眺める(2022年04月)
このリビジョンでの変更は、P1673の最新のリビジョンの内容を反映したことと、参照文献リストの追加・更新などです。
P1774R7 Portable assumptions
コンパイラにコードの内容についての仮定を伝えて最適化を促進するための[[assume(expr)]]
の提案。
以前の記事を参照
- P1774R4 Portable assumptions - [C++]WG21月次提案文書を眺める(2021年10月)
- P1774R5 Portable assumptions - [C++]WG21月次提案文書を眺める(2021年12月)
- P1774R6 Portable assumptions - [C++]WG21月次提案文書を眺める(2022年02月)
このリビジョンでの変更は
- P2507の内容を反映したこと
- CWGのレビューを受けて提案する文言を改善したこと
- いくつかのデイスカッションを追加したことです
この提案で追記されたディスカッションは、ほぼコンパイラが標準属性を無視する場合の無視の仕方に関してのものです。コンパイラが標準属性を無視する場合、無視するというのは属性の効果なのか、属性のパースそのものなのかは特に規定されていません。これまでは式を取るような属性がなかったためあまり問題になりませんでしたが、[[assume(expr)]]
は任意の式を取るためそれが問題になります。
つまり、属性の無視の仕方が未規定のままだと、[[assume(expr)]]
と書いてある時にテンプレートがインスタンス化されるかどうか、ラムダがキャプチャするかどうかなどが変わることになります。それはともすればABI破壊を招きます。
この提案では[[assume(expr)]]
を無視するにしても無視するのはその効果だけであり、式のパースやインスタンス化は行われるということで、CWGではコア言語もそのように変更する方向で議論が進んでいるようです。
この提案は、すでにCWGのレビューを終えており、次の全体会議で投票にかけられる事が決まっています。
- P2507R0 Only [[assume]] conditional-expressions - WG21月次提案文書を眺める(2021年12月)
- CWG Issue 2538. Can standard attributes be syntactically ignored?
- P1774 進行状況
P1967R6 #embed
- a simple, scannable preprocessor-based resource acquisition method
コンパイル時(プリプロセス時)にバイナリデータをインクルードするためのプリプロセッシングディレクティブ#embed
の提案。
以前の記事を参照
- P1967R3
#embed
- a simple, scannable preprocessor-based resource acquisition method - [C++]WG21月次提案文書を眺める(2021年04月) - P1967R4
#embed
- a simple, scannable preprocessor-based resource acquisition method - [C++]WG21月次提案文書を眺める(2021年06月)
このリビジョンでの変更は
- フィードバックに基づく構文の変更
- WG21向け文書とWG14向け文書の分離
__has_embed
の構文の変更- ファイルが存在していてそのファイルが空であり、指定された
embed
引数が正しい場合、2
を返すようにされた - 以前は未規定だった
- ファイルが存在していてそのファイルが空であり、指定された
- typo修正や文言の修正
limit
引数では、少なくとも1回はマクロを展開するように文言を調整
などです。
P2286R8 Formatting Ranges
任意の範囲を手軽に出力できる機能を追加する提案。
以前の記事を参照
- P2286R0 Formatting Ranges - [C++]WG21月次提案文書を眺める(2021年1月)
- P2286R1 Formatting Ranges - [C++]WG21月次提案文書を眺める(2021年2月)
- P2286R2 Formatting Ranges - [C++]WG21月次提案文書を眺める(2021年8月)
- P2286R3 Formatting Ranges - [C++]WG21月次提案文書を眺める(2021年11月)
- P2286R4 Formatting Ranges - [C++]WG21月次提案文書を眺める(2021年11月)
- P2286R5 Formatting Ranges - [C++]WG21月次提案文書を眺める(2022年01月)
このリビジョンでの変更は、提案する文言の調整のみ(文字のエスケープまわり)です。
この提案はすでにLWGのレビューを終え、次の全体会議で投票にかけられる事が決まっています。
P2429R0 Concepts Error Messages for Humans
C++コンパイラの出力するエラーメッセージの改善についての報告書。
C++エラーメッセージが時に何を言っているのかわからなくなりがちなのは良く知られていることですが、C++20でコンセプトが導入されてそれが改善されるかと思いきやより意味がわからなくなっているなど、事態は深刻さを増しています。
この文書はそのような現状を報告するとともに、特にコンセプト関連のエラーメッセージに対しての改善可能性を示し、C++エラーメッセージの改善の議論の呼び水となること目的としたものです。
P2445R1 forward_like
クラス型のメンバ変数について、const
性も含めた正しい完全転送を行うstd::forward_like
の提案。
このリビジョンでの変更は、提案する文言の調整のみです。
この提案はすでにLWGのレビューを終えており、次の全体会議で投票にかけられることが決まっています。
P2460R1 Relax requirements on wchar_t
to match existing practices
wchar_t
のエンコーディングについての実態になじまない制約を取り除く提案。
このリビジョンでの変更は、提案する文言の調整のみです。
この提案は、以前の全てのバージョンに対するDefect Reportとすることで合意されているようです。
P2472R3 make function_ref
more functional
function_ref
のユーザビリティと安全性を向上させるコンストラクタを追加する提案。
以前の記事を参照
- P2467R0 Support exclusive mode for fstreams - WG21月次提案文書を眺める(2021年10月)
- P2467R1 Support exclusive mode for fstreams - WG21月次提案文書を眺める(2022年02月)
- P2467R2 Support exclusive mode for fstreams - WG21月次提案文書を眺める(2022年04月)
このリビジョンでの変更は、追加するコンストラクタにconstexpr
を付加したことです。
P2510R3 Formatting pointers
std::format
について、ポインタ型のフォーマットを充実させる提案。
以前の記事を参照
- P2510R0 Formatting pointers - WG21月次提案文書を眺める(2021年12月)
- P2510R1 Formatting pointers - WG21月次提案文書を眺める(2022年03月)
- P2510R2 Formatting pointers - WG21月次提案文書を眺める(2022年04月)
このリビジョンでの変更は、ポインタ型に対する#
のフォーマット指定(16進数値に対する0x
プリフィックスのように、数値形式を明確化する指定)を提案から削除した事などです。
P2513R2 char8_t
Compatibility and Portability Fix
char8_t
の非互換性を緩和する提案。
以前の記事を参照
- P2513R0
char8_t
Compatibility and Portability Fixes - WG21月次提案文書を眺める(2021年12月) - P2513R1
char8_t
Compatibility and Portability Fixes - WG21月次提案文書を眺める(2022年02月)
このリビジョンでの変更は、u8""
文字列リテラルから変換可能な配列からsigned char
を除外したことなどです。
新たに掲載されたサンプルコードより
// pre C++20 | C++20 | This proposal const char* ptr0 = u8""; // ✅ 💔 💔 const unsigned char* ptr1 = u8""; // ❌ ❌ ❌ const char arr0[] = u8""; // ✅ 💔 ✅ const unsigned char arr1[] = u8""; // ✅ 💔 ✅
✅ : ok、💔 : 破壊的変更、❌ : ng
// C++20以前はok, C++20でng constexpr const char* resource_id () { return u8"o(* ̄▽ ̄*)o"; } // この提案後、こう書けばok constexpr const char* resource_id () { const char res_id[] = u8"o(* ̄▽ ̄*)o"; return res_id; }
P2542R2 views::concat
同じ要素型を持つ異なる型の範囲を連結するRangeファクトリ、views::concat
の提案。
このリビジョンでの変更は、concat-indirectly-readable
コンセプトに追加の意味論制約(等しさを保持しない*
やiter_move
の禁止)を追加した事などです。
P2551R1 Clarify intent of P1841 numeric traits
P1841R2で提案されている数値特性(numeric traits)取得ユーティリティについて、実装経験からの疑問点を報告する文書。
このリビジョンでの変更は、回答が得られて解決された疑問を削除した事、reciprocal_overflow_threshold
についてのオプションを追記した事です。
P2558R1 Add @, $, and ` to the basic character set
@ $ `の3種類の文字をソースコードの基本文字集合に追加する提案。
このリビジョンでの変更は、これによる影響についての説明を追記した事です。
P2577R1 C++ Modules Discovery in Prebuilt Library Releases
↓
P2577R2 C++ Modules Discovery in Prebuilt Library Releases
ビルド済みモジュールライブラリ配布のための規則についての提案。
R1での変更は、リンカ引数から変換して取得する例に標準ライブラリモジュールを含めるようにしたことです(標準ライブラリも他のライブラリと同様の方法で提供するモジュールを取得できる)。
R2での変更は、提案しているルールはどこでも使用すべきというものではないことを明確にしたこと(?)などです。
P2580R0 Tuple protocol for C-style arrays T[N]
生配列にタプルインターフェースのサポートを追加する提案。
タプル-likeな型という観点からは、T[N]
とstd::array<T, N>
は同一の性質を持っているはずです。しかし、std::array<T, N>
はタプルインターフェースを備えているのに対して、T[N]
はそうではありません。
T[N]
にタプルインターフェースのサポートを追加すれば、タプルを渡せるところ(std::apply
やstd::make_from_tupe
など)に生配列を渡すことができるようになります。他にも、std::array<T, N>
よりもT[N]
の方が好まれるような場合においても、生配列からstd::array
を一時的に構築しなくてもタプル操作を利用できるようになるなどのメリットがあります。
#include <array> // サイズの自動推定 void auto_size_deduct() { int c_arr[] = {1, 2, 3}; // ok、要素数3 std::array<int> arr = {1, 2, 3}; // ng } // C APIライブラリ関数 struct ReferenceFrame; int get_origin(struct ReferenceFrame* frame, double (*pt)[3]); // C APIとの相互運用性 std::optional<Point> get_origin(ReferenceFrame& frame) { // この提案の後では、このように書けるようになる double pt[3] { }; if (get_origin(&frame, &pt) != 0) { return { }; } return std::make_from_tuple<Point>(pt); } // コンパイル時の境界チェック void ctbc() { int c_arr[42]{}; c_arr[42] = 42; // UB std::get<42>(c_arr) = 42; // コンパイルエラー }
この提案は、これらのメリットのために、生配列に対してタプルインターフェースサポートを追加しようとするものです。
タプルインターフェースの実体は、次の3つの標準ライブラリ機能からなっています。
std::tuple_size<T>
クラステンプレート- タプルの長さを求める
std::tuple_element<T>
クラステンプレート- タプルの要素型を求める
std::get<I>
関数- タプルの要素を引き当てる
この提案では、これらのものに対して生配列T[N]
の(部分)特殊化を追加することで生配列に対するタプルサポートを有効にしようとしています。
その際、T const [N]
に対して新規追加するものと既存のもの(tuple_size, tuple_element
)がバッティングするため、T const [N]
に対する特殊化も同時に追加しています。また、コンセプトを使用した実装によって、そのようなconst
問題を回避できる他std::get
にコンセプトによるインデックス制約をする実装も可能とのことです(現在の提案ではありません)。
P2581R0 Specifying the Interoperability of Binary Module Interface Files
ビルド済みモジュールを扱う際に、ビルドシステムがそのビルド済みモジュールファイルを直接扱うことができるかどうかを調べられるようにする提案。
バイナリモジュールインターフェースファイル(BMI)はモジュールのインターフェース部分をビルドしたものであり、モジュールをパースし直したりビルドしなおしたりすることなくモジュールのインターフェースを調べたり、モジュールがビルドされた時の情報を取得したりするのに使用されます。その形式は実装定義であり、clangとGCCの間では相互運用可能であるようですが、基本的にはコンパイラによって独自の形式です。
ビルド済みモジュールをライブラリとして配布する場合、そこに含まれるBMIがその環境のコンパイラによって使用可能であったとしても直接使用することができない場合があります(ビルドされたときの設定と使用時の設定が異なるなど)。
SG15のコンセンサスとしては、モジュールの探索にはメタデータを使用する方向性が決まっています。その探索の方法やフォーマットなどはまだ議論中ですが、モジュールライブラリがBMIを提供しそれが使用可能かどうかをコンパイラに知らせる必要があることは確実です。そして、それはビルドシステムによって認識される必要があり、現在その方法はまだありません。
この提案は、ビルドシステムがBMIを読んだりコンパイラを呼び出したりすることなくそれを認識できるようにするためのメカニズムを確立しようとするものです。
この提案では、モジュール探索時に使用するメタデータ内に、BMIの場所とともにそのフォーマットを示す識別子を必ず含むようにすることを提案しています。その識別子は少なくとも以下のものを含んでいる必要があります
- ビルドに使用されたコンパイラ種別とバージョン
- 言語バージョン
- ISA/ABIに関するオプション
また、そもそもBMIを生成する時や配布されたBMIが使用可能ではなかったとき、その環境とビルドコンテキストで使用可能なBMIを生成する必要があり、そのためにこの識別子を取得可能である必要があります。そこで、コンパイラがそのようなインターフェース(コマンドラインオプションによってコマンドライン出力するなど)を備えるようにすることも提案しています。
P2582R0 Wording for class template argument deduction from inherited constructors
クラステンプレートの実引数推定(CTAD)を継承コンストラクタからでも行えるようにする提案。
CTADはC++17で導入され、その時は非集成体のクラステンプレートそのものでしか使用できませんでした。C++20では集成体テンプレートとエイリアステンプレートに拡大されましたが、継承コンストラクタからCTADはできないままでした。
この提案はそれを解消し、継承コンストラクタをもちいてCTADできるようにしようとするものです。
template <typename T> struct Base { Base(T&&); }; template <typename T> struct Derived : public Base<T> { using Base<T>::Base; // Baseのコンストラクタを継承 } Derived d(42); // この提案後ok、Derived<int>と推論される
この手順は、派生クラスに対する基底クラスをエイリアステンプレートのように扱って、エイリアステンプレートからのCTADのアルゴリズムを流用するとともに、継承コンストラクタから生成された推論補助(関数テンプレート)を派生クラスで直接生成された推論補助よりもオーバーロード順で優先することで行われています。
最初の推論補助の抽出は、クラステンプレートC
の基底クラスにクラステンプレートB
が含まれている場合、C
のテンプレートパラメータを持ち右辺がB
であるようなエイリアステンプレートを生成し、このエイリアステンプレートを用いて推論補助を取得します。
上記の例では次のようにエイリアステンプレートとそこからの推論補助が抽出されます。
// 生成された、仮想的なエイリアステンプレート template <typename T> using D = Base<T>; // ↑から生成された推論補助 template<typename T> Derived(T&&) -> Derived<T>;
この提案は既にR1が存在するらしく、R1が次の全体会議で投票にかけられることが決まっています。
P2584R0 A More Composable from_chars
std::from_chars
をrange
対応させつつ使いやすくする提案。
std::from_chars
は文字列から数値への変換を行うものです。引数には入力文字範囲の先頭と終端を取り(C++17までのイテレータペアを受け取るインターフェースと同様)、結果は引数にとった出力用変数への参照に出力されます。戻り値には変換結果のステータスが返されます。
template<std::size_t N> int to_int(char(&input)[N]) { using namespace std::ranges; int result; // 文字列 -> int値へ変換 auto [p, err] = std::from_chars(begin(input), end(input), result); // エラーチェック if (err == std::errc{}) { return result; } else { return 0; } }
これはこれでシンプルで使いやすいインターフェースではあるのですが、入力に範囲(range
)を取れないことや、結果を受けるために一度ローカル変数を用意しなければらないなど、少し面倒なところがあります。例えば、任意の文字範囲(range
)で使用するには次のように書く必要があります
template<std::ranges::contiguous_range R> int to_int(R&& input) { using namespace std::ranges; int result; // 文字列 -> int値へ変換 auto [p, err] = std::from_chars(std::to_addres(begin(input)), std::to_addres(end(input)), result); // エラーチェック if (err == std::errc{}) { return result; } else { return 0; } }
contiguous_range
は必ずしもsized_range
ではなく、イテレータは必ずしもポインタとは限らないため、このように書くのが最適です。
この提案は、std::from_chars
にオーバーロードを追加してrange
対応させると共に、戻り値で変換結果を返すようにするものです。次のようなオーバーロードを追加することを提案しています
namespace std { // 新しいオーバーロードの戻り値型 template <typename T> struct from_chars_result_range { T value; // 変換結果 std::errc ec; std::span<const char> unparsed; }; // 整数型用 template <integral T> requires (!std::same_as<bool, T>) constexpr from_chars_result_range<T> from_chars(std::span<const char> rng, int base = 10); // 浮動小数点数型用 template <floating_point T> from_chars_result_range<T> from_chars(std::span<const char> rng, chars_format fmt = chars_format::general); }
入力範囲としてstd::span<const char>
を取るのは、std::string_view
のrange
コンストラクタがexplicit
されそうなことと、より一般の入力文字範囲は必ずしも文字列ではないと考えられるためです。
テンプレートパラメータT
には変換先の数値型を指定します。例えば先ほどのコードは次のようになります
template<std::ranges::contiguous_range R> int to_int(R&& input) { // 文字列 -> int値へ変換 auto [result, p, err] = std::from_chars<int>(input); // エラーチェック if (err == std::errc{}) { return result; } else { return 0; } }
提案文書より、views::split
と組み合わせる場合の比較例。
現在 | この提案 |
---|---|
std::string s = "1.2.3.4"; auto ints = s | std::views::split('.') | std::views::transform([](const auto & v){ int i = 0; std::from_chars(std::to_address(v.begin()), std::to_address(v.end(), i); return i; }); |
std::string s = "1.2.3.4"; auto ints = s | std::views::split('.') | std::views::transform([](const auto & v) { return std::from_chars<int>(v).value; }); |
提案では、別の実装としてstd::expected<std::from_chars_result_range<T>, std::errc>
を返すインターフェースに関しても言及されていますが、今の所はそちらはサブ案です。
P2585R0 Improving default container formatting
std::format
のコンテナに対するフォーマットを改善する提案。
std::format
のコンテナに対するフォーマットはC++23に向けて提案中(P2286)です。そこでは、コンテナ(range
)の要素型がフォーマット可能であれば、コンテナの要素列をコンテナごとに適した形式でフォーマットします。
コンテナ | 出力 |
---|---|
std::vector<std::pair<int, int>>{{1, 2}, {3, 4}} |
[(1, 2), (3, 4)] |
std::set<std::pair<int, int>>{{1, 2}, {3, 4}} |
{(1, 2), (3, 4)} |
std::map<int, int>{{1, 2}, {3, 4}} |
{1: 2, 3: 4} |
これはそれぞれのコンテナ型ごとにstd::formatter
を特殊化することで行われており、非標準のコンテナに対してはデフォルトのコンテナフォーマット(std::vector
と同様のもの)しか提供されません。
コンテナ | 出力 |
---|---|
boost::container::flat_set<int>{1, 2, 3} |
[1, 2, 3] |
absl::flat_hash_map<int, int>{{1, 2}, {3, 4}} |
[(1, 2), (3, 4)] |
{fmt}
では、formatter
は特定の標準コンテナに対して特殊化されているのではなく、map
-like(::mapped_type
の存在によって判定)、あるいはset
-like(key_type
の存在によって判定)なより広い型に対して特殊化されています。したがって、{fmt}
では非標準の連想コンテナに対しても標準のものと同様のフォーマットを行うことができます。
この提案は、{fmt}
がコンテナフォーマットのためにformatter
で行なっていることとほぼ同様のことをP2286のコンテナフォーマットに対しても適用するものです。
提案では、format_kind
というフォーマット対象の種別を表す変数テンプレート(整数or列挙値)を用意して、input_range
に対してはrange_format_kind
という列挙値をformat_kind
として特殊化します。range
用format_kind
では、range
の種別(map
orset
、それ以外など)によってrange_format_kind
の値を変化させます。
namespace std { // range型の種別を表す列挙値 enum class range_format_kind { disabled, map, set, sequence, string, debug_string, }; // フォーマット種別を指定する任意の値 template <class R> inline constexpr auto format_kind = unspecified; // format_kindのrange型に対する特殊化 template <input_range R> inline constexpr range_format_kind format_kind<R> = []{ if constexpr (requires { typename R::key_type; typename R::mapped_type; } and is-2-tuple<range_reference_t<R>>) { return range_format_kind::map; // map-likeの判定 } else if constexpr (requires { typename R::key_type; }) { return range_format_kind::set; // set-likeの判定 } else { return range_format_kind::sequence; // その他のコンテナ(範囲) } }(); }
input_range
に対するformatter
特殊化では、共通実装であるdefault-range-formatterという型を継承するようにした上で、default-range-formatterにこのformat_kind
を渡すようにします。
namespace std { // range型に対するフォーマッター共通実装 template<range_format_kind K, ranges::input_range R, class charT> struct default-range-formatter; // exposition only // range型に対するformatter特殊化 template<ranges::input_range R, class charT> requires (format_kind<R> != range_format_kind::disabled) && formattable<ranges::range_reference_t<R>, charT> struct formatter<R, charT> : default-range-formatter<format_kind<R>, R, charT> { }; }
このようにして、std::formatter
のシグネチャを変更することなく、そのフォーマッター実装に対してこのformat_kind<R>
を伝え、default-range-formatterでは各range_format_kind
の値に対して特殊化してフォーマットを調整します
namespace std { // デフォルトのコンテナフォーマット template <ranges::input_range R, class charT> struct default-range-formatter<range_format_kind::sequence, R, charT> { ... }; // map-likeコンテナに対するフォーマット template <ranges::input_range R, class charT> struct default-range-formatter<range_format_kind::map, R, charT> { ... }; // set-likeコンテナに対するフォーマット template <ranges::input_range R, class charT> struct default-range-formatter<range_format_kind::set, R, charT> { ... }; }
これによって、現在のstd::set/std::map
専用のフォーマットをそれに近い(同等な)型へと広げることができます。
また、format_kind
はrange
以外の型に対してはその値を未規定としておくことで、将来的に他の型に対してこのようなフォーマット調整を行える余地を残しています。
P2587R0 to_string
or not to_string
std::to_string
の浮動小数点数出力を修正する提案。
例えば次のコードの出力は
auto loc = std::locale("uk_UA.UTF-8"); std::locale::global(loc); std::cout.imbue(loc); setlocale(LC_ALL, "C"); std::cout << "iostreams:\n"; std::cout << 1234 << "\n"; std::cout << 1234.5 << "\n"; std::cout << "\nto_string:\n"; std::cout << std::to_string(1234) << "\n"; std::cout << std::to_string(1234.5) << "\n"; setlocale(LC_ALL, "uk_UA.UTF-8"); std::cout << "\nto_string (uk_UA.UTF-8 C locale):\n"; std::cout << std::to_string(1234) << "\n"; std::cout << std::to_string(1234.5) << "\n";
以下のようになります
iostreams: 1 234 1 234,5 to_string: 1234 1234.500000 to_string (uk_UA.UTF-8 C locale): 1234 1234,500000
std::to_string
の整数型オーバーロードは、グローバルCロケールを使用しますがグルーピングを行わず、実質的にローカライズされません。一方、浮動小数点数型オーバーロードは、グローバルCロケールを使用して小数点を取得し、グルーピングを行いません。そのため、このような結果になります。
また、浮動小数点数型オーバーロードでは固定小数点形式を常に使用するため、その出力が有用なのは限られた範囲の数値に対してのみです。
std::cout << std::to_string(std::numeric_limits<double>::max()) << "\n"; std::cout << std::to_string(-1e-7);
179769313486231570814527423731704356798070567525844996598917476803157260780028538760589558632766878171540458953514382464234321326889464182768467546703537516986049910576551282076245490090389328944075868508455133942304583236903222948165808559332123348274797826204144723168738177180919299881250404026184124858368.000000 -0.000000
1つ目の出力では最初の17桁だけが有効で残りの桁は有用ではない値です。さらに、小数点以下に無意味なゼロがあります。2つ目の出力も、小数点以下の0は無意味です。
これらのことは、std::to_string
はsprintf
を用いて定義されているためにiostreamの振る舞いと異なっていることが原因です。
この提案はこれらの問題の修正のために、std::to_string(v)
をstd::format("{}", v)
で定義し直そうとするものです。
先ほどの例の出力は次のように修正されます
iostreams: 1 234 1 234,5 to_string: 1234 1234.5 to_string (uk_UA.UTF-8 C locale): 1234 1234.5
1.7976931348623157e+308 -1e-7
この変更では整数型の出力は従来通りのままで、浮動小数点数型の出力だけが変更されます。
P2588R0 Relax std::barrier
phase completion step guarantees
std::barrier
のバリアフェーズ完了時処理が、同じバリアで同期する任意のスレッドから起動できるようにする提案。
std::barrier
はFork-Joinモデルのような並行処理の実装に活用することができる繰り返し使用可能な動機機構です。典型的には、同期に参加する全スレッドが並列実行される部分と一つのスレッドだけで実行される同期部分から構成される処理単位の繰り返しのような処理になり、その1つの処理単位のことをバリアフェーズと呼びます。
Fork-Joinモデルのイメージ(Wikipediaより)
std::barrier
はテンプレート引数として完了関数(CompletionFunction
)の型を受け取って、そのオブジェクトを保持しておくことでバリアフェーズの最後にどこか一つのスレッドでそれを実行してから、待機しているスレッドを再開します。
// 複数のスレッドで呼ばれる処理 template<typename CF> void fork_proc(std::barrier<CF>& sync) { // キャンセルされるまで行われる連続処理 // ループごとに全スレッドで同期しつつ実行される while(/*キャンセルの検出*/) { // メインの処理 ... // 全スレッドはここで待ち合わせる(1ループの処理の完了を同期する) // バリアフェーズに参加する全てのスレッドがここに到達した時、CFの処理を実行してから次のバリアフェーズを開始する sync.arrive_and_wait(); // 同期ポイント } } int main() { // 並列数 constexpr std::size_t N = 10; // スレッド数でバリアを初期化 // 同時に、同期ポイントで再開直前に実行する完了関数を指定 std::barrier sync{N, [] { // 処理対象データの更新など、同期が必要なシングルスレッド処理 ... }}; for ([[maybe_unused]] auto i : std::views::iota(0, N)) { std::thread{[&sync]{ fork_proc(sync); }}.detach(); } // 完了を待機する処理など ... }
バリアで同期する(バリアフェーズに参加する)各スレッドは.arive()
(同期ポイント到達通知)と.wait()
(他スレッドの待機)もしくは.arrive_and_wait()
(.arive()
と.wait()
の複合操作)を呼び出すことで同期を取ります。完了関数は、バリアフェーズに参加しているスレッドが全て同期ポイントに到達した後、バリアフェーズに参加するスレッドのいずれかで実行され、その実行が完了した後で次のバリアフェーズが開始されます。
現在の標準の規定では、この完了関数がどのスレッドで実行されるかは実装の自由として明確に規定していません。しかし、std::barrier
の保証やメンバ関数の規定などの相互作用から生じる意図しない制約によって、実質的に同期ポイントに最後に到達したスレッドで実行する実装しか取れないようになっているようです。
それによって、ハードウェア(GPUなど)が持っているスレッド同期機構やそのサポート機構を用いてstd::barrier
を効率的に実装することが妨げられており、この提案は、この意図しない制限を取り払うことで、std::barrier
の効率的な実装を許可しようとするものです。
これによる変更は規定の変更のみですが、保証内容や意味論が変化するために破壊的変更となります。提案ではハードウェアによる効率化に最も適した選択肢を選んでいますが、この振る舞いはstd::barrier
提案者やそれを既に使用している人によっても意外なものであり、まだ入ったばかりなこともあり影響を受けるコードは存在しないと思えるため破壊的変更の影響はメリットを上回ると主張しています。そして、この変更をC++20に対するDRとすることを提案しています。
詳細
この意図しない制限というのは、.arive()
(同期ポイント到達通知)と.wait()
(他スレッドの待機)の操作が分かれている事に原因があります。.arive()
は戻り値としてarrival_token
というものを返し、.wait()
はそのarrival_token
を受け取って待機します。すなわち、この2つの操作が分割されていることによって、バリア同期ポイント通知とバリア同期の待機を異なるスレッドで行うことができるようになっています。
これによって、恣意的ではありますが、次のようなことが起こります。
// 参加スレッド数2で初期化、完了関数cfをセット std::barrier<CF> b{2, cf}; // arrival_tokenの型 using tok_t = decltype(b.arrive()); void thread() { new tok_t(b.arrive()); // A: 同期ポイント到達を通知するが、返されるトークンを消費しない(リークしてる } // B: スレッド終了 // C: 2つのスレッドを起動 auto t0 = std::thread(thread); auto t1 = std::thread(thread); // D: 2つのスレッド終了待機 t0.join(); t1.join(); // E: 完了関数cfが呼ばれていることが保証される
この例では、2つのarrival_token
が有効であり続けながらstd::barrier
の.wait()
は呼ばれておらず、バリアフェーズに参加している(完了を待機している)スレッドが居なくなっています。現在の標準の規定はこの時でも完了関数が呼ばれていることを保証しており、それをこの場合でも確実に達成するためには、最後に.arive()
したスレッドで完了関数を実行するという実装を取らざるを得ません。
これは、現在のstd::barrier
仕様が保証している次の2つのことの相互作用の結果です
- スレッドが
.wait()
を呼び出さなくてもいい自由 - 完了関数(バリアフェーズ完了ステップ)がいつ、どこで実行されるかの保証
数百万のHWスレッドを持つアーキテクチャでは、わずかな並列化されていない処理によって大きく並列化のスケーラビリティが制限されます(アムダールの法則)。.arive()
と.wait()
を分割しているAPIはその影響は削減するためのもので、完了関数が実行するシングルスレッド処理とstd::barrier
の同期の処理を並列化し、同期をさらに別のスレッドやHWアクセラレータ(NVIDIA GPUはそれを持っている)で実行することで同期のコストを完了関数の処理の背後に隠してしまうことを目的とするものです。しかし、上記の問題によってそのような実装は現在可能となっていません・・・
この提案では、std::barrier
の保証を次のように変更することでこの問題の解決を図ります
- 累積性は以下の2つのところで確立される
- バリアフェーズに参加しているすべてのスレッドと完了関数を実行するスレッドの間
- 完了関数を実行するスレッドと
.wait()
の呼び出しによってバリアフェーズ完了を待機しているすべてのスレッドの間
- バリアフェーズ完了を待機するスレッドが無い場合、完了関数が実行されるかは未規定
- 完了関数はバリアフェーズに参加している(参加していた)スレッドの一つ、もしくは新しいスレッドで実行される
- 完了関数を実行するスレッドは未規定であり、既存のバリアと無関係なスレッドも含まれ、そのスレッドの任意の時点でコードが実行される
累積性(Cumulativity)とは、2つの処理(A, B)を接続するある地点(A -> C -> BのC)について、AがCに到達してからBが実行されるという順序関係を言うもので、特にマルチスレッド処理の場合にはAの並行処理がすべて終わってから(各スレッドの完了がCに累積してから)、Bの処理開始が実行(累積)されることを表現しています
これらの変更によって、完了関数の実行とstd::barrier
による同期は累積性が切り離されており、それによってその2つの処理を並列に実行することが許可され、なおかつそれらの処理を実行するスレッドはバリアフェーズと無関係なスレッドでも良くなり、同期ポイントでバリアフェーズに参加しているスレッドが無い場合には必ずしも完了関数を実行しなくても良い(してもいい)、となるようになります。
P2589R0 static operator[]
添え字演算子(operator[]
)を静的メンバ関数として定義できるようにする提案。
この提案のモチベーションはP1169と同じです。
operator[]
はP2128によって任意個数の引数を取ることができるようになっており、operator()
との違いはほぼ演算子の見た目だけになっています。従って、operator[]
をoperator()
と同等に扱えるようにすることで構文と意味論の一貫性が向上し、使いやすさや教えやすさの向上につながります。
operator()
ではラムダ式であったりstd::function
の様なコールラッパを考慮しなければならなかったりしますが、operator[]
の場合はそれらの考慮は必要ないため、変更はとても小さく最小限で済みます。
P2590R0 Explicit lifetime management
メモリ領域上にあるトリビアルな型のオブジェクトの生存期間を開始させるライブラリ機能の提案。
例えば、次のようなコードはC++17までは厳密には未定義動作でした
struct X { int a, b; }; X* make_x() { X *p = (X*)malloc(sizeof(struct X)); p->a = 1; // UB p->b = 2; // UB return p; }
なぜなら、malloc
で確保したメモリ領域にはX
のオブジェクトが構築されておらず、そのためX
のポインタは生存期間外のオブジェクトを参照している不正なポインタであり、それを介したアクセスは未定義動作となるためです。
とはいえこのようなコードはCでは問題なく、またC++においても低レベルな操作でよく使用されるものであります。そのためC++20では、特定のライブラリ関数によって操作されるメモリ領域上にトリビアルな型(implicit-lifetime type)のオブジェクトを暗黙的に構築するようにされました。その対象はmalloc
やmemcpy
、std::bit_cast
などです。
これ以外にも実装定義なものが含まれていますが、ユーザー定義の関数はそこには含まれません。例えば、メモリプールからメモリを割り当てる関数など、ライブラリ作成者がC++コードで記述するメモリ割り当て関数を上記例のmalloc
の代わりに使えば、相変わらず未定義動作です。
P0593R6では、この場合に明示的にオブジェクトの生存期間を介するための関数std::start_lifetime_as<T>
を提案として含んでいました。それはLEWGのレビューを通過していましたが、LWGの時間不足のためにC++20には間に合いませんでした。
この提案は、そのstd::start_lifetime_as<T>
を標準ライブラリに含めるためのものです。
struct X { int a, b; }; X* make_x() { // pの領域上でXのオブジェクトの生存期間を開始 X *p = std::start_lifetime_as<X>(malloc(sizeof(struct X))); p->a = 1; // ok p->b = 2; // ok return p; }
テンプレート引数のT
はimplicit-lifetime typeと呼ばれる型(トリビアル型や集成体型など)でなければなりません。
std::start_lifetime_as<T>(p)
は単一オブジェクトのためのものですが、このような場合実際には配列を作成することの方が多いと思われます。そのために、std::start_lifetime_as_array<T>(p, n)
も提案されています。
void process(Stream *stream) { unique_ptr<char[]> buffer = stream->read(); // stream->read()内でオブジェクト構築していたとしても、どちらかのパスは未定義動作となる if (buffer[0] == FOO) { process_foo(reinterpret_cast<Foo*>(buffer.get())); // UB } else { process_bar(reinterpret_cast<Bar*>(buffer.get())); // UB } // start_lifetime_as_arrayを使用すれば未定義動作を回避できる // ただし、要素数nが必要 if (buffer[0] == FOO) { process_foo(std::start_lifetime_as_array<Foo>(buffer.get(), n)); // ok } else { process_bar(std::start_lifetime_as_array<Bar>(buffer.get(), n)); // ok } }
std::start_lifetime_as_array<T>(p, n)
はp
の領域でT[n]
の配列の生存期間を開始するものです。
これらの関数は実際にはコンストラクタを呼び出したりするものではなく、あくまで未定義動作回避のためにコンパイラにアピールするものです。従って、実際には何もしない関数となるでしょう。
- P0593R6 Implicit creation of objects for low-level object manipulation
- トリビアルな型のオブジェクトを暗黙的に構築する - C++20 コア言語機能
- P2590 進行状況
P2591R0 Concatenation of strings and string views
std::string
とstd::string_view
を+
で結合できるようにする提案。
現在は、std::string
とstd::string_view
の間で+
を使用できません。
std::string calculate(std::string_view prefix) { return prefix + get_string(); // ERROR }
しかし、このことはstd::string
の他のAPIと一貫していません。.apend()
などの他のAPIは、すでにstring_view
(も含めたview型)を受け取れるようになっているためです。
std::string str; std::string view; // Appending str + view; // ERROR str + std::string(view); // OK, but inefficient str + view.data(); // Compiles, but BUG! std::string copy = str; copy += view; // OK, but tedious to write (requires explicit copy) copy.append(view); // OK, ditto // Prepending view + str; // ERROR std::string copy = str; str.insert(0, view); // OK, but tedious and inefficient
また、このことは生の文字列とも一貫していません。
std::string str; str + "hello"; // OK str + "hello"sv; // ERROR "hello" + str; // OK "hello"sv + str; // ERROR
この提案はstd::string
とstd::string_view
の間のoperator+
を追加することでこれらの問題を解決しようとするものです。
そもそも、C++17でこの+
が他のものと一緒に導入されなかった理由は、std::string
とstd::string_view
の間のoperator+
がそれを遅延して結合するような中間オブジェクトを返す、string builder的な実装を将来的に考慮するものだったようです(C++17時点で、LLVMのstring_view
に近いクラスの実装がそうなっていた様子)。しかし、そのような提案は現在でもなく、それを考慮したとしても生の文字列との間でそうなっていないので+
を選択するのは間違っている、と筆者の方は述べています。
P2592R0 Hashing support for std::chrono
value classes
<chrono>
の時間や日付を表す型に対してハッシュサポートを追加する提案。
<chrono>
に定義されている時間や日付を表す型にはハッシュサポートがなく、そのためそれらの型を非順序連想コンテナのキーとして使用することができません。
std::unordered_set<std::chrono::milliseconds> unique_measurements;
ただし、それらの型の実体は整数型等の数値型であるため、ハッシュを定義することは容易です。
truct duration_hash { template <class Rep, class Period> constexpr auto operator()(const std::chrono::duration<Rep, Period> &d) const noexcept(/* ... */) { std::hash<Rep> h; return h(d.count()); // } }; std::unordered_set<std::chrono::milliseconds, duration_hash> unique_measurements; // OK
<chrono>
にあるほとんどの型は同様にしてハッシュサポートを用意に追加できるため、標準がこれを提供しない理由はないと考えられるため、このようなサポートを追加しようとする提案です。
提案の対象は次のものです
std::chrono::duration
std::chrono::time_point
std::chrono::day
std::chrono::month
std::chrono::year
std::chrono::weekday
std::chrono::weekday_indexed
std::chrono::weekday_last
std::chrono::month_day
std::chrono::month_day_last
std::chrono::month_weekday
std::chrono::month_weekday_last
std::chrono::year_month
std::chrono::year_month_day
std::chrono::year_month_day_last
std::chrono::year_month_weekday
std::chrono::year_month_weekday_last
std::chrono::zoned_time
std::chrono::leap_second
time_zone
とtime_zone_link
はragular
ではない(コピーできない)ことから、ハッシュサポートは必要ないと考えられるため除外されています。
P2593R0 Allowing static_assert(false)
static_assert(false)
がテンプレートの実体化前にエラーとならないようにする提案。
if constexpr
のfalse
となるブロックでstatic_assert(false)
を使用したとき、意図通りにならず常にコンパイルエラーになってしまう問題はC++17で導入された当初から割と有名な罠でした。
template<typename T> void f(T t) { if constexpr (sizeof(T) == 4) { ... } else { static_assert(false); // 常にコンパイルエラー } }
これは、constexpr if文が条件付きのテンプレート実体化抑制を行う機能であり、static_assert(false)
はテンプレートパラメータに依存していないことからテンプレート実体化前に評価されてしまうために起きていることです。
より正確には、これは診断不要のill-formedとなりほぼ未定義動作となっていますが、現在にコンパイラはどれもこれをエラーとして診断しています。
The validity of a template may be checked prior to any instantiation. [ Note: Knowing which names are type names allows the syntax of every template to be checked in this way. — end note ] The program is ill-formed, no diagnostic required, if: - no valid specialization can be generated for a template or a substatement of a constexpr if statement within a template and the template is not instantiated, or - ...
ここでは、インスタンス化前にテンプレートの有効性をチェックできることを規定していて(いわゆるtwo-phase name lookupの一段階目に行われる)、その中でも検出されたら診断不要のill-formedとなる事項を列挙しています。その一番最初の項目が
テンプレートまたはテンプレート内constexpr ifのサブステートメントに対して有効な特殊化を生成できない事が分かっていて、テンプレートがインスタンス化されない場合(は診断不要のill-formed)
とあり、static_assert(false)
はテンプレートパラメータT
と無関係にstatic_assert
が発動する事がわかるため、まさにこれに該当しています。
この問題の回避策には例えば
static_assert(sizeof(T) == 0);
static_assert(sizeof(T*) == 0);
[]<bool flag=false>(){ static_assert(flag); }();
static_assert([]{return false;}());
などが知られています。しかし、先ほどの規定に照らせばどれも診断不要のill-formedから脱し切れておらず(少なくとも上3つは)、これらのワークアラウンドはたまたま動いているに過ぎません。
この問題のよく知られている正しい回避策は、always falseと呼ばれるイディオムです。
template<typename T> inline constepxr bool always_false = false; template<typename T> void f(T t) { if constexpr (sizeof(T) == 4) { ... } else { static_assert(always_false<T>); // Tのサイズが4以外の時にだけエラー } }
これを標準ライブラリにいれる提案もありましたが、テンプレートパラメータが値である場合にそのまま使用できず、static_assert<T>
のようなものは覚えるべきことを増やして本質的な問題に蓋をしているに過ぎません。
この提案では、テンプレート中のstatic_assert(false)
がテンプレートインスタンス化時にのみ効果を持つようにすることで、この問題の解決を図るものです。
- constexpr ifの落とし穴 - 本の虫
- C++17 constexpr if 文 - cpprefjp
- constexpr if and static_assert - stack overflow
- P2593 進行状況