文書の一覧
全部で122本あります。
もくじ
- P0149R1 Generalised member pointers
- P0260R14 C++ Concurrent Queues
- P0876R19 fiber_context - fibers without scheduler
- P1030R8 std::filesystem::path_view
- P1839R7 Accessing object representations
- P2079R6 System execution context
- P2414R5 Pointer lifetime-end zap proposed solutions
- P2434R3 Nondeterministic pointer provenance
- P2654R1 Modules and Macros
- P2663R6 Proposal to support interleaved complex values in std::simd
- P2664R9 Proposal to extend std::simd with permutation API
- P2688R5 Pattern Matching: match Expression
- P2719R2 Type-aware allocation and deallocation functions
- P2719R3 Type-aware allocation and deallocation functions
- P2746R7 Deprecate and Replace Fenv Rounding Modes
- P2758R4 Emitting messages at compile time
- P2806R3 do expressions
- P2825R4 Overload resolution hook: declcall( unevaluated-call-expression )
- P2830R8 Standardized Constexpr Type Ordering
- P2830R9 Standardized Constexpr Type Ordering
- P2841R6 Concept and variable-template template-parameters
- P2843R1 Preprocessing is never undefined
- P2883R1 offsetof Should Be A Keyword In C++26
- P2899R0 Contracts for C++ - Rationale
- P2900R13 Contracts for C++
- P2933R3 Extend header function with overloads for std::simd
- P2952R2 auto& operator=(X&&) = default
- P2953R1 Forbid defaulting operator=(X&&) &&
- P2971R3 Implication for C++
- P2988R9 std::optional<T&>
- P2996R9 Reflection for C++26
- P3019R12 Vocabulary Types for Composite Class Design
- P3045R5 Quantities and units library
- P3070R2 Formatting enums
- P3081R1 Core safety profiles for C++26
- P3086R3 Proxy: A Pointer-Semantics-Based Polymorphism Library
- P3094R6 std::basic_fixed_string
- P3111R3 Atomic Reduction Operations
- P3125R2 constexpr pointer tagging
- P3139R1 Pointer cast for unique_ptr
- P3148R1 Formatting of chrono Time Values
- P3164R3 Early Diagnostics for Sender Expressions
- P3176R1 The Oxford variadic comma
- P3179R5 C++ parallel range algorithms
- P3206R0 A sender query for completion behaviour
- P3229R0 Making erroneous behaviour compatible with Contracts
- P3289R1 Consteval blocks
- P3347R1 Invalid/Prospective Pointer Operations
- P3348R2 C++26 should refer to C23 not C17
- P3351R2 views::scan
- P3367R3 constexpr coroutines
- P3373R1 Of Operation States and Their Lifetimes
- P3374R1 Adding formatter for fpos
- P3375R2 Reproducible floating-point results
- P3385R3 Attributes reflection
- P3388R1 When Do You Know connect Doesn't Throw?
- P3394R1 Annotations for Reflection
- P3395R0 Formatting of std::error_code
- P3400R0 Specifying Contract Assertion Properties with Labels
- P3402R2 A Safety Profile Verifying Initialization
- P3407R1 Make idiomatic usage of offsetof well-defined
- P3411R1 any_view
- P3412R1 String interpolation
- P3420R1 Reflection of Templates
- P3423R1 Extending User-Generated Diagnostic Messages
- P3425R1 Reducing operation-state sizes for subobject child operations
- P3430R2 simd issues: explicit, unsequenced, identity-element position, and members of disabled simd
- P3431R0 Deprecate const-qualifier on begin/end of views
- P3439R1 Chained comparisons: Safe, correct, efficient
- P3475R1 Defang and deprecate memory_order::consume
- P3477R2 There are exactly 8 bits in a byte
- P3480R3 std::simd is a range
- P3481R1 std::execution::bulk() issues
- P3491R1 define_static_{string,object,array}
- P3496R0 Immediate-Escalating Expressions
- P3499R0 Exploring strict contract predicates
- P3500R0 Are Contracts "safe"?
- P3501R0 The ad-dressing of cats
- P3506R0 P2900 Is Still not Ready for C++26
- P3516R0 Uninitialized algorithms for relocation
- P3527R1 Pattern Matching: variant-like and std::expected
- P3533R1 constexpr virtual inheritance
- P3534R0 Avoid UB When Compiling Code That Violates Library Specification
- P3541R1 Violation handlers vs noexcept
- P3546R0 Explicit return type deduction for std::numeric_limits and numbers
- P3547R0 Modeling Access Control With Reflection
- P3548R0 P1030 std::filesystem::path_view forward progress options
- P3549R0 Diverging expressions
- P3550R0 Imports cannot ...
- P3552R0 Add a Coroutine Lazy Type
- P3554R0 Non-transient allocation with vector and basic_string
- P3555R0 An infinite range concept
- P3557R0 High-Quality Sender Diagnostics with Constexpr Exceptions
- P3558R0 Core Language Contracts By Default
- P3559R0 Trivial relocation: One trait or two?
- P3560R0 Error Handling in Reflection
- P3561R0 Index based coproduct operations on variant, and library wording
- P3564R0 Make the concurrent forward progress guarantee usable in bulk
- P3565R0 Virtual floating-point values
- P3566R0 You shall not pass char* - Safety concerns working with unbounded null-terminated strings
- P3567R0 flat_meow Fixes
- P3568R0 break label; and continue label;
- P3569R0 Split define_aggregate from Reflection
- P3572R0 Pattern matching
- P3573R0 Contract concerns
- P3574R0 Constexpr Callables
- P3575R0 SG14: Low Latency/Games/Embedded/Financial Trading virtual Meeting Minutes 2024/11/13
- P3576R0 SG19: Machine Learning virtual Meeting Minutes to 2024/11/14-2024/12/12
- P3577R0 Require a non-throwing default contract-violation handler
- P3578R0 Language Safety and Grandma Safety
- P3579R0 Fix matching of non-type template parameters when matching template template parameters
- P3580R0 The Naming of Things
- P3581R0 No, inplace_vector shouldn't have an Allocator
- P3582R0 Observed a contract violation? Skip subsequent assertions!
- P3583R0 Contracts, Types & Functions
- P3584R0 Enrich Facade Creation Facilities for the Pointer-Semantics-Based Polymorphism Library - Proxy
- P3585R0 allocator_traits::is_internally_relocatable
- P3586R0 The Plethora of Problems With Profiles
- P3587R0 Reconsider reflection access for C++26
- P3588R0 Allow static data members in local and unnamed classes
- P3589R0 C++ Profiles: The Framework
- P3590R0 Constexpr Coroutines Burdens
- おわり
P0149R1 Generalised member pointers
メンバポインタの表現可能な範囲を拡張する提案。
現在のメンバポインタはあるクラス型の直接のメンバに対するもののみを表現することができ、これはクラスの先頭アドレスからのオフセットによって実装されています。
同様に、クラスの先頭からの固定のオフセットに存在するもののメンバポインタで表現できないものとして、非仮想基底クラスとメンバのメンバ、メンバの非仮想基底クラスなどがあります。
この制限は以前にも何度か指摘されたものの、実装の制約などから来るものではなく誰も提案していないからそうなっていない、というだけの制限のようです。
この提案は、メンバポインタの現在の制限を緩和して、上記のものを表現できるようにしようとするものです。ここで提案されているのは次の3つです
- メンバ変数型へのアップキャスト
- メンバのメンバへのポインタの作成
- 基底クラスサブオブジェクトへのポインタの作成
1つ目は、メンバポインタを、そのメンバの型の基底クラスにアップキャストする変換を許可することです。次のコードの最後のものがそれにあたります
struct A {}; struct B : A{}; struct C { B b; }; C c; B* to_b = &c.b; // OK、Normal pointer B C::* c_to_b = &C::b; // OK、C++98 member pointer A* to_a = to_b; // OK、C++98 implicit upcast A C::* c_to_a = c_to_b; // NG、この提案ではok
ただし、キャスト先が仮想基底クラスの場合は実行時型情報の必要性やABI破壊の懸念などの理由から、ここでは提案されていません。また、これに対応するダウンキャスト(c_to_a
-> c_to_b
のキャスト)もstatic_cast
によって許可するようにすることを提案しています。
2つ目は、メンバのメンバへのポインタを取得するための構文を用意するものです。そのために、.
・[]
・.*
の3つの演算子を拡張して、
.
: 「クラス型T2
のT1
型メンバへのポインタ」型の式と「型T3
のT2
型メンバ」を指定する識別子名、に対して.
が適用された場合、「型T3
のT1
のメンバへのポインタ」型の値になるE1.*(E2.identifier)
という式は(E1.*E2).identifier
と等価である必要があるE1, E2
はT1, T2
型の式、式全体の型はT3
[]
: 「クラス型T1
の型T2
の配列メンバへのポインタ」型の式に対して[]
が適用された場合、「クラス型T1
のT2
型メンバへのポインタ」型の値になるE1.*(E2[E3])
という式は(E1.*E2)[E3]
と等価である必要があるE1, E2
はT1, T2
型の式、E3
はT2
の有効なインデックス
.*
: 「クラス型T2
のT1
型メンバへのポインタ」型の式と「クラス型T3
のT2
型メンバへのポインタ」型の式、に対して.*
が適用された場合、「クラス型T3
のT1
のメンバへのポインタ」型の値になるE1.*(E2.*E3)
という式は(E1.*E2).*E3
と等価である必要があるE1, E2
はT1, T2
型の式、式全体の型はT3
struct A { int i; }; struct B { constexpr B(){}; A a{}; int is[42]{}; }; constexpr A B::* ap = &B::a; constexpr int (B::*isp)[42] = &B::is; constexpr int A::*ip = &A::i; constexpr B b; constexpr auto& i_1 = (b.*ap).i; // OK, C++98 constexpr auto& i_2 = b.*(ap.i); // NG、この提案ではok constexpr auto& is7_1 = (b.*isp)[7]; // OK, C++98 constexpr auto& is7_2 = b.*(isp[7]); // NG、この提案ではok constexpr auto& i_3 = (b.*ap).*ip; // OK, C++98 constexpr auto& i_4 = b.*(ap.*ip); // NG、この提案ではok static_assert(&i_1 == &i_2); // NG、この提案ではok static_assert(&i_1 == &i_3); // NG、この提案ではok static_assert(&i_1 == &i_4); // OK, C++17 static_assert(&is7_1 == &is7_2); // NG、この提案ではok
ここではメンバポインタの取得ではなくメンバポインタを介したオブジェクト参照の取得までやっているので、少しわかりづらいかもしれません。.
の場合はT2 = B, T1 = A, T3 = int
、[]
の場合はT2 = B, T1 = int
、.*
の場合はT2 = A, T1 = int, T3 = B
と読み替えると少しわかりやすいかもしれません・・・
最後は、クラス型の基底クラスサブオブジェクトを指すメンバポインタを生成する構文です。
struct A {}; template <int N> struct B : A {}; struct C : B<0>, B<1> {};
この場合に、C
の基底クラスA
のサブオブジェクトを指すA C::*
のメンバポインタを作成できるようにしたいわけです。しかし、基底クラスのサブオブジェクトを指定する構文はないので、それを用意する必要があります。この提案ではそれを用意せずに、最初の場合のメンバポインタのアップキャストの場合の延長で、this
のアップキャストによって基底クラスサブオブジェクトへのメンバポインタを生成することを提案しています。
そのための構文には&T::this
というものを提案しており、これの結果は型T
のT
メンバへのポインタを形成し、それが暗黙的にA C::*
などにアップキャストされることで基底クラスサブオブジェクトへのポインタを得ることができます。
A A::* a_to_a = &A::this; // この提案のthisサブオブジェクトへのメンバポインタ取得 A B<1>::* b1_to_a = a_to_a; // C++98 class type downcast A C::* c_to_b1_a = b1_to_a; // C++98 class type downcast C C::* c_to_c = &C::this; // この提案のthisサブオブジェクトへのメンバポインタ取得 B<1> C::* c_to_b1 = c_to_c; // この提案のメンバ型のアップキャスト A C::* c_to_b1_a = c_to_b1; // この提案のメンバ型のアップキャスト A C::* c_to_b1_a = &B<1>::this; // 上記2つの提案機能の組み合わせ
当然ですが、これは仮想基底クラスに対しては使用できません。
現在のメンバポインタの実装は一般的に、クラス先頭アドレスからのオフセットとして表現されているため、これらの機能は比較的容易に実装できるはずだとしています。ただし、仮想基底クラスへのポインタは単なるオフセット以上のものを持つため、この方法では表現可能ではありません。
しかしなぜか、MSVCは仮想基底クラスへのメンバポインタ的な機能を実装しているようで、そこでは単なるオフセット以上の複雑な表現を持っています。そして、ここで提案されている機能とMSVCのこれは本質的に非互換なものなので、MSVCの機能に対して段階的な廃止を推奨しています。
P0260R14 C++ Concurrent Queues
標準ライブラリに並行キューを追加するための設計を練る提案。
以前の記事を参照
- P0260R5 C++ Concurrent Queues - [C++]WG21月次提案文書を眺める(2023年01月)
- P0260R7 C++ Concurrent Queues - [C++]WG21月次提案文書を眺める(2023年07月)
- P0260R8 C++ Concurrent Queues - [C++]WG21月次提案文書を眺める(2024年04月)
- P0260R9 C++ Concurrent Queues - [C++]WG21月次提案文書を眺める(2024年05月)
- P0260R10 C++ Concurrent Queues - [C++]WG21月次提案文書を眺める(2024年07月)
- P0260R11 C++ Concurrent Queues - [C++]WG21月次提案文書を眺める(2024年10月)
- P0260R13 C++ Concurrent Queues - [C++]WG21月次提案文書を眺める(2024年12月)
このリビジョンでの変更は
- 寄せられたフィードバックに基づく文言更新
- ノンブロッキング関数の例を追加
std::expected
についての説明を追加- async senderの動作についての説明を追加
などです。
P0876R19 fiber_context
- fibers without scheduler
スタックフルコルーチンのためのコンテキストスイッチを担うクラス、fiber_context
の提案。
以前の記事を参照
- P0876R11
fiber_context
- fibers without scheduler - WG21月次提案文書を眺める(2022年10月) - P0876R12
fiber_context
- fibers without scheduler - WG21月次提案文書を眺める(2023年02月) - P0876R13
fiber_context
- fibers without scheduler - WG21月次提案文書を眺める(2023年04月) - P0876R14
fiber_context
- fibers without scheduler - WG21月次提案文書を眺める(2023年10月) - P0876R15
fiber_context
- fibers without scheduler - WG21月次提案文書を眺める(2024年02月) - P0876R16
fiber_context
- fibers without scheduler - WG21月次提案文書を眺める(2024年04月) - P0876R16
fiber_context
- fibers without scheduler - WG21月次提案文書を眺める(2024年04月) - P0876R17
fiber_context
- fibers without scheduler - WG21月次提案文書を眺める(2024年07月) - P0876R18
fiber_context
- fibers without scheduler - WG21月次提案文書を眺める(2024年10月)
このリビジョンでの変更は
- 例外状態テストプログラムを付録に移動
- Windows/Linuxにおいてlibstdc++を使用してファイバー固有の例外処理を正しく動作させるBoost.Contextのパッチへのリンクを追加
- ファイバーを用いて構築された6つの製品レベルライブラリへのリンクを追加
などです。
P1030R8 std::filesystem::path_view
パス文字列を所有せず参照するstd::filesystem::path_view
の提案。
以前の記事を参照
- P1030R4
std::filesystem::path_view
- WG21月次提案文書を眺める(2020年12月) - P1030R5
std::filesystem::path_view
- WG21月次提案文書を眺める(2022年09月) - P1030R6
std::filesystem::path_view
- WG21月次提案文書を眺める(2023年07月) - P1030R7
std::filesystem::path_view
- WG21月次提案文書を眺める(2024年09月)
このリビジョンでの変更は、path
のフォーマットオプションに??
(path_view
のバックエンドがbyte
の場合のみパスをエスケープ済み文字列としてフォーマットする)を追加したことです。
この提案はLEWGでレビューされていましたが、機能を支持する人がいないとのことで追及が停止されています。
P1839R7 Accessing object representations
reinterpret_cast<char*>
によるオブジェクト表現へのアクセスを未定義動作とならないようにする提案。
以前の記事を参照
- P1839R3 Accessing Object Representations - [C++]WG21月次提案文書を眺める(2022年02月)
- P1839R4 Accessing Object Representations - [C++]WG21月次提案文書を眺める(2022年03月)
- P1839R5 Accessing Object Representations - [C++]WG21月次提案文書を眺める(2022年06月)
- P1839R6 Accessing Object Representations - [C++]WG21月次提案文書を眺める(2024年10月)
このリビジョンでの変更は、
volatile
オブジェクトではオブジェクト表現へのアクセスは許可されなくなったstd::launder
は同じ型のオブジェクトが複数存在する場合に未規定の選択を返すようになった- また、
T
がcv std::byte
またはその配列型である場合も許可される - これにより、オブジェクト表現へのポインタまたはその配列へのポインタを取得できるようになる
- また、
- synthesized object表現の要素の生存期間は、構築と破棄の全期間をカバーするものと見做されるようになった
- synthesized object表現は初期化する必要がないこと(つまり、適切なストレージが確保され次第生存期間が開始される)と、そのストレージが実際のオブジェクト内にネストされたオブジェクトによって再利用されるものとはみなされないこと、を明確にした
p(b)
の記述において、非アクティブな共用体メンバとそのサブオブジェクトが除外されるようになった- そのため、可能な場合はアクティブな共用体メンバが取得され、そうでない場合そのビットはパティングビットとして扱われる
- 未規定のオブジェクト表現ビットは安定的(値は未規定のまま)であり、不確定でもエラー性でもない事が明示的に指定されるようになった
char
/char
の配列のオブジェクト表現の要素の値を決定する際に、“congruent to”という言葉を使用するようにした- サブオブジェクトのオブジェクト表現は、もはや囲むオブジェクトのオブジェクト表現内にネストされているとはみなされない
- 代わりに、オブジェクト表現がストレージを共有し、他のオブジェクトのストレージを再利用しないことを許可する明示的な例外を設けた
- また、synthesized object表現はストレージを提供しないことを明確にする
- non-potentially-overlappingサブオブジェクトのオブジェクト表現に関する文言の改善
unsigned char
の完全な配列のみがそれ自身のオブジェクト表現として許可される理由の説明の追加- strict aliasing ruleは、オブジェクト表現へのアクセスが元のオブジェクトではなく
unsigned char
オブジェクトへのアクセスになった事を反映するように変更された char*
へのキャストによってオブジェクト表現の要素へのポインタを取得する、という文言を復元- R3で誤って削除されていた
- [expr.static.cast]p13から“would be well-formed”である条件を削除
const
性を破棄するケースは段落の前部分でチェックされる
などです。
P2079R6 System execution context
ハードウェアの提供するコア数(スレッド数)に合わせた固定サイズのスレッドプールを提供するSchedulerの提案。
- P2079R1 Parallel Executor - [C++]WG21月次提案文書を眺める(2020年8月)
- P2079R2 System execution context - [C++]WG21月次提案文書を眺める(2022年1月)
- P2079R3 System execution context - [C++]WG21月次提案文書を眺める(2022年07月)
- P2079R4 System execution context - [C++]WG21月次提案文書を眺める(2024年05月)
- P2079R5 System execution context - [C++]WG21月次提案文書を眺める(2024年10月)
このリビジョンでの変更は
- 2024/11のWrocław会議におけるSG1/LEWGのフィードバックを反映
- delegatee schedulersに関する文言を削除
system_scheduler
のsender
は、std::exception_ptr
引数を用いてset_error
で完了できるようになったset_stopped_t
に対するsystem_scheduler
のquery(get_completion_scheduler_t)
オーバーロードを削除system_scheduler
の特殊メンバ関数にnoexcept
を付加system sender
クラスに左辺値connect
を追加system sender
クラスにsender_concept, completion_signatures, get_env
を追加し、sender
とする- replaceabilityを義務付ける
- replaceabilityメカニズムを定義する(実行時とリンク時の両方)
- replaceabilityのAPIを追加
- Specificationセクションを追加
などです。
P2414R5 Pointer lifetime-end zap proposed solutions
Pointer lifetime-end zapと呼ばれる問題の解決策の提案。
以前の記事を参照
- P2414R0 Pointer lifetime-end zap proposed solutions - WG21月次提案文書を眺める(2021年07月)
- P2414R1 Pointer lifetime-end zap proposed solutions - WG21月次提案文書を眺める(2021年08月)
- P2414R2 Pointer lifetime-end zap proposed solutions - WG21月次提案文書を眺める(2023年12月)
- P2414R3 Pointer lifetime-end zap proposed solutions - WG21月次提案文書を眺める(2024年04月)
- P2414R4 Pointer lifetime-end zap proposed solutions - WG21月次提案文書を眺める(2024年08月)
このリビジョンでの変更は
- P2434への参照を更新
- Martin Uecker氏を著者から貢献者リストへ移動(本人の要望
- SG1からのフィードバックを適用
usable_ptr<T>
のエイリアスを追加usable_ptr<T>
のoperator*
の宣言を修正volatile atomic<T>
へのキャストコードを修正- P2434R1の“words of power”について由来を調査したものの発見できなかった
- EWGからのフィードバックを適用
- N4993のWDに対応するために、“representation bytes”から“value representation”へ更新
ポインタのprovenanceが確立される前に、オブジェクトの複数のインスタンスが作成および削除される可能性を考慮して、“prospective pointer value”の定義を更新
P2434R3 Nondeterministic pointer provenance
現在のC++のポインタ意味論をポインタのprovenanceモデルに対して整合させるための提案。
以前の記事を参照
- P2434R0 Nondeterministic pointer provenance - WG21月次提案文書を眺める(2021年11月)
- P2434R1 Nondeterministic pointer provenance - WG21月次提案文書を眺める(2024年05月)
- P2434R2 Nondeterministic pointer provenance - WG21月次提案文書を眺める(2024年10月)
このリビジョンでの変更は
- N5001にリベース
- 文言中の
%p
を処理 - エイリアシングの例を強化
- アライメントに関する議論を(軽微に)修正
- 編集手順の明確化
などです。
P2654R1 Modules and Macros
標準ライブラリで提供されるマクロを、標準ライブラリモジュール(std.compat
)からエクスポートするようにする提案。
以前の記事を参照
このリビジョンでの変更は
- C23で
ATOMIC_VAR_INIT
が削除されたが、ATOMIC_FLAG_INIT
は削除されていないことを反映 offsetof
とvar-args
の提案をより適切に説明<cassert>
はcontract_assert
によって不要になったと結論付けた- Cストリームオブジェクトに対するP3208R0の作業への参照を追加
- Cスタイルの可変長引数関数に関するP3550R0の作業への参照を追加
- マクロのカテゴリを中心に提案を再構成
などです。
P2663R6 Proposal to support interleaved complex values in std::simd
std::simd
でstd::complex
をサポートできるようにする提案。
以前の記事を参照
- P2663R0 Proposal to support interleaved complex values in
std::simd
- WG21月次提案文書を眺める(2022年10月) - P2663R1 Proposal to support interleaved complex values in
std::simd
- WG21月次提案文書を眺める(2023年02月) - P2663R3 Proposal to support interleaved complex values in
std::simd
- WG21月次提案文書を眺める(2023年05月) - P2663R4 Proposal to support interleaved complex values in
std::simd
- WG21月次提案文書を眺める(2023年10月) - P2663R5 Proposal to support interleaved complex values in
std::simd
- WG21月次提案文書を眺める(2023年12月)
このリビジョンでの変更は
- P1928R13で現在使用されているスタイルに合致するように文言を更新
- LWGレビューコメントへの対応
- 再確認が必要なLEWG設計イシューをいくつか指摘
などです。
P2664R9 Proposal to extend std::simd with permutation API
std::simd
に、permute操作のサポートを追加する提案。
以前の記事を参照
- P2664R0 Proposal to extend
std::simd
with permutation API - WG21月次提案文書を眺める(2022年11月) - P2664R1 Proposal to extend
std::simd
with permutation API - WG21月次提案文書を眺める(2023年02月) - P2664R3 Proposal to extend
std::simd
with permutation API - WG21月次提案文書を眺める(2023年05月) - P2664R4 Proposal to extend
std::simd
with permutation API - WG21月次提案文書を眺める(2023年10月) - P2664R5 Proposal to extend
std::simd
with permutation API - WG21月次提案文書を眺める(2023年12月) - P2664R6 Proposal to extend
std::simd
with permutation API - WG21月次提案文書を眺める(2024年01月) - P2664R7 Proposal to extend
std::simd
with permutation API - WG21月次提案文書を眺める(2024年07月) - P2664R8 Proposal to extend
std::simd
with permutation API - WG21月次提案文書を眺める(2024年10月)
このリビジョンでの変更は
- P1928R15の変更に合わせて、若干の名称変更
basic_simd_mask
の引数として要素サイズではなく型が渡されていたミスを修正gather_from
をpartial_gather_from
/unchecked_gather_from
に拡張し、それぞれの動作を適切に反映したscatter
関数についても同様
などです。
P2688R5 Pattern Matching: match
Expression
C++へのパターンマッチング導入に向けて、別提案との基本構造の違いに焦点を当てた議論。
以前の記事を参照
- P2688R0 Pattern Matching Discussion for Kona 2022 - WG21月次提案文書を眺める(2022年10月)
- P2688R1 Pattern Matching:
match
Expression - WG21月次提案文書を眺める(2024年02月) - P2688R2 Pattern Matching:
match
Expression - WG21月次提案文書を眺める(2024年09月) - P2688R3 Pattern Matching:
match
Expression - WG21月次提案文書を眺める(2024年10月) - P2688R4 Pattern Matching:
match
Expression - WG21月次提案文書を眺める(2024年12月)
このリビジョンでの変更は
- 文言の改善
- Alternative Patternの説明を、
std::cast
からADLによるtry_cast
に変更
などです。
P2719R2 Type-aware allocation and deallocation functions
↓
P2719R3 Type-aware allocation and deallocation functions
型を指定する形のnew/delete
演算子カスタマイズ方法の提案。
- P2719R0 Type-aware allocation and deallocation functions - WG21月次提案文書を眺める(2024年05月)
- P2719R1 Type-aware allocation and deallocation functions - WG21月次提案文書を眺める(2024年10月)
R2での変更は、提案する文言を追加したことです。
このリビジョンでの変更は
- EWGレビュー用に設計上の選択肢を追加
- 定数式の評価について
- 修飾子の削除について
- 一致する宣言スコープの強制について
- コルーチンとの相互作用について言及
- より包括的な文言に改善
- 機能テストマクロを追加
- [expr.new]と[expr.delete]の型同一性パラメータの内容をより適切に説明
- 型を認識する
operator delete
はdestroying operator deleteにはならないことを明示的に記述 - 定数式で型を認識する
operator new
を使用できるようにするための文言を追加
- Brian, Corentin, Richard氏からのフィードバックを反映
などです。
P2746R7 Deprecate and Replace Fenv Rounding Modes
浮動小数点環境の丸めモード指定関数std::fesetround()
を非推奨化して置き換える提案。
以前の記事を参照
- P2746R0 Deprecate and Replace Fenv Rounding Modes - WG21月次提案文書を眺める(2023年01月)
- P2746R1 Deprecate and Replace Fenv Rounding Modes - WG21月次提案文書を眺める(2023年04月)
- P2746R2 Deprecate and Replace Fenv Rounding Modes - WG21月次提案文書を眺める(2023年05月)
- P2746R3 Deprecate and Replace Fenv Rounding Modes - WG21月次提案文書を眺める(2023年08月)
- P2746R4 Deprecate and Replace Fenv Rounding Modes - WG21月次提案文書を眺める(2024年02月)
- P2746R5 Deprecate and Replace Fenv Rounding Modes - WG21月次提案文書を眺める(2024年04月)
- P2746R6 Deprecate and Replace Fenv Rounding Modes - WG21月次提案文書を眺める(2024年10月)
このリビジョンでの変更は
- いくつかの整理
- 実装に関する議論を追加
- 実装作業は未完了
- SG6がこの提案とP3375との関係について確認するまで文言の確定は延期
- この確認によっては、ここに記載された詳細の一部が影響を受ける可能性がある
などです。
P2758R4 Emitting messages at compile time
コンパイル時に任意の診断メッセージを出力できるようにする提案。
以前の記事を参照
- P2758R0 Emitting messages at compile time - WG21月次提案文書を眺める(2023年01月)
- P2758R1 Emitting messages at compile time - WG21月次提案文書を眺める(2023年12月)
- P2758R2 Emitting messages at compile time - WG21月次提案文書を眺める(2024年02月)
- P2758R3 Emitting messages at compile time - WG21月次提案文書を眺める(2024年05月)
このリビジョンでの変更は
- 文言変更
- CWG/LEWGへの再ターゲット
- 適切な文言とエスカレーション問題への対処のため、constexpr-erroneousの概念を導入
などです。
constexpr-erroneousの概念は、定数式におけるエラーを識別し、その式の評価が実行時に延期されることなくコンパイルエラーにするための規格上の仕組みを指す言葉です。
この提案では、constexpr_error_str()
が呼ばれるとコンパイルエラーを起こして定数式を中断するとともに指定されたメッセージをエラーメッセージとして出力しますが、通常の定数式は必ずしも定数評価が必要でない場所では定数式の評価が行き詰まると実行時に評価するようにフォールバックしてコンパイルエラーにはならない場合があります。例えば静的記憶域期間の変数の初期化式があり、定数初期化ができない場合は動的初期化にフォールバックします。
constexpr_error_str()
が示すエラーはこのようなフォールバックの対象外のエラーであり、なおかつ確実にそこでコンパイルを止めることを意図しています。この種類の失敗を識別するために、constexpr-erroneous (value)というコンパイル時のエラーカテゴリを追加し、constexpr_error_str()
はconstexpr-erroneousカテゴリのエラーを引き起こすように指定されます。
P2806R3 do expressions
値を返せるスコープを導入するdo
式の提案。
以前の記事を参照
- P2806R0
do
expressions - WG21月次提案文書を眺める(2023年02月) - P2806R1
do
expressions - WG21月次提案文書を眺める(2023年03月) - P2806R2
do
expressions - WG21月次提案文書を眺める(2023年12月)
このリビジョンでの変更は、P3549R0 Diverging Expressions におけるdivergenceに関する長い議論の文言と参照を追加したことです。
P2825R4 Overload resolution hook: declcall( unevaluated-call-expression )
与えられた式が呼び出す関数の関数ポインタを取得する言語機能の提案。
以前の記事を参照
- P2825R0 calltarget(unevaluated-call-expression) - WG21月次提案文書を眺める(2023年02月)
- P2825R2 Overload Resolution hook: declcall(unevaluated-postfix-expression) - WG21月次提案文書を眺める(2024年04月)
- P2825R3 Overload Resolution hook: declcall(unevaluated-postfix-expression) - WG21月次提案文書を眺める(2024年12月)
このリビジョンでの変更は
- 提案する文言を追加し、レビューによって修正
- メンバ関数へのdevirtualizedポインタを追加
などです。
P2830R8 Standardized Constexpr Type Ordering
↓
P2830R9 Standardized Constexpr Type Ordering
std::type_info::before()
をconstexpr
にする提案。
以前の記事を参照
- P2830R0 constexpr type comparison - WG21月次提案文書を眺める(2023年04月)
- P2830R1 constexpr type comparison - WG21月次提案文書を眺める(2023年12月)
- P2830R3 constexpr type comparison - WG21月次提案文書を眺める(2024年04月)
- P2830R3 Standardized Constexpr Type Ordering - WG21月次提案文書を眺める(2024年05月)
- P2830R3 Standardized Constexpr Type Ordering - WG21月次提案文書を眺める(2024年12月)
R8での変更は、LWGメーリングリストでの指摘に伴う文言の修正です。
このリビジョンでの変更は、LWGレビューによるeditorialな文言修正です。
P2841R6 Concept and variable-template template-parameters
コンセプトを受け取るためのテンプレートテンプレートパラメータ構文の提案。
以前の記事を参照
- P2841R0 Concept Template Parameters - WG21月次提案文書を眺める(2023年05月)
- P2841R1 Concept Template Parameters - WG21月次提案文書を眺める(2023年10月)
- P2841R2 Concept Template Parameters - WG21月次提案文書を眺める(2024年04月)
- P2841R3 Concept Template Parameters - WG21月次提案文書を眺める(2024年05月)
- P2841R4 Concept and variable-template template-parameters - WG21月次提案文書を眺める(2024年09月)
- P2841R5 Concept and variable-template template-parameters - WG21月次提案文書を眺める(2024年10月)
このリビジョンでの変更は
- コンセプトテンプレートパラメータに制約を行えないことを指定
- concept-nameが依存関係にある場合、concept-idは値に依存することを指定
- テンプレートテンプレートパラメータがコンセプト依存の制約(concept dependent constraint)によって制約されないことを指定
などです。
テンプレートテンプレートパラメータがコンセプト依存の制約(concept dependent constraint)によって制約されないことを指定
この変更は次のような場合に対処するためのものです
template < template <typename> concept C, template <C> class TT > struct A { static int x; }; template < template <typename> concept C, template <C> class TT > int A<C, TT>::x = 0; // Aを指名することができない(R5まで
C++におけるテンプレートの半順序のルールは歴史的に、「置換前に、テンプレート引数に依存せずに決定可能」でした。この例では、TT
はコンセプトC
によって制約されるテンプレートパラメータを一つ持つ型であることを表していますが、具体的なC
が渡されるまではテンプレートパラメータを取ることとその数くらいしか分からず、A
の特殊化が複数ある場合のマッチングにおける半順序をC
が渡される前に決定することが困難になります。
そのためこの提案では、このようなC
の使用を禁止することでこのような状況が起こらないようにして、C++のテンプレートにおける半順序のルールとその実装の複雑化を回避しています。
P2843R1 Preprocessing is never undefined
プリプロセッサに存在する未定義動作を取り除く提案。
以前の記事を参照
このリビジョンでの変更は
- コンパイル中のUBに関する根拠をintroductionに組み込み
- 関連領域の未解決のコア言語Issueのリンクを追加
- target audienceにCのliaisonを追加
- P2621R2採択後のWDにリベース
- 採択済みの提案についての議論をすべて削除
- conflict resolutionに関するサブセクションを削除
- 提案された文言をN5001にリベース
defined
テストにおけるコンパイラの適合性を更新- 全てのコンパイラがこの提案の推奨事項に準拠している
- UBからIFNDRへの残りのケースにおける診断を強化する初期分析を完了
- ライブラリによるキーワードの再
define
禁止をプリプロセッサ仕様に盛り込む事を提案 - プリプロセッサ仕様で使用されているものを含む、禁止されるマクロ名のリストを完成させることを提案
- ill-formed commentsの診断を提案
- 未解決のコア言語Issueに関する文言にセクションを追加
- この提案が採択された後の実装の変更について、コンパイラ毎の概要を追記
などです。
P2883R1 offsetof
Should Be A Keyword In C++26
offsetof
をキーワード化し、offsetof
マクロの機能を言語機能とする提案。
以前の記事を参照
このリビジョンでの変更は
- EWG, Varna 2023のレビューを記録
- レビューを受けてのモチベーションの強化
- CWG2784に関する議論を追加
- リフレクションを用いたライブラリソリューションについて議論
- P1839R6とそのフォローアップであるP3407R0についての議論を追加
- 使用経験あるいはそれに近い情報について追加
- 提案する文言を追加
などです。
この提案はEWGにおけるレビューにおいてコンセンサスを得ることができず、一旦追及は停止されています。
P2899R0 Contracts for C++ - Rationale
P2900のContracts提案に関する、議論の流れや設計根拠についてまとめた文書。
P2900では文言の他には、結果として決定された設計についての説明が記述されていますが、その設計に至った経緯などについてはほぼ記述されていません。それらは個別の提案とその議論および投票などに散らばっており、その数の多さも相まって追うのはかなり困難を極めます。
この文書は、P2900の設計に至るまでの提案とその議論の流れ、各提案や機能についての議論と投票結果などをまとめた文書です。
文書は"Overview"と"ProposedDesign"の2つのセクションに分かれています。
"Overview"では、Contractsのモチベーションや目標、契約プログラミングそのものについて、およびC++20以前からP2900(C++26 Contracts)に至るまでの提案と議論の流れがまとめられています。
"ProposedDesign"では、P2900の各セクション/サブセクションごとにその動機の要約や設計根拠の説明、設計上の決定の履歴、および関連する提案文書へのリンクがまとめられています。
また、この文書はP2900の今後の更新に追随して、その変更に関する同様のまとめを追記していく予定です。
かなり重厚な文書ですが、読み通すことでC++26のContractsの設計について理解することができるでしょう。
P2900R13 Contracts for C++
以前の記事を参照
- P2900R1 Contracts for C++ - WG21月次提案文書を眺める(2023年10月)
- P2900R3 Contracts for C++ - WG21月次提案文書を眺める(2023年12月)
- P2900R4 Contracts for C++ - WG21月次提案文書を眺める(2024年01月)
- P2900R5 Contracts for C++ - WG21月次提案文書を眺める(2024年02月)
- P2900R6 Contracts for C++ - WG21月次提案文書を眺める(2024年04月)
- P2900R7 Contracts for C++ - WG21月次提案文書を眺める(2024年05月)
- P2900R8 Contracts for C++ - WG21月次提案文書を眺める(2024年08月)
- P2900R10 Contracts for C++ - WG21月次提案文書を眺める(2024年10月)
- P2900R10 Contracts for C++ - WG21月次提案文書を眺める(2024年10月)
- P2900R12 Contracts for C++ - WG21月次提案文書を眺める(2024年12月)
このリビジョンでの変更は
pre
やpost
はmain
や違反ハンドラなどの特別な関数にも適用できることを明確化- 文言の見直し
などです。
P2933R3 Extend header function with overloads for std::simd
<bit>
にあるビット演算を行う関数について、std::simd
向けのオーバーロードを追加する提案。
以前の記事を参照
- P2933R0
std::simd
overloads for<bit>
header - WG21月次提案文書を眺める(2023年08月) - P2933R1
std::simd
overloads for<bit>
header - WG21月次提案文書を眺める(2023年12月) - P2933R2
std::simd
overloads for<bit>
header - WG21月次提案文書を眺める(2024年10月)
このリビジョンでの変更は
- 概要に不足していた文言を追加
- 詳細な動作説明をP1928の最近の更新スタイルと文言に合わせて変更
- 要素が整数・符号なしなどであるSIMD型を検出するコンセプトを追加
rotl/rotr
にオーバーロードを追加し、第二引数にスカラを指定できるようにした
などです。
P2952R2 auto& operator=(X&&) = default
default
な特殊メンバ関数の戻り値型をauto&
で宣言できるようにする提案。
以前の記事を参照
- P2952R0
auto& operator=(X&&) = default
- WG21月次提案文書を眺める(2023年08月) - P2952R1
auto& operator=(X&&) = default
- WG21月次提案文書を眺める(2023年12月)
このリビジョンでの変更は、drafting noteの誤りを修正したことなどです。
P2953R1 Forbid defaulting operator=(X&&) &&
右辺値修飾されたdefault
代入演算子を禁止する提案。
以前の記事を参照
このリビジョンでの変更は、EWGにおける投票結果を追加したことです。
P2971R3 Implication for C++
以前の記事を参照
- P2971R0 Implication for C++ - WG21月次提案文書を眺める(2023年09月)
- P2971R0 Implication for C++ - WG21月次提案文書を眺める(2024年05月)
このリビジョンでの変更は
- 全体を通した編集上の修正
- トークンの選択についてのサブセクションを追加
- N4993へリベース
などです。
P2988R9 std::optional<T&>
std::optional
が参照を保持することができるようにする提案。
以前の記事を参照
- P2988R0
std::optional<T&>
- WG21月次提案文書を眺める(2023年10月) - P2988R1
std::optional<T&>
- WG21月次提案文書を眺める(2024年01月) - P2988R3
std::optional<T&>
- WG21月次提案文書を眺める(2024年02月) - P2988R4
std::optional<T&>
- WG21月次提案文書を眺める(2024年04月) - P2988R5
std::optional<T&>
- WG21月次提案文書を眺める(2024年05月) - P2988R6
std::optional<T&>
- WG21月次提案文書を眺める(2024年08月) - P2988R7
std::optional<T&>
- WG21月次提案文書を眺める(2024年09月) - P2988R8
std::optional<T&>
- WG21月次提案文書を眺める(2024年10月)
このリビジョンでの変更は、optional<U&>
からoptional<T>
へのムーブ代入時に参照先のU&
をムーブしてしまうバグを修正したことなどです。
P2996R9 Reflection for C++26
値ベースの静的リフレクションの提案。
以前の記事を参照
- P2996R0 Reflection for C++26 - WG21月次提案文書を眺める(2023年10月)
- P2996R1 Reflection for C++26 - WG21月次提案文書を眺める(2023年12月)
- P2996R2 Reflection for C++26 - WG21月次提案文書を眺める(2024年02月)
- P2996R3 Reflection for C++26 - WG21月次提案文書を眺める(2024年05月)
- P2996R4 Reflection for C++26 - WG21月次提案文書を眺める(2024年07月)
- P2996R5 Reflection for C++26 - WG21月次提案文書を眺める(2024年08月)
- P2996R7 Reflection for C++26 - WG21月次提案文書を眺める(2024年10月)
- P2996R8 Reflection for C++26 - WG21月次提案文書を眺める(2024年12月)
このリビジョンでの変更は
value_of()/extract()
がconsteval
関数内のローカル変数のリフレクションで使用可能であることを保証define_aggregate()
は現在定義中のクラスには使用できない事を明示的に指定members_of(closure-type)
は未規定のリフレクションのシーケンスを返す- 定数評価中の評価に強い順序付けを導入
static_assert
宣言のアサーションは単純な定数評価式ではない- コアとなる文言の変更
- 宣言の注入を 副作用として分類
- 関連するIFNDR条件を[expr.const]から削除
- [lex.phases]の修正
- R7で導入された“semantically follows”関係をリファクタリングし、単純な定数評価式が副作用と到達可能性によって評価される条件を定義する
- “semantically sequenced”関係を、注入された宣言をill-formedにする[expr.const]の条件にインライン化する
- 宣言の注入を 副作用として分類
- 注入された宣言のルールを表す[expr.const]の条件の例を改善
- ライブラリの文言変更
- 型特性の文言を若干改善
type_of, alignment_of, bit_size_of, data_member_spec, define_aggregate
consteval
関数内のローカル変数にvalue_of()
を適用できないバグを修正substitute
の注記において、プレースホルダ型を推論するために必要な場合はインスタンス化がトリガーされる可能性があることを明確化- クロージャ型のうち、どのメンバがmembers-of-reachableであるかを明確化
- 完全クラスコンテキストにおけるmembers-of-reachableの文言バグを修正し、members-of-precedesに名称変更
define_aggregate()
から冪等性を削除
- 型特性の文言を若干改善
- R8の改訂履歴を充実
などです。
P3019R12 Vocabulary Types for Composite Class Design
動的メモリ領域に構築されたオブジェクトを扱うためのクラス型の提案。
以前の記事を参照
- P3019R0 Vocabulary Types for Composite Class Design - WG21月次提案文書を眺める(2023年10月)
- P3019R3 Vocabulary Types for Composite Class Design - WG21月次提案文書を眺める(2023年12月)
- P3019R6 Vocabulary Types for Composite Class Design - WG21月次提案文書を眺める(2024年02月)
- P3019R8 Vocabulary Types for Composite Class Design - WG21月次提案文書を眺める(2024年04月)
- P3019R9 Vocabulary Types for Composite Class Design - WG21月次提案文書を眺める(2024年09月)
- P3019R10 Vocabulary Types for Composite Class Design - WG21月次提案文書を眺める(2024年10月)
- P3019R11 Vocabulary Types for Composite Class Design - WG21月次提案文書を眺める(2024年12月)
このリビジョンでの変更は
indirect
の概要を修正、デフォルトコンストラクタにexplicit
を追加indirect
とpolymorphic
の仕様において、“may only be X”を“may be X only”に置き換えるT
が不完全型になる可能性がある場合のT
に対するconstraintsをmandatesにする- type_traitsによって暗黙的に要求される完全型要求を削除
indirect
におけるT
はコピーコンストラクタに対してのみcopy-constructibleである必要がある- 制約と不完全型のサポートに関する説明を追加
<=>
の仕様を修正し、synth-three-way-result
を使用するindirect
のoperator==
のconstraintsをmandatesにするindirect
のoperator<=>
のconstraintsを削除- 制約と比較に関する設計変更を反映するために、non-technical specificationセクションを更新
などです。
P3045R5 Quantities and units library
物理量と単位を扱うライブラリ機能の提案。
以前の記事を参照
- P3045R0 Quantities and units library - WG21月次提案文書を眺める(2024年02月)
- P3045R1 Quantities and units library - WG21月次提案文書を眺める(2024年05月)
- P3045R3 Quantities and units library - WG21月次提案文書を眺める(2024年10月)
- P3045R4 Quantities and units library - WG21月次提案文書を眺める(2024年12月)
このリビジョンでの変更は
- ハードウェア電圧測定値の読み出しの例の説明を修正
text_encoding
をcharacter_set
にリネームmp_units
名前空間をstd
に置き換えabsolute
ヘルパをpoint
にリネーム- 式テンプレートをsymbolic expressionsにリネーム
- 使用例を追加
- “Minimal Viable Product (MVP) scope”を"Core Library Framework scope"に変更
half_high_dot
にスペースを出力する機能を追加QuantitySpecOf
とUnitOf
のコンセプトを簡素化QuantityOf
とQuantityPointOf
コンセプトをReferenceOf
で制約- 数量サブカテゴリを超える変換が"Nested quantity kinds"セクションに追加
- P2830R7を"Simplifying the resulting symbolic expressions"セクションで紹介
- テキスト出力時には、スケール付き単位は
[...]
ではなく(...)
で囲まれるようになった - 単位のフォーマットのセクションから無効な[ISO/IEC 80000]引用符を削除
- "Symbols of common units"セクションに明示的な変換の例を追加
symbol_text
にUTF-8プリントルールを追加unit-symbol-solidus
代替文法を追加std-format-spec
の章に拡張機能を追加- テキスト出力に関する未解決の質問を追加
などです。
P3070R2 Formatting enums
列挙型の値を簡単にstd::format
にアダプトさせるための、format_as
の提案。
以前の記事を参照
- P3070R0 Formatting enums - WG21月次提案文書を眺める(2023年12月)
- P3070R1 Formatting enums - WG21月次提案文書を眺める(2024年10月)
このリビジョンでの変更は
- Wording, Alternatives Considered, Acknowledgementsのセクションを追加
std::byte
フォーマットついては別の提案で議論することになった
などです。
P3081R1 Core safety profiles for C++26
P3038で提案されている安全性プロファイルの、より具体的な意味論についての提案。
以前の記事を参照
このリビジョンでの変更は
- SG23/EWGのフィードバックを適用
- initialization safety profile を type safety profileに統合
- 議論の的になっていた部分を延期
static_cast
をdynamic_cast
で処理したり、dynamic_cast
のパフォーマンス保証など
- 様々な改善を適用
std::byte
へのreinterpret_cast
やポインタからuintptr_t
へのキャストの許可など
- SG23の推奨に従って、非ローカルな生存期間解析をP3465R1へ分割
- 文言の追加
などです。
P3086R3 Proxy: A Pointer-Semantics-Based Polymorphism Library
静的な多態的プログラミングのためのユーティリティ、"Proxy"の提案。
以前の記事を参照
- P3086R0 Proxy: A Pointer-Semantics-Based Polymorphism Library - WG21月次提案文書を眺める(2024年02月)
- P3086R2 Proxy: A Pointer-Semantics-Based Polymorphism Library - WG21月次提案文書を眺める(2024年04月)
このリビジョンでの変更は
- モチベーションを更新
- 9つの名前付き要件を追加
proxy_indirect_accessor
クラステンプレートを追加proxy::invoke(), proxy::reflect()
をフリー関数に変更proxy
にアクセシビリティサポートを追加proxy::operator bool(), proxy::operator->(), proxy::operator*()
を追加std::swap(proxy, proxy)
の定義をフレンド関数に変更
などです。
P3094R6 std::basic_fixed_string
NTTPとして使用可能なコンパイル時文字列型であるstd::basic_fixed_string
の提案。
以前の記事を参照
- P3094R0
std::basic_fixed_string
- WG21月次提案文書を眺める(2024年02月) - P3094R1
std::basic_fixed_string
- WG21月次提案文書を眺める(2024年04月) - P3094R2
std::basic_fixed_string
- WG21月次提案文書を眺める(2024年05月) - P3094R3
std::basic_fixed_string
- WG21月次提案文書を眺める(2024年07月) - P3094R4
std::basic_fixed_string
- WG21月次提案文書を眺める(2024年10月)
このリビジョンでの変更は
convertible_to<charT>
をsame_as<charT>
に変更- structural typesについては、P2484R0をP3380R1に置換
- 関連提案を更新
std::string_literal
の章を追加- 文字列リテラルからのコンストラクタがP3491R0で拡張
inplace_string
の章に投票結果を追加- Compiler Explorerのリンクを更新
などです。
P3111R3 Atomic Reduction Operations
std::atomic
にアトミックリダクション操作を追加する提案。
以前の記事を参照
- P3096R0 Atomic Reduction Operations - WG21月次提案文書を眺める(2024年09月)
- P3096R2 Atomic Reduction Operations - WG21月次提案文書を眺める(2024年12月)
このリビジョンでの変更は
- atomic reduction sequenceの置換を表にリファクタリング
- 整数リダクション操作の場合と同じ有効な置換を提供するために、"as if"整数演算を利用したアトミックリダクションシーケンスの置換に代替の表現を提供する
などです。
P3125R2 constexpr pointer tagging
タグ付きポインタをサポートするためのライブラリ機能の提案。
以前の記事を参照
- P3125R0 Pointer tagging - WG21月次提案文書を眺める(2024年05月)
- P3125R1 Pointer tagging - WG21月次提案文書を眺める(2024年10月)
- P3125R1 Pointer tagging - WG21月次提案文書を眺める(2024年12月)
このリビジョンでの変更は、設計の改善と文言の追加などです。
P3139R1 Pointer cast for unique_ptr
std::const_pointer_cast
とstd::dynamic_pointer_cast
にstd::unique_ptr
のオーバーロードを追加する提案。
以前の記事を参照
このリビジョンでの変更は、仮想デストラクタの代わりにdestroying deleteを許可したことです。
P3148R1 Formatting of chrono Time Values
chrono
の値のフォーマットに関する問題を解決する提案。
以前の記事を参照
このリビジョンでの変更は
- ゼロパディング問題(12時間表示の細部)に対する異なるアプローチを提案
- 精度指定子の位置と動作に関する議論に対する改善
などです。
このリビジョンでの提案内容はそれぞれ次のようになります、
時間単位([h])の表示オプション
add? | 指定子 | 意味 | 29h + 15min |
3h |
16h |
---|---|---|---|---|---|
%H |
時間、0埋め | 29 |
03 |
16 |
|
+ | %K |
24時間時計の時間、0埋め | 05 |
03 |
16 |
+ | %k |
時間、端数 | 29.25 |
3 |
16 |
%I |
12時間時計の時間、0埋め | 05 |
03 |
04 |
|
+ | %i |
12時間時計の時間 | 5 |
3 |
4 |
(第一列の + は提案しているものであることを表します)
分単位([min])の表示オプション
add? | 指定子 | 意味 | 15min + 45s |
3min |
---|---|---|---|---|
%M |
60進数の分、0埋め | 15 |
03 |
|
+ | %f |
分、端数 | 15.75 |
3 |
秒単位([sec])の表示オプション
add? | 指定子 | 意味 | 90s + 500ms |
3s + 500ms |
---|---|---|---|---|
%S |
60進数の秒、0埋め | 30.500 |
03.500 |
|
+ | %s |
秒、端数 | 90.500 |
3.500 |
複合表示オプション
add? | 指定子 | 同等表現 | 意味 | 15h+25min+45s |
---|---|---|---|---|
%T |
%H:%M:%S |
24時間時計表示、秒の端数まで | 15:25:45 |
|
+ | %N |
%i:%M:%S |
12時間時計表示、秒の端数まで | 3:25:45 |
%R |
%H:%M |
24時間時計表示、秒以下なし | 15:25 |
|
+ | %P |
%i:%M |
12時間時計表示、秒以下なし | 3:25 |
%r |
:%I:%M:%S %p |
%N +0埋め+AM/PM |
03:25:45 PM |
|
+ | %l |
:%i:%M:%S %p |
%N +AM/PM |
3:25:45 PM |
精度の指定に関してはP2945に委譲されているようです。
P3164R3 Early Diagnostics for Sender Expressions
提案中のExecutorライブラリにおいて、sender
チェーンのエラーを早期に報告するようにする提案。
以前の記事を参照
- P3164R0 Improving diagnostics for sender expressions - WG21月次提案文書を眺める(2024年04月)
- P3164R2 Improving diagnostics for sender expressions - WG21月次提案文書を眺める(2024年07月)
このリビジョンでの変更は
- 最新のWDにリベース
transform_completion_signatures
の再指定を削除して、型エラーを伝播する- 型エラーについてはP3557で対処される
stopped_as_optional
を調整して、その子sender
がsingle-sender
コンセプトを満たすことを必須とし、single-sender
コンセプトを変更してnon-dependentsender
でも適切に動作するようにする- [exec.snd.general]に
sender
アルゴリズムのユーザー定義カスタマイズによって、デフォルト実装ではnon-dependentsender
が生成されることを保証する要件を追加 - non-dependent
sender
の作成をサポートするために、説明専用のbasic-sender
ヘルパを追加 - 説明専用の
sender-of
コンセプトを更新し、non-dependentsender
も処理できるようにする- つまり、
sender-of<Sndr, int>
はsender_in<Sndr>
を包摂する
- つまり、
run_loop
のscheduler
に対してschedule
を呼び出した際に返されるsender
がnon-dependentであることを指定する
などです。
P3176R1 The Oxford variadic comma
前にカンマが無い省略仮引数引数(...
)の使用を非推奨にする提案。
以前の記事を参照
このリビジョンでの変更は
- N4981へリベース
- "Editorial changes"と"Normative changes"のサブセクションを"§ 7 Proposed wording"へインライン化
- parameter-declaration-clauseの拡張案の順序を変更
- 新しい段落を追加せず、既存の段落を更新する
などです。
この提案は2024年11月の全体会議で承認され、C++26に採択されています。
P3179R5 C++ parallel range algorithms
RangeアルゴリズムをExecutionPolicy
に対応させる提案。
以前の記事を参照
- P3179R0 C+ parallel range algorithms - WG21月次提案文書を眺める(2024年04月)
- P3179R1 C+ parallel range algorithms - WG21月次提案文書を眺める(2024年05月)
- P3179R2 C++ parallel range algorithms - WG21月次提案文書を眺める(2024年07月)
- P3179R2 C++ parallel range algorithms - WG21月次提案文書を眺める(2024年10月)
- P3179R4 C++ parallel range algorithms - WG21月次提案文書を眺める(2024年12月)
このリビジョンでの変更は
- [algorithm.syn]に必要な文言を追加
- 変更がない場合でも、その文脈のアルゴリズムの説明を提案文言に含めるようにする
random-access-sized-range
コンセプトを説明専用にする- 既存の機能テストマクロをバンプする
ExecutionPolicy
を短縮し、文言内のexecution-policy
コンセプトを斜体で表示する- 出力として範囲を使用するアルゴリズムについては、
result_last
(または類似名)を使用する- ただし、"Issues to address"/"in Out-of-scope"に記載がない限り
fill/generate
アルゴリズムファミリを修正し、設計の一貫性を保つためにoutput_iterator/output_range
の代わりにindirectly_writable
を使用するcopy_n
アルゴリズムファミリにsentinel-for-output
を追加- "design overview"に説明専用コンセプトの簡単な説明を追加
- 設計上の問題のため、
rotate_copy
をスコープ外にする
などです。
P3206R0 A sender query for completion behaviour
sender
の完了動作についてのクエリ方法の提案。
P2300以前、P2257R0ではsender
に対するプロパティの指定とそのクエリに関する一般化された方法が提案されていました。P2300への移行の際にsender
に対するプロパティ指定が削除されたことでそのクエリも同時に削除されていました。sender
に対するプロパティとは例えば、処理の優先度やブロッキングの有無などです。
この提案は、そのようなsender
のプロパティの一つである完了に関する動作についてのクエリ方法を提案するものです。
完了に関する動作とは、sender
をexecution::start()
したときにそのsender
で表現されている非同期操作がどのように完了するかということです。そのような動作には
- インライン:
start
としたのと同じスレッドで処理が完了する - 同期:
start
がリターンする前に処理が完了する - 非同期: 処理の完了は
start
のリターン以降かつ、それとは事なるスレッドで発生する
の3つがあります。
この3つの状態のいずれで完了するのかを静的に知ることができれば、receiver
との接続後のoperation_state
の状態などを最適化できる可能性があります。例えば、インライン/同期のいずれかで完了することが分かっているのであれば、start
の呼び出しに同期はいらず、operation_state
の生存期間管理のための追加の作業が不要になります。
コルーチンのawaitable
型はawait_ready()
が返すbool
値によってこの情報を伝達するチャネルを持っていますが、std::execution
にはそれがありません。この提案は、sender
に対してそのようなクエリAPIを追加しようとするものです。
提案するAPIは、execution::get_completion_behaviour(sndr,env)
です。これはsender
(sndr
)と接続するreceiver
の環境env
によるカスタマイゼーションポイントであり、次のいずれかの値を返すものです
completion_behaviour::inline_completion
: インライン完了- 接続された
receiver
の完了シグナルは、execution::start
呼び出しのリターン前に呼び出しスレッドで発生する
- 接続された
completion_behaviour::synchronous
: 同期完了- 接続された
receiver
の完了シグナルの発生とexecution::start
呼び出しのリターンの間には、happens-before関係が成立する
- 接続された
completion_behaviour::asynchronous
: 非同期完了- 接続された
receiver
の完了シグナルの発生は、execution::start
呼び出しのリターン前に呼び出し元スレッドで発生することは無い
- 接続された
completion_behaviour::unknown
: 不明- 完了の動作は不明
このカスタマイズポイントとしては、sender
型のメンバ関数としてenv
を受け取る形で実装するか、型エイリアスとして定義するかの2つを提案しています。
// メンバ関数として実装 template<class InputSender> struct example_sender_adaptor { // [...] template <class Self,class...Env> requires (sizeof...(Env) <=1) constexpr auto get_completion_behaviour(this Self&& self,Env&&...env) noexcept { return execution::get_completion_behaviour(std::forward_like<Self>(self.sender), std::forward<Env>(env)...); } InputSender sender; }; // 型エイリアスとして実装(環境に関係なくsender型のみで完了動作が決まる場合 struct example_sender1 { // [...] using completion_behaviour = constant<execution::completion_behaviour::asynchronous>; };
sender
に対して完了動作をクエリ可能(特に静的にクエリ可能)になることによって、例えばsync_wait()
では同期プリミティブが不要になり、repeat
の様なアルゴリズム(sender
の処理を繰り返すアルゴリズム)では入力sender
の完了(set_value
)後にそのsender
の操作を呼び出すようなsender
を構成してそれを実行する必要がありますが、インライン/同期完了であることが分かっていれば、単なるwhile
ループによって実行可能になります。
さらに、sender
をコルーチンで扱うためにawaitable
に変換するas_awaitable
においてはこれまでsender
の完了動作について知る方法が無かったため、ネストしたコルーチン(sender
)に対してsymmetric transferを適用することができませんでしたが、このクエリがあればそれを可能にする機会が得られるようになります。
また、提案では既存のsender
アルゴリズムに対してこのクエリを実装した場合にどのような値を返すべきかの検討もしています。
sender
ファクトリschedule(run_loop::scheduler)
asynchronous
just(), just_error(), just_stopped()
inline_completion
read_env()
inline_completion
sender
アダプタfinally(sender1, sender2), continues_on(sender, scheduler), starts_on(scheduler, sender)
finally(sender1, sender2)
に対して、min(get_completion_behaviour(sender1, env), get_completion_behaviour(sender2, env))
then(sender, fn), upon_error(sender, fn), upon_stopped(sender, fn)
get_completion_behaviour(sender, env)
let_value(sender, fn), let_error(sender, fn), let_stopped(sender, fn)
min(get_completion_behaviour(sender, env), get_completion_behaviour(rs, env)...)
rs...
はfn
が返しうる結果sender
into_variant(sender), stopped_as_optional(sender)
get_completion_behaviour(sender, env)
bulk(sender, shape, fn)
get_completion_behaviour(sender, env)
when_all(senders...)
min(get_completion_behaviour(senders, env)...)
split(sender)
- 動的な値を返す
split(sender)
の呼び出しがsplit
の新しいコピーを返す前に入力操作が完了していれば、インライン完了- それ以外の場合、
get_completion_behaviour(sender, env)
- 動的な値を返す
ここでは、unknown < asynchronous < synchronous < always_inline
の順に順序付けしたうえで、min
はこの順序の上で小さい方を返すものです。
P3229R0 Making erroneous behaviour compatible with Contracts
Erroneous Behavior(EB)をContracts都のフレームワークに取り込む提案。
この提案は、P3100R1で提案されているUB/EBをContractsの下で統一的に扱うという構想の第一歩となるもので、まずEBをContractsの枠組みに取り込もうとするものです。
P3100R1については以前の記事を参照
EBと契約はどちらも、プログラムの実行中に欠陥が検出されたもののその時点での動作はwell-definedである(UBではない)という、というプログラムの欠陥への対処として同じ方向性の概念を示しています。しかし、C++26においてこれらは別々のツールとして機能的な関連性をほとんど持たない状態で併存しています。
EBが発生した場合に実装取ることのできる動作オプションと契約の評価セマンティクスの間には一定の互換性があります
- 診断を発行して終了する: デフォルトの違反ハンドラの下でenforceセマンティクスを使用することと同型
- 診断を発行せず終了する: quick-enforceセマンティクスを使用することと同型
- 診断を発行して継続する: observeセマンティクスを使用することと同型
- 誤った値をチェックせず継続する: ignoreセマンティクスを使用することと同等
違反ハンドラ周りの仕様を無視すれば、誤った値(erroneous values)を生成する式E
の評価は次のような関数の呼び出しと同等になります
auto eval_E() post (r: !is_erroneous(r)) { return E; }
これらの観点から、この2つのアプローチはかなり類似したものであり、ほとんどその用語が違うだけであることが分かります
- Erroneous behaviour: well-definedではあるものの誤っている動作を検出し、その動作が発生する条件を明記する
- Contract assertions: 明示的にその動作が誤りであるという条件を指定し、違反が発生した場合に生じる動作(EBの場合もあればUBの場合もある)の実装をユーザーに委ねる
これらの比較から、契約違反後(違反ハンドラ呼び出し後)のwell-definedな動作とはErroneous behaviourそのものであり、ある操作についてEBを指定する条件とは、その操作についての契約アサーションに他ならないことが分かります。唯一異なる点は、EBは契約違反後のwell-definedな動作であるのに対して、契約違反後に継続する場合はUBになる場合もある(ignore/observeセマンティクスの場合)という点です。
ただし、どうしても両機能が異なる点がいくつかあります
- 違反ハンドラ
- 診断メッセージ出力はどちらにおいても推奨される動作だが、契約の場合は違反ハンドラの差し替えによってユーザーがこの挙動をオーバーライドできるのに対して、EBでは提供されていない
- 終了方法の規定
- 契約注釈が終了するセマンティクスで評価された場合、適合する3つの終了モードが指定されているが、EBでは指定されていない
- 終了モードとは、
std::terminate()
、std::abort()
、実装定義の方法(トラップ命令など)、の3つ
- 終了モードとは、
- 契約注釈が終了するセマンティクスで評価された場合、適合する3つの終了モードが指定されているが、EBでは指定されていない
- その後の状態
- 契約違反によってプログラムが終了する場合、契約アサーションの評価の一部として違反が処理された直後に終了する
- EBでは、その操作の後の未規定の時点、とされる
- EBの後、プログラムは事実上のエラー状態となり、その後いつでも突然終了する可能性がある
- これをDamocles semanticと呼ぶらしい
- 定数評価とSFINAE
この提案は、EBをContractsの枠組みに取り込むために、これらの矛盾をC++26のうちに解消しておこうとするものです。
提案している変更はつぎの5点です
- 暗黙の契約アサーション(Implicit contract assertions)の導入
- 違反ハンドラAPIの拡張
- Damocles semantic を “sticky” erroneous values に置き換える
noexcept
との相互作用の再定義- 定数評価の変更
1の変更のみによって、先に挙げたEBと契約の間の異なる点は解消されます。
この提案はC++26への導入を目指していますが、EWGでのレビューにおいて拒否されているため、一旦追及は停止されています。P3100R1は引き続き検討中なのでそちらで継続するものと思われます
P3289R1 Consteval blocks
宣言のコンテキストで任意の定数式を実行するためのブロックの提案。
以前の記事を参照
このリビジョンでの変更は、consteval
ブロックをstatic_assert
と区別するために文言を更新したことです。
この提案はP2996R10にマージされ、リフレクション機能の一部として標準に導入される予定です。
P3347R1 Invalid/Prospective Pointer Operations
無効化されたポインタに対する一部の演算を明示的に許可する提案。
以前の記事を参照
このリビジョンでの変更は
- LEWGIレビューへの対応
- 概要を簡潔にし、詳細を“Background”セクションへ移動
- N4933のWDへの対応として、“representation bytes”を“value representation”へ変更
- 参考文献の更新
などです。
P3348R2 C++26 should refer to C23 not C17
Cへの参照をC17からC23へ更新する提案。
以前の記事を参照
- P3348R0 C++26 should refer to C23 not C17 - WG21月次提案文書を眺める(2024年08月)
- P3348R1 C++26 should refer to C23 not C17 - WG21月次提案文書を眺める(2024年10月)
このリビジョンでの変更は
- 浮動小数点環境に関する関連提案P3479R0とP2746R6へのリンクを追加
- SG6のレビューにおいて、浮動小数点数に関する部分を別の提案へ移すことが決定
<cstdlib>, <cstring>, <ctime>
へ不足していた宣言を追加し、TODOメモに対応std::time_put
の脚注の削除(C99で削除されていたが残っていた)は編集上の処理によって対応された(のでここでの対応は不要になった- N5001へリベース
などです。
P3351R2 views::scan
状態を持つ関数を使用可能なviews::transform
であるRangeアダプタ、views::scan
の提案。
以前の記事を参照
このリビジョンでの変更は
- SG9レビューに基づいて、全体の再設計
- 投票結果セクションとレビュー結果セクションをいくつか追加
- 参照型に関する軽微な修正と議論を追加
- P1729R5との命名競合に関する議論を追加
- いくつかの文言の修正
- P2846R5の採択を見越して、
reserve_hint
メンバを追加 - N5001へのリベース
などです。
このリビジョンでのサンプルコード
import std; int main() { std::vector vec = {1, 2, 3, 4, 5}; std::println("{}", vec | views::scan(std::plus{})); // [1, 3, 6, 10, 15] std::println("{}", vec | views::partial_sum); // [1, 3, 6, 10, 15] std::println("{}", vec | views::scan(std::plus{}, 10)); // [11, 13, 16, 20, 25] std::println("{}", vec | views::partial_sum(10)); // [11, 13, 16, 20, 25] }
P3367R3 constexpr coroutines
コンパイル時でもコルーチンを動作可能にする提案。
以前の記事を参照
- P3367R0 constexpr coroutines - WG21月次提案文書を眺める(2024年10月)
- P3367R2 constexpr coroutines - WG21月次提案文書を眺める(2024年12月)
このリビジョンでの変更は
- モチベーションの文章を追加
- AST変換アプローチの例を追加
などです。
P3373R1 Of Operation States and Their Lifetimes
P2300のstd::execution
において、構成された非同期アルゴリズムの内部状態の生存期間を特定の場合に限り短くする提案。
以前の記事を参照
このリビジョンでの変更は、具体的な行動方針を提案したことです。
この提案では次の2種類のsender
アルゴリズムに対して提案する変更(ネストした場合のoperation_state
生存期間の短縮)を行うことを提案しています
let_value, let_error, let_stopped
- ただし次のタイミングで、先行する
operation_state
の生存期間を終了する- これらのアルゴリズムによって送信された値が永続化された後
- 後続操作のための
sender
を取得するために提供された呼び出し可能オブジェクトを呼び出す前
- ただし次のタイミングで、先行する
split
- 次のタイミングでサブ
operation_state
の生存期間を終了するsplit
によって送信された値が永続化された後split
のsender
に接続して開始することで開始された操作が、その値で完了する前
- 次のタイミングでサブ
ただし、split
に関してはそれそのものがどういう形でC++26で出荷されるか不安定という事情があります。
P3374R1 Adding formatter for fpos
fpos<mbstate_t>
のフォーマッタを追加する提案。
以前の記事を参照
このリビジョンでの変更は
- 状態を、回復可能な記述子ではなく
bool
でフォーマットする - いくつかの文言の軽微な問題の修正
などです。
P3375R2 Reproducible floating-point results
計算結果の再現性の保証された浮動小数点数型の提案。
以前の記事を参照
- P3375R0 Reproducible floating-point results - WG21月次提案文書を眺める(2024年09月)
- P3375R1 Reproducible floating-point results - WG21月次提案文書を眺める(2024年10月)
このリビジョンでの変更は
- 貢献者リストを更新
- status quoセクションを更新し、コンパイル時浮動小数点環境とリテラルの解析に関する議論を含めた
- 関連する他の提案についての議論を追加
- ストレージと属性から構成されるコア型に焦点を当てた、新たな調査の方向性に関するセクションを追加
- ライブラリ型の提案と文言を撤回
- コア型の提案を拡張
- 未解決の質問を更新
などです。
P3385R3 Attributes reflection
リフレクションにおいて、エンティティに指定されている属性の情報を取得・付加できるようにする提案。
以前の記事を参照
- P3385R0 Attributes reflection - WG21月次提案文書を眺める(2024年09月)
- P3385R1 Attributes reflection - WG21月次提案文書を眺める(2024年10月)
- P3385R2 Attributes reflection - WG21月次提案文書を眺める(2024年12月)
このリビジョンでの変更は
- 属性のスコープについて解決
- 属性の引数について解決
などです。
属性のリフレクションにおいては、非標準の属性の扱いが問題になります。特に、属性の無視可能性のルール(これは長年の議論の的であり、問題になっている)に抵触してしまうと提案が頓挫する可能性があります。そのためこの提案では、非標準属性を考慮に入れつつも属性の無視可能性について新しい規定を追加しないようにするために、次のself-consistencyルールを指定しています
- 無視された属性へのリフレクションは
^^[[]]
と区別がつかない - 宣言に属性をスプライシングすることは、それらの属性を手動で付加した場合と区別がつかない
このルールは、標準属性については規範的(なるべくリフレクション動作を規定する)であり、非標準属性については寛容的(使おうと思えば使える)であろうとするものです。
この提案の初期のリビジョンでは属性の引数節を意図的に無視していましたが、そのサポートの必要性が全会一致で示されました。その際問題となるのは標準属性では任意の引数を取れる[[assume(expr)]]
のみであり、これは空文に対する属性として使用されるものであり取得するには明示的に直接クエリするしかないため、考慮しないことにしました。
非標準属性では任意の引数を取ることができますが、現状任意の引数シーケンスをサポートする標準的な方法が存在していません。おそらくtoken sequence(P3294)のような表現にするしかないと思われますが、その議論はまだ始まったばかりです。
そのためこの提案では、標準属性をカバーするのに十分な算術型または文字列リテラル引数のサポートのみを必須とすることで現実的なユースケースをカバーする方向で提案しています(その他の引数のサポートは実装定義)。
P3388R1 When Do You Know connect Doesn't Throw?
execution::conect
による操作が例外を送出するかどうかを早期に判定できるようにする提案。
以前の記事を参照
このリビジョンでの変更は
- 具体的な提案を追加
- 代替案リストを削除
- 文言の追加
などです。
このリビジョンでは、以前に提示していた2つのオプションのうち、「receiver
コンセプトの一部として、receiver
が例外を投げずにムーブ可能であることを要求する」方向性を提案として採用しています。追加の意味論要件として「あるsender
型のインスタンスをあるreceiver
型のインスタンスに接続しても例外を発生しない場合、そのsender
型のインスタンスを任意のreceiver
型のインスタンスに接続する際も、同じ関連環境型であれば例外は発生しない」を追加しています。
P3394R1 Annotations for Reflection
C++任意のエンティティ(宣言)に対して静的リフレクションのためのアノテーションを付加できるようにする提案。
以前の記事を参照
このリビジョンでの変更は、文言を追加したことです。
P3395R0 Formatting of std::error_code
std::error_code
をフォーマット可能にする提案。
std::error_code
はostreamへの出力が可能です。
std::error_code ec; auto size = std::filesystem::file_size("nonexistent", ec); std::cout << ec;
generic:2
しかし、std::print
では出力できません。
しかし、ioマニピュレータが適用されるのはカテゴリ名の方だけであるなど、おかしな動作をする部分があります。
std::cout << std::left << std::setw(12) << ec;
generic :2
この提案は、std::error_code
をフォーマット可能(print
可能)にするとともに、より使いやすいフォーマット指定を可能にしようとするものです。
基本のフォーマットは<<
と同じです
std::print("{}\n", ec);
generic:2
しかし、フォーマットオプションの指定は文字列全体にかかります
std::print("[{:>12}]\n", ec);
generic:2
そして、エラーメッセージを出力可能するオプションを用意しています
std::print("{:s}\n", ec);
No such file or directory
ただし、エラーメッセージの実際の文字列は実装定義です。
このエラーメッセージ出力の最大の問題は、標準ライブラリ実装とそれが動作するプラットフォームによってメッセージのエンコーディングがまちまちである点です(標準の仕様では未規定)。次の表はそれを比較したものです
プラットフォーム\ライブラリ実装 | libstdc++ | libc++ | Microsoft STL |
---|---|---|---|
POSIX | strerror |
strerror |
- |
Windows | strerror /ACP |
strerror |
ordinary literals/ACP |
strerror
はCロケールエンコーディングを表し、ACPはActive Code Pageの略です。
この表からも分かる通り、エンコーディングは異なる場合があり、実際に異なることが多いと思われます。この提案では、Cロケールエンコーディング(実行文字集合)を使用することを提案しています。
なお、この提案の内容は{fmt}ライブラリですでに実装されています。
P3400R0 Specifying Contract Assertion Properties with Labels
契約アサーションに対してラベルを指定する機能の提案。
契約アサーションの実行時の動作はセマンティクスというもので指定されており、セマンティクスはおおむねグローバルに決定されます(規定としては実装定義だが、実体はコンパイラオプションで一括指定する)。しかし、より細かい単位でセマンティクスを制御し、あるいは契約アサーションの特定の振る舞いについてを同様にローカルに制御したいユースケースがあります。
P2900のC++26 Contractsはこのようなユースケースを把握しつつも、最初のMVPとしてそのような機能を提供していませんでした。ただ、以前の提案からの1つの機能の候補として、アサーションにラベルを指定することでアサーションのきめ細かな制御を行う方向性の機能を導入していくことについてはおおむね合意されていました。
この提案は、C++29以降のContracts機能拡張として、契約アサーションにラベルを指定することでアサーションの振る舞いについてユーザーが細かく制御できるようにしようとするものです。
提案の内容は次のものです
- コア言語
- ラベルを指定するための構文
pre<label>(expr)
のように指定する- ラベル(
label
)はassertion-control-expressionという定数式(言語キーワードではない)
- ラベル(
- ラベルオブジェクトの型の定義
- ラベルに使用されるオブジェクトはAssertion-Control Objectsと呼ばれる
assertion_control_object
コンセプトを満たす型のオブジェクトをラベルとして使用できる
- ラベル専用の
using
文の追加- assertion-control-using-directives / assertion-control-using-declarations
- 導入される名前はラベル構文からの探索時にのみ可視になる
contract_assert using namespace std::contracts::labels;
のようになる
- assertion-control-using-directives / assertion-control-using-declarations
- 複合ラベル演算
- Assertion-Combination Operator として
operator|
オーバーロードを提供 - ラベルの合成を可能にする
- 複合されたラベルは combined assertion-control objects と呼ばれる
- Assertion-Combination Operator として
- 違反ハンドラからのラベルの取得
contract_violation::control_object()
から違反が起きたアサーションに指定されているラベルのポインタを取得する- ただし、この関数はラベル型がポリモルフィックである場合にのみ
nullptr
以外を返す
- ただし、この関数はラベル型がポリモルフィックである場合にのみ
- 複合ラベルにおいて特定のラベルが含まれているかを調べるために、
get_constituent_label()
を提供
- 環境ラベル
- ラベルオブジェクトによって制御可能なプロパティ(全てオプトイン
- 許可するラベルの指定方法
allowed_semantics
(静的)メンバ変数によって、許可するセマンティクスのリストを指定する- 型は、
std::contracts::evaluation_semantic
列挙型
- 型は、
- 評価時のセマンティクス選択方法
- 現在のセマンティクス種別を受け取って評価に使用するセマンティクス種別を返す
compute_semantic()
- 現在のセマンティクス種別を受け取って評価に使用するセマンティクス種別を返す
- 呼び出す違反ハンドラを指定する方法
handle_contract_violation()
で特定の違反ハンドラを呼び出すように指定する- 定義しないか
false
を返すことでグローバルの違反ハンドラを呼び出すように指定できる
- 定義しないか
- ラベルの次元指定
- 呼び出し側の契約アサーション(Caller-Facing)と呼び出し先の契約アサーション(Callee-Facing)の制御
- 許可するラベルの指定方法
- ラベルを指定するための構文
- 標準ライブラリ
ラベルはC++のキーワードや何らかの専用言語機能で提供されるのではなく、クラスがのオブジェクトとして、契約アサーションに続く<>
の中に指定できます。
// ラベルの定義 struct my_label_t {}; constexpr my_label_t my_label; void f(int i) pre<my_label>( i > 0 ); // ラベルの指定
このようなラベルオブジェクトはAssertion-Control Objectsと呼ばれ、その型と値によって契約アサーションの動作を細かく制御することができます。ラベルの指定(Assertion-Control Objects)自体は定数式の結果である必要があります。
ラベルは|
によって合成して指定することができます。
struct my_label_2_t {}; constexpr my_label_2_t my_label_2; void g(int i) pre<my_label | my_label_2>(i > 0 ); // 複合ラベルの指定
これらのAssertion-Control Objectsが提供する性質によって、それが指定された契約アサーションの振る舞いを細かく制御します。例えば、契約違反が起きたら継続しないようにするラベルは次のように構成できます
struct enforce_or_quick_enforce_t { static constexpr evaluation_semantic_set allowed_semantics = evaluation_semantic_set(evaluation_semantic::enforce, evaluation_semantic::quick_enforce); }; constexpr enforce_or_quick_enforce = {};
契約違反が起きた場合に呼ばれる違反ハンドラはまずライブラリが指定するものを呼び出すようにするラベルは次のように構成できます。
struct my_library_violation_handler_t { // この関数自体がカスタムの違反ハンドラ std::true_type handle_contract_violation(const std::contracts::contract_violation& violation) { // violationオブジェクトに対して何らかの操作を行う ... return {}; // trueを返すと次の違反ハンドラを呼び出しに行く(複合ラベルではなければグローバルの違反ハンドラが呼ばれる) // falseを返す(あるいはvoid戻り値型)とそこで違反のハンドリングは終了する } }; constexpr my_library_violation_handler = {};
この2つのラベルの性質を両方備えたラベルはラベルを合成することで得られます。
constexpr auto my_lib_assertion = std::contracts::combine_labels(enforce_or_quick_enforce, my_library_violation_handler);
そして、このように作成したラベルは通常のラベル同様に指定して使用することができます。
void f() pre<my_lib_assertion>(true) post<my_lib_assertion>(true) { contract_assert<my_lib_assertion>(true); }
ラベルが指定されている契約アサーションの動作はグローバルの指定の影響を受けず、そのラベルオブジェクトに指定された振る舞いによって評価されます。
P3402R2 A Safety Profile Verifying Initialization
クラスのすべてのサブオブジェクトが初期化されていることを保証するプロファイルの提案。
以前の記事を参照
- P3402R0 A Safety Profile Verifying Class Initialization - WG21月次提案文書を眺める(2024年09月)
- P3402R1 A Safety Profile Verifying Class Initialization - WG21月次提案文書を眺める(2024年10月)
このリビジョンでの変更はあまり明示的ではありませんが、クラス以外の初期化全般を取り扱うようにしたことの様です。
P3407R1 Make idiomatic usage of offsetof
well-defined
C++におけるoffsetof
マクロのCとの非互換を解消するために、C++におけるポインタの制限を緩和する提案。
以前の記事を参照
このリビジョンでの変更は
- P1839R6のレビュー中に発見された文言の改善を適用
- これには、
volatile
オブジェクトのオブジェクト表現へのアクセスの禁止が含まれる
- これには、
- 文書の可読性の向上
- 非standard-layout型と
container_of
の標準化の可能性に関する議論を追加 - ここで議論されている3つの主要な設計案を比較する表を追加
などです。
P3411R1 any_view
view
を型消去するためのview
、any_view
の提案。
以前の記事を参照
このリビジョンでの変更は
- パフォーマンステストの更新
- テンプレートパラメータの再設計
any_view<Foo>
とany_view<const Foo>
は問題なく動作するはず- 4つの異なる代替設計を提示
などです。
P3412R1 String interpolation
std::format
/std::print
向けの引数となるフォーマット文字列と対象引数列の組を生成する、文字列補完リテラルの提案。
以前の記事を参照
このリビジョンでの変更は
basic_formatted_string
構造体を削除- これにより、P3298およびP3398への依存を回避できる
print
オーバーロードを削除し、代わりにプログラマがprint
時にx
リテラルを使用できるようにするf
リテラルはstd::string
/std::wstring
を直接生成する
f
リテラルを更新std::make_formatted_string
またはstd::format
ではなく、__FORMAT__
への関数呼び出しを生成するようにする- これにより、標準ライブラリのフォーマット機能に依存しない使用を可能にする
- 他の名前について検討
などです。
このリビジョンでは、f
リテラルの基本的な使用感は変わりませんが、展開は変わっています
f"Weird, but OK: {1 < 2, 2 > 1}" // Transformed to: __FORMAT__("Weird, but OK: {}", (1 < 2, 2 > 1)) int values[] = {3, 7, 1, 19, 2 }; f"Reversed: {std::set<int, std::greater<>>(values, values + 5)}" // Transformed to: __FORMAT__("Reversed: {}", (std::set<int, std::greater<>>(values, values + 5))) f"{x=}"; // translates to: __FORMAT__("x={}", x);
展開結果の__FORMAT__
はまだ暫定的な名前ですが、この意図は標準ライブラリを使用しないような実装においてもユーザーが対応する関数を定義することによってf
リテラルを使用可能にすることにあります。
標準ライブラリとしては、次のようなstd::format
に転送する実装を提供します
template<typename... Args> std::string __FORMAT__(std::format_string<Args...> lit, Args&&... args) { return std::format(std::move(lit), std::forward<Args>(args)...); } template<typename... Args> std::string __FORMAT__(std::wformat_string<Args...> lit, Args&&... args) { return std::format(std::move(lit), std::forward<Args>(args)...); }
ただし、この目的を達するためには、__FORMAT__
は最終的に他の識別子と衝突しないような十分にユニークな名前にする必要があります。std
の中に入れると名前は単純化できますが、ユーザー定義を許可するために手続きが必要になります。そのほか特殊な関数名(ユーザー定義リテラルlikeなものなど)を使用する方法などが挙げられていますが、どうするのかまだ決定していません。
このリビジョンで追加されたx
リテラルは、展開結果を__FORMAT__
の呼び出しで囲まないものです。これにより、中間のstd::string
生成を省いて直接的にstd::print
に渡すことができます
std::print(x"Weird, but OK: {1 < 2, 2 > 1}"); // Transformed to: std::print("Weird, but OK: {}", (1 < 2, 2 > 1));
P3420R1 Reflection of Templates
テンプレートそのものに対するリフレクションの提案。
以前の記事を参照
このリビジョンでの変更は
- 関数宣言の一部をトークンシーケンスとして返すAPIを、宣言を受け取り変更された宣言を返す関数型APIに置き換え
- 実装の容易性を考慮し、依存識別子と非依存識別子が混在する可能性のあるコードをトークンシーケンスとして返すメタ関数を削除
- これらの結果、以前に提案されていたas-ifルールは不要になった
などです。
P3423R1 Extending User-Generated Diagnostic Messages
コンパイル時に診断メッセージを指定することのできる機能に対する文字列の制約を共通化する提案。
以前の記事を参照
このリビジョンでの変更は
- 属性のインスタンス化のタイミングについて議論を追加
- 属性の引数節が無効な置換をもたらした場合にSFINAEが発生すべきか
- 投票セクションを追加
- EWGに転送されたことに伴うAudienceの変更
- N5001へリベース
などです。
P3425R1 Reducing operation-state sizes for subobject child operations
operation_state
のサイズを削減可能にする提案。
以前の記事を参照
このリビジョンでの変更は明確ではないですが、提案する文言を追加したことがメインの様です。
P3430R2 simd issues: explicit, unsequenced, identity-element position, and members of disabled simd
std::simd
(P1928)のLWGレビューで見つかったIssueとその解決についてまとめた提案。
以前の記事を参照
- P3430R0 simd issues: explicit, unsequenced, identity-element position, and members of disabled simd - WG21月次提案文書を眺める(2024年10月)
- P3430R1 simd issues: explicit, unsequenced, identity-element position, and members of disabled simd - WG21月次提案文書を眺める(2024年12月)
このリビジョンでの変更は
- Issue 4について説得力のある例が見つからなかったため、問題なしとした
- 提案の全ての未反映の変更を統合した、Wordingセクションを追加
- 機能テストマクロのバンプ
などです。
P3431R0 Deprecate const-qualifier on begin/end of views
標準ライブラリのview
のconst
-iterableを非推奨にする提案。
標準ライブラリのview
型、特にrange
アダプタの結果型のview
型のbegin()/end()
メンバ関数にはconst
オーバーロードが用意されています。これによって、そのview
オブジェクトがconst
修飾されているときでもイテレーションが可能になります。この性質をconst
-iterableと呼びます。
しかし、view
は参照セマンティクスを持つものであるため、そのconst
性は要素のconst
を意味していません。また、一部のview
型はその特有の事情からconst
-iterableではなく、通常const
-iterableであるようなview
型でもforward_range
ではないinput_range
に対してはconst
-iterableではなくなります。
これらの事によって、view
のconst
-iterable性はしばしば落とし穴となります。
例えば次のコードのように、const
参照によってrange
を受け取っている場合
template <typename Rng> void do_something(const Rng& rng) { for (auto& x : rng) { … } }
これはふつうのコードであれば推奨されるスタイルですが、ことrange
に限ってはそうではなく、const
-iterableではないview
型を渡すと想定外にエラーとなります。正しくは次のように、range
を転送参照で受け取って、イテレーション時にconst
参照で受けるのがベストです
template <typename Rng> void do_something(Rng&& rng) { for (const auto& x : rng) { … } }
view
のconst
-iterable性によって、このようなコードはおおむね正しく動くように見えてしまいます。しかし、const
-iterableであっても要素はconst
とは限らないためいずれにせよそれは想定通りではなく、このような間違ったコードは早期にコンパイルエラーとなるべきです。
さらに、規格あるいはview
の実装を見に行くと分かりますが、ほとんどのview
型はconst
-iterableサポートのためにとても複雑なbegin()/end()
の実装を持っています。const
-iterable性が不要になれば、それらはかなり簡易化されるはずです。
そして、view
型がconst
でもrange
として扱える事のメリットはかなり小さい可能性があります。view
の型はかなり複雑であり、入力のrange
やそのほかの引数によって容易に変化します。そのため、view
オブジェクトに再代入したいケースというのはかなり少ないはずです(そもそも代入できないため)。また、const
化することでスレッド間での共有アクセスが可能になると考える人がいるかもしれませんが、まずconst
-iterableは要素のconst
を意味しないためその期待はあまり満たされません。さらに、view
の構築は通常かなり軽量であるため、各スレッドそれぞれで構築するようにしてもそれほどオーバーヘッドにはならないでしょう。
また、view
オブジェクトはRangeアダプタの結果に代表されるようにごく短い間しか存在しないものであり、ローカル変数に保存されることがあってもその実体のスコープは狭く、クラスメンバやより広いスコープを持つ変数に保存されることはめったにありません。
これらの理由により、この提案では既存の標準ライブラリview
のconst
-iterable性を非推奨にしようとする提案です。
view
からconst
-iterable性を取り除くことで、view
をconst
にすることの是非やconst
-iterable性そのものについてなどを考える必要がなくなり、それについての利用者の混乱を解消することができます。
begin()
end()
empty()
cbegin()
cend()
operator bool()
data()
size()
front()
back()
operator[]
次のview
型に対して非推奨とすることを提案しています
std::ranges::ref_view
std::ranges::owning_view
std::ranges::as_rvalue_view
std::ranges::transform_view
std::ranges::take_view
std::ranges::take_while_view
std::ranges::drop_view
std::ranges::drop_while_view
std::ranges::join_view
std::ranges::join_with_view
std::ranges::lazy_split_view
std::ranges::common_view
std::ranges::reverse_view
std::ranges::as_const_view
std::ranges::elements_view
std::ranges::enumerate_view
std::ranges::zip_view
std::ranges::zip_transform_view
std::ranges::adjacent_view
std::ranges::adjacent_transform_view
std::ranges::chunk_view
std::ranges::slide_view
std::ranges::cartesian_product_view
std::ranges::concat_view
これ以外のview
型
std::span/std::string_view
:const
/非const
で広く使用されており、再代入が有効な型であるため、破壊的変更になるstd::ranges::view_interface
: 下記のview
で使用されているstd::ranges::iota_view
: 適切な型名を持つRangeファクトリであるstd::ranges::repeat_view
: 適切な型名を持つRangeファクトリであるstd::ranges::empty_view
: 全てのメンバ関数はstatic
std::ranges::single_view
: 深いconst
性を持つ
に対してはconst
-iterable性を維持しようとしています。
とはいえ、この除外されているもの以外のものでも、現在const
-iterableであるものについてはconst
オーバーロードを削除してしまうと破壊的変更となるため、ここでは非推奨にとどめています。
P3439R1 Chained comparisons: Safe, correct, efficient
誤って書かれることの多い、連鎖比較を意図通りに動作するようにする提案。
以前の記事を参照
このリビジョンでの変更は
- 文言の追加
- 畳み込み式のサポートを追加
- トリガー(判定)のルールを、「個々の二項比較」ではなく「書き換えられた式全体が有効であり、文脈的に
bool
に変換可能である」に変更 - Q&Aセクションを追加し、次の項目を追加
- 以前の実装可能性に関する懸念事項への対応
- ユーザー定義の数学型が問題なく動作すること
- 廃止期間を設けるべきかどうか
- 追加の利点
などです。
P3475R1 Defang and deprecate memory_order::consume
memory_order_consume
を非推奨化する提案。
以前の記事を参照
このリビジョンでの変更は、より詳細な文言を追加したことです。
この提案はすでにEWGの投票をパスしてCWGでレビューされています。
P3477R2 There are exactly 8 bits in a byte
1バイトを8ビットであると規定するようにする提案。
以前の記事を参照
- P3477R0 There are exactly 8 bits in a byte - WG21月次提案文書を眺める(2024年10月)
- P3477R1 There are exactly 8 bits in a byte - WG21月次提案文書を眺める(2024年12月)
このリビジョンでの変更は、SG22での検討結果を追記し、SG22をaudienceから外したことのみです。
この提案についてはWG14(C)も関心を持つ可能性があるとされ、SG22の観点からの懸念点は報告されなかったようです。
P3480R3 std::simd
is a range
std::simd
をrange
にする提案。
以前の記事を参照
- P3480R0 std::simd is a range - WG21月次提案文書を眺める(2024年10月)
- P3480R1 std::simd is a range - WG21月次提案文書を眺める(2024年12月)
このリビジョンでの変更は
- タプルインターフェースのサポートの必要性について質問を追加
- [simd.iterator]の配置場所を明確化
- 適切な表現に修正
default_sentinel_t
での<=>
比較を修正- 機能テストマクロのバンプは必要?
などです。
P3481R1 std::execution::bulk()
issues
std::execution::bulk()
の改善提案。
以前の記事を参照
このリビジョンでの変更は
- SG1のレビューからのフィードバックを適用
- 文言セクションを追加
などです。
P3491R1 define_static_{string,object,array}
コンパイル時に構築した文字列や配列などを静的ストレージに昇格させて実行時に持ち越せるようにするライブラリ機能の提案。
以前の記事を参照
このリビジョンでの変更は文言の改善のみです。
P3496R0 Immediate-Escalating Expressions
定数式における動的メモリ確保の制限を少しだけ緩和する提案。
この提案は、以前にP3032R2で提案されていた内容の一部を抽出したものです。P3032については以前の記事を参照
P3032では次の2つの事が提案されていました
- 定数式にならない即時関数呼び出しが定数式になるように、それを含むより大きな式を定数式として扱う
- 定数式における動的メモリ確保に関して、開放のタイミングが同じ評価内にある場合に加えて、同じ直接のコンテキスト内にある場合も許可する
この提案では、このうち1だけを改めて提案しなおしています。
そのモチベーションは共通しており、リフレクション機能で頻繁に問題になる事が想定されているので予め解決しておこうとするものです。
P3032での問題は次のようなコードでエラーが発生することでした
enum E { a1, a2, a3 }; constexpr int f2() { return enumerators_of(^E).size(); // ng } int main() { constexpr int r2 = f2(); return r2; }
これはenumerators_of(^E).size()
の式全体がconsteval
コンテキスト(即時コンテキスト)にならない(enumerators_of(^E)
の呼び出しだけがなる)ことで、enumerators_of
の戻り値のstd::vector
の解放がそのコンテキスト内で完了しないとみなされてしまうためにエラーになっています。
P3032及びここでの提案は、このような場合にenumerators_of(^E).size()
全体がconsteval
コンテキストに昇格するようにしようとするものでした。
このような一部の定数式の特別扱いはルールが複雑になる割に回避が簡単であるとしてP3032の初期のリビジョンでは提案されていませんでした。この提案は、その実装経験のフィードバックとともに、現在の規格内での先行例を指摘しています。
すなわち、式E1
が式E2
の部分式であってE1
は定数式ではないがE2
は定数式である、ような場合に全体が定数式ではないとしてエラーにしないケースが規格上で2か所存在しています。
1つは、consteval
関数の呼び出しそのものです。この場合、consteval
関数の名前の使用そのものは定数式ではないものの、consteval
関数の呼び出しは定数式になる場合があります。
consteval int id(int i) { return i; } /* not constexpr */ void f(int x) { auto a = id; // error、名前の使用 auto b = id(1); // ok、定数引数による呼び出し auto c = id(x); // error、非定数引数による呼び出し }
id
という式そのものは定数式ではありませんが、id(1)
は定数式になります。
もう一つの例は、集成体においてデフォルトメンバ初期化子に非即時呼び出しなconsteval
関数呼び出しが含まれている場合、集成体初期化式が定数式であればデフォルトメンバ初期化子のconsteval
関数呼び出しも即時呼び出しになります。
// id()は先穂の例と共通 struct A { int x; int y = id(x); // 引数が非定数式なため即時呼び出しではない }; template<class T> constexpr int k(int) { // A(42)は定数式ではあるものの即時呼び出しではないため、k<int>は即時関数ではない return A(42).y; // ok }
A::y
のデフォルトメンバ初期化子のid(x)
は内部的に定数式ではないものの、それが呼ばれるA(42)
は定数式であるため、その外側がconsteval
コンテキストではなくても定数式となり許可されています。
これらの事を根拠にこの提案ではimmediate-escalating expressionの規則を修正して、consteval
関数呼び出しが定数式で囲まれていない場合にのみimmediate-escalating expressionとなるようにしようとしています。
式enumerators_of(^E).size()
(現在この全体がconsteval
コンテキストではない)については
enumerators_of
はconsteval-only式(consteval
関数であるため^E
もconsteval-only式(meta::info
がconsteval-only型であるためenumerators_of(^E)
の呼び出しもconsteval-only式- ただし、戻り値の
vector
がこのconsteval
コンテキスト内で破棄されないため、これは定数式ではない - そのため即時呼び出しではない
- ただし、戻り値の
- より大きな式
enumerators_of(^E).size()
は定数式であり、consteval
な非定数式を部分式にもつため、この式は即時呼び出しとなる(ようになる- これにより、
enumerators_of(^E)
そのものはimmediate-escalating expressionではなくなる
- これにより、
のようにして許可されます。
かなり回りくどいですが、現在の仕様ではenumerators_of(^E)
の呼び出しだけがimmediate-escalating expressionになってしまい、そのコンテキスト内で戻り値のstd::vector
が破棄されなければならない、という制約が付加されてしまうことで問題が起きています。この提案後には、その制約が破られることで非定数式となった部分式かつconsteval
関数呼び出しを囲む定数式全体が即時呼び出しとなるようになることで、enumerators_of(^E)
の戻り値の使用可能なスコープが囲む式全体まで広がり、これによってエラーが解消されます。
なお、この提案の内容はP2564R3(immediate-escalating expressionを導入した提案)に対するDR(すなわちC++23に対するDR)とすることを意図しています。
P3499R0 Exploring strict contract predicates
非緩和契約の設計についての提案。
非緩和契約(あるいは厳密な契約)とは、P2680R1やP3285R0で提案されている契約条件の評価において未定義動作を起こさないようにするための仕組みの事です。これは主に、未定義動作に繋がる操作を禁止するとともに、一部の未定義動作を定義済み動作にすることによって行われます。
非緩和契約のような制限の無い契約の事を緩和契約と呼んでおり、これはP2900R13の契約仕様のセマンティクスに合致しています。しかし、P2900R13のContracts仕様には非緩和契約は含まれておらず、そのことがEWGにおける継続的な反対の主な対象となっています。
現状の非緩和契約の概念はアイデアのみの段階でその仕様や実装が無く、実現可能性が不透明です。P3376R0およびP3386R0ではP3285R0の非緩和契約について検討を行っていますが、ほとんど実用的な契約条件を表現できない事などの問題が報告されており、SG21では非緩和契約は少なくともC++26に向けては追及しないことが決定されています。
この提案では、非緩和契約の設計について検討し、その最初の仕様の策定を目指そうとするものです。
まず出発点として、データ競合を除く未定義動作が確実に起こらないことが保証可能な述語を記述できる式を特定します
- 算術型または列挙型のリテラル。
- 算術型または列挙型の非
volatile
変数を指すid-expression(変数名を指定する式- ポインタや参照は含まれない
- 単項演算子
+, -, !
を使用した式(unary-expression) - 二項演算子
+, -, /, %, *, !, ,, ^, |, ||, &, &&, <<, >>
を使用した式(binary-expression) - 単項演算子
?, !
を使用した条件式(conditional-expression) <, >, <=, >=
を使用した式(relational-expression)==, !=
を使用した式(equality-expression)<=>
を使用した式(compare-expression)- 算術型または列挙型のコア定数式
かなり小さな集合ではありますが、将来的に拡張していくことができます。
これらの式による述語は予測不可能な未定義動作を起こさず、副作用を持たないものです。そのため、非緩和契約で使用可能な述語をこれら楽しきにのみ制限することで非緩和契約の一つの目標は達成されます。そして、これらの式で発生が予測される未定義動作は次のものに絞られます
- 符号付整数型のオーバーフロー/アンダーフロー
- 浮動小数点数値の、その値を表現できない型への変換
- ゼロ除算
- 負の値でのシフト
- 型のビット幅以上のシフト量によるシフト
非緩和契約ではこれらの未定義動作を定義済み動作に変換する必要があります。P3285R0で提案されていた飽和演算やラップアラウンド演算による動作の書き換えは、契約外での動作と異なることによってバグの隠蔽や見落としに繋がることが指摘されているため望ましい選択肢ではなく、この提案では未定義動作の発生を契約違反として扱うアプローチを推奨しています。
これは、P3100R1/P3229R0の方向性と合致しており、よりきめ細かい契約違反ハンドリングを可能にする方向性です。
最後に、非緩和契約の構文については、P2900の現在のデフォルトを変更せずにpre strict (x)
のようにオプトインなものとすることを提案しています。これは、反対意見はあったもののEWGで合意済みの事です。
これらの事項を文言にエンコードすることによって非緩和契約の仕様は策定可能となりそうです。しかし、この仕様にはかなりの制限があります。例えば
結局この提案の結論としては、非緩和契約の方向性が有効であるとは思えない、というものです。ただ、依然として求める声が多い非緩和契約がどのようなものになるのかについてのこれらの検討がP2900R13のコンセンサスを高めるものになることが期待されています。
P3500R0 Are Contracts "safe"?
C++ Contracts提案の安全性に対する懸念点からの反対意見に対しての解消のための説明文書。
P2900R13で提案中のContracts機能に対してはいくつか懸念点が繰り返し提起されています。
まず一つは評価セマンティクスが柔軟であることによって、契約違反が起きた後に実行が継続されてしまうことです。これにより、プログラムの実行が未定義動作に突入する可能性があります。
T& MyVector::operator[] (size_t index) pre (index < size()) { return _data[index]; // UB if index >= size() }
この例では、契約チェックがobserve
セマンティクスで行われている場合、未定義動作が発生します。
もう一つは契約アサーションが任意のbool
式を受け取れることによって、契約条件式自体の評価に伴って未定義動作が発生しうることです。
int f(int a) { return a + 100; } int g(int a) pre (f(a) > a);
この例では、f()
内での符号付整数の加算がオーバーフローすることはないとコンパイラが仮定することができ、その場合g()
の事前条件は常に満たされるため、契約の評価セマンティクスによらずg()
の事前条件チェックを省略することができます。
これらの問題により、P2900のContracts機能は安全ではないから同意できない/修正すべきだ、という意見が根強くあります。この文書は、それらの人々を説得するためのもので、C++ Contracts機能が何を目的としているか、P2900が何をしようとしているか、C++26にContractsが必要な理由、などを説明するものです。
要約すると
- 「安全」という言葉の意味とここでの定義
- 「安全(性)」という言葉は人によって異なる意味で使用されており、この共通理解が欠如している
- 安全には主に次の3つの意味があり、区別する必要がある
- 言語安全性
- 機能安全性
- セキュリティ
- これら3つの概念は正確性によって強く関連付けられている
- 正確なコードは3つの安全性を満たしているが、逆は必ずしも成り立たない(3つの安全性を満たしても正確性が満たされないことがある)
- 開発者の究極の目的は正確性を最大化することにあり、それは3つの安全性のいずれかあるいはすべてだけを追求していては達成できない
- Contractsがターゲットとしているのは、プログラムの正確性を向上させること
- これは、現在C++に行われている言語機能についての提案の中で、明示的に正確性をターゲットにする唯一の機能である
- 懸念の解決策について
- C++26 Contractsの必要性について
- WG21はソフトウェアの安全性(機能安全性/セキュリティ)を優先する道義的責任を負っている
- しかし、機能安全性もセキュリティも言語安全性だけでは達成できず、ソフトウェアの問題の多くは未定義動作よりもむしろ論理的なバグや設計上の欠陥によって引き起こされている
- Contractsは正確性を向上させるための機能であり、コード内の各所でプログラム状態をチェックできるようにするツールを提供することでプログラムの正確性を向上させ、それによって機能安全性/セキュリティを向上させようとするもの
- 言語安全性の向上に全く関係しない訳では無いが、その主目的は言語安全性の向上をターゲットにしていない
- Contracts機能は言語安全性を向上させようとするほかの機能と相補的な関係にある
- P2900はC++ Contractsの最初の一歩(MVP)であり、C++29以降の機能の基盤となるもの
- また、C++にContractsが導入されるまでに時間がかかればかかるほど、Contractsに投資する人々/企業が撤退してしまう
- C++ Contracts機能は20年以上に渡って開発されておりP2900はその集大成である
のようなことが説明されています。
P3501R0 The ad-dressing of cats
規格文書内でのアドレスという言葉の意味と使用を明確化する提案。
規格内ではアドレスという言葉には明確な定義がなく、暗黙的な定義の下で使用されています。それによって例えば、ある場所([intro.memory]/1)では「全てのバイトは一意のアドレスを持つ」というように使用されている一方で、別の場所([basic.compound]/3)ではポインタはメモリのバイトの「アドレスを表す」のように使用されています。他にも、&
演算子の記述のために使用されていたり、ポインタそのものの事を指して使用されていたりしているようです。
そして、アドレスという単語を使用すべきなのに使用していないことによって導入されている欠陥が一つあります。[defns.order.ptr]では、std::less<void*>
等で実装定義のポインタ値の狭義全順序による順序付けを許可することを指定していますが、アドレスという言葉を使用せずに曖昧に規定していることによって、これはライブラリサイドでは実装不可能になってしまっています。例えば
std::less<void*>
では、配列へのポインタとその配列の先頭要素のポインタを区別できない(比較不能)可能性があるstd::less<int*>
では、あるint
オブジェクトの次を指すポインタ(&x+1
)と、そのオブジェクトの直後に配置されている別のint
オブジェクトへのポインタを区別できない可能性がある
実装定義の全順序によるポインタ値の順序付けがサポートされている場合、これらのケースでも比較可能であり、ライブラリの比較関数オブジェクトでも同様なはずです。しかし、アドレスという言葉の曖昧性やその順序付けの曖昧性により、それを正しく指定できていません。
この提案では、アドレスという言葉をしっかりと定義して、使用すべきところではその定義の下で使用しすべきでない所ではほかの適切な言葉におきかえるようにしようとするものです。
この提案ではまず、アドレスはメモリバイトに対する(実装定義の)全順序付きの不透明ラベルであるとして定義しています。そのうえで、全てのポインタ値(関数ポインタやnullptr
を含む)をこの定義の下でのアドレスという言葉を関連付けるようにすることで、ポインタ値に対する(実装定義の)全順序を導入します。そして、ポインタに対する比較をこのアドレスという言葉を使用して定義することで、ポインタの順序付けを実装定義の全順序の上で定義し、同様にライブラリの比較関数オブジェクト(std::less
等)における結果も実装定義の全順序の上で定義するようにします。
また、定数評価中に比較(<
)が利用可能なポインタに対して“ordered”という言葉を導入し、比較の定義でこれを使用することで、定数式で使用できない比較とできる比較を区別するようにしています。
同時に、混同されていたポインタとアドレスという言葉の使用方法をこの定義の下で明確化し変更しています。
この提案はほとんど言葉の使用方法の調整のみですが、(意図するライブラリ比較と言語比較の不整合解消を除いて)唯一オブジェクトの一つ後ろを指すポインタに関する比較が未規定ではなくなった点だけはセマンティクスの変更となります。
P3506R0 P2900 Is Still not Ready for C++26
Microsoftによる、P2900R11のContracts仕様に対する反対意見の表明文書。
この文書はP3173R0での主張を現在の状況を踏まえて更新するものです。
P3173R0については以前の記事を参照
P3173R0で主張されていた3点については状況はあまり変わっていません。仮想関数における契約のサポートは追加されていますが、この文書では複雑すぎであり仮想関数の長年の使用方法に則っていない、として不十分としています。
ここでは追加で
- コルーチンの契約サポートは実装と使用の経験が必要
- 標準ライブラリでの契約機能の使用については、既存のアサートを
contract_assert
に置き換えただけであり、事前条件や事後条件を使用していない- このため、実用性の懸念は未解決
- 契約条件の評価中に発生した例外を違反ハンドラがハンドリングしてしまうのは、多くの点で有害
- 契約条件評価を
try-catch
で囲むことになるので、バイナリサイズや実行時コストの点で不利 - 例外を自然に伝播させるためには違反ハンドラの置換が必要になる
- 契約条件評価を
- 契約注釈内での外部の変数の
const
化はそれ自体が問題を発生させる部分的な解決策でしかなく、混乱をもたらしている- より良い解決策は
const
化を削除すること
- より良い解決策は
などの問題点を挙げています。
これらの問題点が解消されない限り、引き続きMicrosoftはC++26にP2900を組み込むのに反対する、としています。
P3516R0 Uninitialized algorithms for relocation
未初期化メモリに対するリロケーションアルゴリズムのライブラリ機能の提案。
C++26に向けて、言語で(トリビアル)リロケーションの定義を提供するとともに、それを行うためのライブラリ操作を導入しようとしています(P2786/P1144)。その作業の最先端はP2786R11で行われていますが、そこでは最近機能のスコープを絞るためにライブラリのAPIの大半が分離されました。
この提案は、P2786が提供しようとしているリロケーションの概念を用いて、未初期化メモリに対するリロケーションによる初期化を行うアルゴリズムを提案するものです。
提案するのは主に次の3つの関数を基本としたものです
std::uninitialized_relocate()
std::uninitialized_relocate_n()
std::uninitialized_relocate_backward()
- これらのRange版と並列版
これらのアルゴリズムは、ある未初期化なメモリ範囲を、別の範囲からそれぞれの要素をリロケートして初期化するものです。いずれの関数も入出力範囲のオーバーラップを許可し、途中で例外が送出された場合は入出力範囲のすべての要素が破棄されます。そして、このアルゴリズムが正常に完了した場合は入力の範囲に要素は含まれなくなり、未初期化状態になります。
この提案ではリロケーションという概念の定義などを提供しようとはしておらず、リロケーションをムーブ+破棄であると仮定してそのas-ifによってリロケーション動作を指定しています。これによって、P2786がマージされていない状況でもリロケーションAPIを提供することができ、P2786がマージされてもそのより高度なリロケーションの定義を自動で利用することができます。
提案文書より、vector::erase()
で使用する例
// 今まで constexpr iterator vector<T>::erase(iterator first, iterator last) { if (first == last) return last; auto new_end = std::move(last, end(), first); std::destroy(new_end, end()); end_ -= (last - first); return first; }
// この提案 constexpr iterator vector<T>::erase(iterator first, iterator last) { if (first == last) return last; // Destroy the range being erased and relocate the // tail of the vector into the created gap. std::destroy(first, last); std::uninitialized_relocate(last, end(), first); // 👈 end_ -= (last - first); return first; }
文書にはもう少しstd::vector
で使用する例が載っています。
リロケーション操作は通常、単なるムーブ代入よりも効率的であるため、これをいれておくことでC++26以降のライブラリのパフォーマンスの最適化を図ることができます。
P3527R1 Pattern Matching: variant-like and std::expected
パターンマッチングでstd::expected
を使用できるようにする提案。
以前の記事を参照
このリビジョンでの変更は明確ではないですが
std::expected
をstd::variant
互換にすることでハンドリングするようにしたstd::visit
の変更を提案しなくなった
などだと思われます。
この提案では、std::expected
に対して次の変更を適用することで、match
式においてstd::variant
と同等に扱えるようにしています
.index()
を追加std::variant_size
/std::variant_alternative
のstd::expected
で動作する特殊化を追加std::expected
で動作するstd::get
のstd::size_t
オーバーロードを追加
また、パターンマッチングに限らない場所でより使用感を近づけるために、オプションとして次の事を提案しています
std::expected
で動作するstd::holds_alternative()
を追加std::expected
で動作するstd::get
の型指定するオーバーロードを追加std::expected
で動作するstd::get_if
のオーバーロードを追加
std::visit
については、2つしか選択肢がないことやパターンマッチング導入後に不要になることから変更を提案していません。
P3533R1 constexpr virtual inheritance
定数式で、仮想継承をしているクラス型を扱えるようにする提案。
以前の記事を参照
このリビジョンでの変更は、constexpr
コルーチンよりも前に採択される可能性に備えて代替の文言を用意したことです。
P3534R0 Avoid UB When Compiling Code That Violates Library Specification
コンパイル時に起こるタイプのライブラリのUBについて、IFNDRに置き換える提案。
標準ライブラリの仕様には、ライブラリが想定しないような使用方法がなされた場合にUBとなる規定が一部に存在しています。そのようなライブラリのUBのうち、一部のものは未定義動作が発生するのが実行時ではなくコンパイル時であることが分かっている場合があります。この提案は、そのようなケースのUBという言葉の使用を(ill-formed, no diagnostic required)に置き換えようとするものです。
その対象の例としては、ユーザーが標準ライブラリ内テンプレートの特殊化を追加することを禁止する規定です。これは現在UBとされていますが、このUBはコンパイル時に発生し、実行時に起こるわけではありません(それを追加することでどのテンプレート特殊化が選択されるか分からなくなることでUBとなるが、それが起こるのはコンパイル時のみ)。
実行時ではIFNDR(プログラム全体に対して一切要件を課さなくなる)よりもUB(違反が発生した部分のみの動作が未定義となる)の方が動作の指定としては優れているというか、マシなものです。しかし、コンパイル時は逆で、コンパイル時のUBはコンパイル処理そのものの動作を無制限のリスク要因としてしまうため、この様な規定方法はおそらく標準の意図するところではなく、通常必要とはされないはずです。そのためこの提案では、標準ライブラリのコンパイル時に発生するタイプのUBの使用をIFNDRに置き換えようとしています。
P3541R1 Violation handlers vs noexcept
違反ハンドラ(契約注釈)とnoexcept
演算子の相互作用について明確にすることを求める提案。
以前の記事を参照
このリビジョンでの変更は
noexcept
に加えて、スタック巻き戻しの保証についても考慮するようになったnoexcept
演算子とnoexcept
指定子が別の問題であることを明確にするために、議論を再構成した
などです。
このリビジョンでは、違反ハンドラからの例外送出を予告なしの例外送出(unannounced throw)と呼称し、次のようなコードにおいてスタック巻き戻しがどうなるかを問うています。
void set_positive(int* pValue) { std::lock_guard _ {_mutex}; _result = (*pValue > 0); // i }
iの行の式では現在例外送出されることがないため、実装はスタックの巻き戻しを考慮せずにmutex
のアンロックを通常のフローに従って配置できます。しかし、P3081R0の世界では予告なしの例外送出時にこのようなところでも例外が送出される可能性があるため、その場合にどうするかを指定する必要があります。
それは、実行時のペナルティを受け入れてデストラクタ呼び出しを保証するか、デストラクタの呼び出しが保証されない別の種類のスタック巻き戻し機構が存在することになるか、のどちらかです。あるいは、実装が違反ハンドラを呼び出すか呼び出さないかの2択ではなく、呼び出さない・通常のスタック巻き戻しを伴う呼び出し・別のスタック巻き戻しを伴う呼び出し、の3種類から選択できるようにすべきでは?としています。
予告なしの例外送出においてこのようなプログラムの挙動が不明瞭である場合、バグからの復帰という意味では(例外の代わりに)未定義動作が起きている現状とあまり変化がありません。したがって、違反ハンドラからの例外送出時のプログラムの動作について(このスタック巻き戻しも含めて)明確に指定する必要がある、としています。
P3546R0 Explicit return type deduction for std::numeric_limits and numbers
std::numeric_limits
や<numbers>
で型の指定を省略可能にする機能の提案。
std::numeric_limits
によって何らかの定数を取得する時、典型的には次のようなコードを書くことになりますが、欲しい型名を2回書くことになります。
float f1 = std::numeric_limits<float>::max();
変数宣言であれば、変数の型名はauto
で受けることもできます。しかし、非静的メンバ変数の宣言や関数引数の宣言ではauto
を使用できないため2回書かざるを得ません。
/* (1)- For structs */ struct F { float f1 = std::numeric_limits<float>::max(); }; /* (2)- For functions */ void func1( float f = std::numeric_limits<float>::max());
この提案はstd::deduce
というタグ型を導入し、それを利用することによってこの2つ目の型の指定を省略可能にしようとするものです。
/* (1)- For structs */ struct F { float f1 = std::numeric_limits<float>::max(); // Before float f2 = std::numeric_limits<std::deduce>::max(); // After }; /* (2)- For functions */ void func1(float f = std::numeric_limits<float>::max()); // Before void func2(float f = std::numeric_limits<std::deduce>::max()); // After
元の型名よりもたいていの場合長くなりますが、変数の型名を変更したくなった場合に両方とも変更する必要が無くなるメリットがあります。また、std::deduce
という特徴的な名前はそれが使用されている個所を目立たせ検索しやすくする効果もあります。
このstd::deduce
は純粋なライブラリタグ型であり、std::numeric_limits
の場合は例えば次のように実装できます
namespace std { template<> struct numeric_limits<std::deduce> { struct max { // テンプレート型変換演算子 template<typename Out> operator Out() { return numeric_limits< Out >::max(); } /* ... */ }; }; }
変数テンプレートの場合も同様のテクニックによって実装可能です
inline constexpr auto M_ONES = 1.111111111111111111111111111111111111111L; template< typename T = double > struct m_ones_v_c { constexpr operator T() const { return M_ONES; } }; template<> struct m_ones_v_c<std::deduce> { template< typename Out > constexpr operator Out() const { return M_ONES; } }; template<typename T> inline constexpr auto m_ones = m_ones_v_c<T>{}; inline constexpr auto m_ones = m_ones_v_c<double>{};
これらの実装はおそらく、既存の使用方法に対してAPIの破壊的変更を伴わないものです。
提案ではstd::deduce
は<utility>
に配置し、std::numeric_limits
と<numbers>
に対してこれを使用可能にする変更を適用しようとしています。
P3547R0 Modeling Access Control With Reflection
クラスメンバのリフレクションにおいて、アクセスコンテキストを明示的に指定するようにする提案。
クラスメンバのリフレクションにおけるクエリにおいて、クラスのアクセス制御を考慮すべきかという問題が古くから議論の対象となってきました。P2996では一貫して次のような方向性の下で設計されています
- リフレクション対象のクラスの全てのメンバのリフレクションを取得できる
- 任意のエンティティのリフレクションに対して、プロパティをクエリできる
- リフレクションされたエンティティを名前探索やアクセス制御に頼らずに、スプライシングによって直接参照する
これにより、members_of()
によってクラスの全メンバのリフレクションを取得し、identifier_of
やtype_of
等の術語によってそれらのプロパティを照会し、スプライシングによって様々なコンテキストでメンバを使用することができています。
しかし、P2996のレビューが進むにつれて、クラスのアクセス制御の扱いについて委員会内部でも様々な流派が存在することが分かってきました。
- 多くの人は、クラスのイントロスペクションを許容するモデルはリフレクションの絶対要件であるとしている
- ユーザーはプライベートメンバのリフレクションがそのまま取得できることに驚くかもしれず、オプトインにすべきだという意見も少なからず表明された
- 呼び出しコンテキストがアクセスできるすべてのメンバのリフレクションを取得できるべき、という指摘もあった
- アクセスできないメンバのリフレクションを取得できるべきではない、という人もわずかに居る
これらの立場の中には相容れないものもあるため、もはやすべての人々の意向を満たす解決策は存在していません。
この提案はこれらの懸念の一部を解消するために、access_context
という型の値によってアクセスコンテキストを表現し、それを値としてやり取りすることによってクエリが取得できるアクセスコンテキストを制御できるようにしようとするものです。
この提案では例えば、members_of()
は第2引数にアクセスコンテキストを取るようにします
consteval auto members_of(info cls, access_context ctx) -> vector<info>;
std::meta::access_context::unchecked()
を使用することで現在のP2996の動作である無制限のアクセス制御と同等のコンテキストを取得できます
using std::meta::access_context; class Cls { private: static constexpr int priv = 42; }; constexpr std::meta::info m = members_of(^^Cls, access_context::unchecked())[0]; static_assert(identifier_of(m) == "priv"); static_assert([:m:] == 42);
他には、現在のコンテキスト(呼び出し元の最も内側のコンテキスト)を取得するstd::meta::access_context::current()
、グローバル名前空間からのアクセスコンテキストを取得するstd::meta::access_context::unprivileged()
の3つが用意され、access_context
の値はこれら3つの関数のいずれかから取得され、他の方法で構成できません。
これらの基本メタ関数を使用して作成されるメタ関数では、std::meta::access_context
の値を受け取ってそれを使用するようにすることで同等のアクセス制御の指定を行えます
consteval auto constructors_of(std::meta::info cls, std::meta::access_context ctx) { return std::vector(std::from_range, members_of(cls, ctx) | std::meta::is_constructor); }
このようにすることで、一つの関数で様々なアクセス制御の指定に対応することができ、P2996R7で導入されたget_public_members
系の関数名でアクセス制御の指定を行う必要がなくなります。
protected
メンバや基底クラスメンバへのアクセス制御には複雑なものがあるので、これだけだと対応できません。例えば次のようなクラスがある時
struct Base { protected: int prot; }; struct Derived : Base { void fn(); };
Derived::fn()
の定義内からは、prot
メンバへアクセスできる一方で、そのメンバへのポインタを形成することはできず、Base
のスコープを指定する必要があります
void fn(){ this->prot = 42; // ok auto mptr = &Derived::prot; // ok auto mptr = &Base::prot; // ng }
このような複雑なアクセス制御のセマンティクスを解決できなかったことが、この提案の元になったstd::meta::access_context
APIをP2996R7から削除させることになりました。
この問題の解決のために、std::meta::access_context
に.via()
メンバ関数を追加し、そこにスコープを指定するクラスのリフレクションを渡すことで、上記のようなセマンティクスの再現を可能とします
void Derived::fn() { using std::meta::access_context; constexpr auto ctx1 = access_context::current(); // 現在のアクセスコンテキスト constexpr auto ctx2 = ctx1.via(^^Derived); // ctx2にはDerivedクラスをスコープとして指定 static_assert(nonstatic_data_members_of(^^Base, ctx1).size() == 0); // スコープクラスが指定されない場合第1引数のクラス名が使用され、Baseのスコープでアクセス可能なBaseの非静的データメンバをクエリする // が、コンテキストはDerivedであり、Baseのprotectedメンバにアクセスできない // Derivedのコンテキストからは、Base::protにアクセスできない static_assert(nonstatic_data_members_of(^^Base, ctx2).size() == 1); // ok、Derivedのスコープを使用し、DerivedのコンテキストでBaseの非静的データメンバをクエリする // Derivedのコンテキストから、Derived::protにアクセスできる }
このaccess_context
クラスの構造は次のようになります
class access_context { consteval access_context(info scope, info naming_class) noexcept : scope{scope}, naming_class{naming_class} { } public: const info scope; // exposition only const info naming_class; // exposition only consteval access_context() noexcept : scope{^^::}, naming_class{} { }; consteval access_context(const access_context &) noexcept = default; consteval access_context(access_context &&) noexcept = default; static consteval access_context current() noexcept { return {__metafunction(detail::__metafn_access_context), {}}; } static consteval access_context unprivileged() noexcept { return access_context{}; } static consteval access_context unchecked() noexcept { return access_context{{}, {}}; } consteval access_context via(info cls) const { if (!is_class_type(cls)) throw "naming class must be a reflection of a class type"; return access_context{scope, cls}; } };
この提案ではまた、あるメンバのリフレクションが指定されたコンテキストでアクセス可能かどうかをクエリするメタ関数is_accessible()
を追加しています。これを用いると、指定したコンテキストからアクセス可能なメンバのリフレクションのみを取得する処理が簡単に書けます
consteval auto has_inaccessible_nonstatic_data_members(info cls, access_context ctx) -> bool { return !std::ranges::all_of(members_of(cls, std::meta::access_context::unchecked()), [=](info r) { return is_accessible(r, ctx); }); } consteval auto has_inaccessible_bases(info cls, access_context ctx) -> bool { return !std::ranges::all_of(bases_of(cls, std::meta::access_context::unchecked()), [=](info r) { return is_accessible(r, ctx); }); }
この2つの関数は標準ライブラリで用意していてほしいという要望があったため、この提案に含まれています。
以前に同様の懸念に対処しようとしていた提案(P3451R0/P3473R0)ではクエリ時ではなくスプライシング([:r:]
)時にアクセスチェックを行うことによってアクセス制御をリフレクションに反映させようとしていました。しかし、クエリそのものは出来てしまい、リフレクションさえ取得できればアクセス制御を回避することが可能なメタ関数が多数あるため、そのアプローチではアクセス制御の懸念の解消には不十分でした。
この提案では、クエリの段階でアクセス制御を考慮し調整できるようにすることで、アクセス制御のリフレクションへの組み込みをより確実に行うことができます。なおかつ、access_context::unchecked()
などの関数はコード上で目立つ(検索が容易である)ため、コード作成者の意図を誤解しづらくなっています。
- P3451R0 A Suggestion for Reflection Access Control - WG21月次提案文書を眺める(2024年10月)
- P3473R0 Splicing Should Respect Access Control - WG21月次提案文書を眺める(2024年10月)
- P3547 進行状況
P3548R0 P1030 std::filesystem::path_view
forward progress options
path_view
に対する懸念に対しての解決の提案。
P2645では、提案中のpath_view
に対して標準化に当たってのいくつかの懸念点が指定されていました。これについてLEWGで活発な議論が行われ、そこでは問題は主にpath_view
が内部パス文字列表現のためにchar
を使用することによってWindowsでANSIエンコーディングを使用してしまう事、に絞られたようです。
この問題によってpath/path_view
-> パス文字列 -> path/path_view
のようなラウンドトリップを行った場合に、元に戻らなくなる(同じパスを表さなくなる)ことが懸念されています。
しかし、std::filesystem::path
においては、内部パス表現は渡されたパスのビット列を正確にネイティブのAPIに渡すことを意図したものであり、そもそもラウンドトリップをサポートし切ることはあまり目指されてはいないようです。path_view
においてもその設計を踏襲しており、内部ビット表現にchar
を使用するのは特定のエンコーディングを仮定するのではなく、渡されたパス文字列のビットを(システムにとって)正しく保持することを目的とするものです。
そして、path_view
においては、保持するパス文字列ビットを人間にとってのパス文字列に変換する際には、それをUTF-8エンコーディングとして読み取るという設計になっており、これをfilesystem::path
にも適用すべきとしています。
この提案では、LEWGにおける議論及びその後の調査によって導かれた解決策を4つ提示しています。
- ネイティブファイルシステムエンコーディングが
char
ではないプラットフォームにおけるchar
内部表現サポートを削除する - 全てのプラットフォームで
char
内部表現サポートを削除する- 利点
- プラットフォーム間で
path_view
の一貫性を確保できる
- プラットフォーム間で
- 欠点
path
へのchar
文字列入力は可能だが、path_view
ではできなくなるpath
の内部表現がchar
である場合、path
からpath_view
への変換は可能であるため、意外性がある
- 利点
path_view
の表現はそのままで、path_view
のフォーマッターはpath
のフォーマッタと同じ動作をする- C++26(予定の)
path_view
の表現はそのままで、フォーマッタをC++29まで延期する- 利点
path_view
はソースエンコーディングの認識を保持するのに対して、path
は忘れるため、一部のコーナーケースにおいて人間にとっての最適性という意味でより優れたフォーマッタを設計できる可能性がある
- 欠点
path
とpath_view
のフォーマッタの一対一対応が損なわれる可能性がある
- 利点
筆者の方は案3、すなわち現状維持を押しています。
また、P2645で別の懸念点であったパフォーマンスの問題について、ベンチマークが提供されています
P3549R0 Diverging expressions
値を生成しない式を表現するための特別な型(ボトム型)を追加する提案。
値を生成しない式とはvoid
型の式の事ではなく、制御フローを脱出することでいかなる値を生成することもない式の事です。このような式の事を発散する式(Diverging Expression)とも呼び、現在のC++には発散する式が2種類存在します
throw
式[[noreturn]]
関数の呼び出し式
この2つの式はどちらも発散する式ではありますが、その扱いは微妙に異なっています。例えば条件演算子において違いを観測することができます
int x1 = condition ? 42 : throw std::runtime_error("oops"); // ok int x2 = condition ? 42 : std::terminate(); // ng
条件演算子による式は2つの分岐の型を統合した型を持つ必要がありますが、片方のオペランドがthrow
式の場合その型はもう一方のオペランドから決定されます。一方で[[noreturn]]
関数の呼び出し式も同様に発散する式ではあるものの、このような特別扱いはされないためその関数の戻り値型が式の型となり、そこから条件演算子の型を決定しようとするため、上記の例ではエラーになります。
これを回避するにはたとえば次のように書くことができます
int x = condition ? 42 : throw (std::terminate(), 0);
これはthrow
式の評価より前にstd::terminate()
が呼ばれることで当初の意図と同様に動作します。
このことは提案中のパターンマッチングにおいても同様であり、throw
式は特別扱いされる一方で[[noreturn]]
関数はその関数の戻り値型が取得されます。
// 理想、動作しない void f(int i) { int j = i match { 0 => 0; _ => std::terminate(); // int と void でmatch式の型が統合されない }; use(j); } // throw式によるワークアラウンド void g(int i) { int j = i match { 0 => 0; _ => throw (std::terminate(), 0); }; use(j); } // do式(提案中)と明示的な型指定によるワークアラウンド void h(int i) { int j = i match { 0 => 0; _ => do -> int { std::terminate(); }; }; use(j); }
この提案は、発散する式をその種類の違いによらず型システムで認識できるようにすることで、このような問題を解決しようとするものです。
一つの方法としては式の持つプロパティに式の発散有無を追加してそれを確認することです。しかし、この方法はスケールせず、規定が複雑になるとともに式の種類が増えた場合に一貫性を持って対応するのが難しくなります(後述のdo
式がまさに該当します)。
この提案では別の解決策として、ボトム型と呼ばれる種類の特別な型を追加しようとしています。
ボトム型とは、まさにこのような発散する式を表すためにほかの言語ですでに使用されている特殊な型です。ボトム型は具体的なオブジェクトを構築することができず、他の任意の型に変換することができます。例えば、任意の型T
に対してT
にもT&
にも変換することができ、このような性質は通常のコードでは再現できません。また、auto(*)(T) -> noreturn_t
はauto(*)(T) -> U
に変換することができます(実際にはC++ではこの変換をいつも許可することはできませんが)。
ここではそれを仮にstd::noreturn_t
として、次の事を提案しています
- 値を持たない式、すなわち無条件に発散する式を表すための新しい型
std::noreturn_t
を追加する std::noreturn_t
は任意の型に変換可能std::noreturn_t
は構築可能ではなく、あらゆる方法で変換・生成できないstd::noreturn_t(*)(Args...)
は任意の型R
に対してR(*)(Args...)
に変換可能- ただし、この変換は変換元のポインタが定数式である場合のみ許可される
- 現在
[[noreturn]] void f()
と指定されている標準ライブラリ関数をstd::noreturn_t f()
に変更する throw
式の型をvoid
からstd::noreturn_t
に変更するthrow
式の条件演算子における例外規定を削除する
これによって、式の発散を型システムで扱えるようになります。
例えば条件演算子において
condition ? 42 : throw std::runtime_error("oops")
この式の2番目のオペランド(42
)の型はint
であり、3番目のオペランドの型はstd::noreturn_t
となります。std::noreturn_t
は任意の型に変換可能であるため、この条件演算子の式の型はint
になります。この過程でthrow
式を特別扱いする必要はありません。
同様に
condition ? 42 : std::terminate()
これも先ほどと同じ推論により式の型はint
になります。
ボトム型はこれ以外にも、いくつか有用な使用法があるかもしれません。
1つは、std::function<std::noreturn_t()>
の様な関数型の宣言と使用が可能になることで、処理が完了しない(正常にリターンしない)コールバックをstd::function
などで扱うことができるようになります。
もう1つはstd::expected<T, std::noreturn_t>
のように使用して、エラーが起こらないことを表すことです(あるいは逆にstd::expected<std::noreturn_t, E>
として必ず失敗することを表現することもできます。これは一見意味がなさそうに見えますが、規約や一貫性のためなどで関数の戻り値型をstd::expected
で統一しているような場合に、必ず成功する/失敗する処理を型によって表現可能になります。
この場合、std::noreturn_t
を指定された特殊化はその値を保持する必要がないためかなり単純な実装による最適化が可能になります。
最後は、value_type
を提供できないValueless Rangesというカテゴリのrange
を定義するのに使用できることです。例えば抽象基底クラス(A
型とすると)による範囲を生成したくなったとき、reference
としてA&
を指定して返すことはできるものの、value_type
としてA
を指定できません。おそらく使用されるまでは問題にならないものの、使用されてしまうと抽象クラスがオブジェクトを構築できないためにエラーになります。range
のvalue_type
は一部のアルゴリズムが要求します。
他にも、空の配列を構造体末尾に持つ可変長構造体のような型などvalue_type
として使用ができないような型をrange
の要素型にすることは現在困難です。特に、必ずしもいつもコンパイルエラーにならないことがあります。std::noreturn_t
をvalue_type
に指定することでこれが解決され、value_type
がないことを表現できるようになるとともに、value_type
を構築しようとすると確実にエラーになります。
さらにこの提案では、P2806で提案中のdo
式においても、同じルールを適用することを提案しています。その際
std::terminate() do { std::terminate(); } do { log::error("hasta la vista"); std::terminate(); }
これらの式のいずれもを発散する式として(型がstd::noreturn_t
になるように)扱うようにすることを提案しています。
現在のdo
式は最終行の分を暗黙のreturn
文にしないためここには特別扱いが必要で、do_return
がなく戻り値型指定もされていない場合にdo
式の最後の文が発散しないかをチェックするようにすることを提案しています。
P3550R0 Imports cannot ...
Cの可変長引数機能をC++で使用できないようにする提案。
名前付きモジュールからはマクロはエクスポートされないため、import std;
しただけではCの可変長引数にアクセスすることができません。
import std; void c_style_variadic_arg_func(int arg1, ...) { ::va_list arg_list; // ok、これはマクロではない va_start(arg_list, arg1); // ng、これはマクロ int arg2 = va_arg(arg_list, int); // ng、これもマクロ va_end(arg_list); // ng、これもマクロ }
これを機能させるには<cstdarg>
もしくは<stdarg.h>
のどちらかのヘッダのインクルードが必要になります。
一方で、Cの可変長引数機能は上記例からも明らかなように、使用するのが難しく間違って使用するのが簡単な機能であり、この機能の使用にはセキュリティリスクがあります。そして、C++には可変引数テンプレートという完全かつ型安全な代替機能があり、もはやCの可変長引数機能を使用する理由はほとんどなくなっています。
この提案は、モジュールコードでCの可変長引数機能を使用できるようにするよりも、この機能のサポートを縮小あるいは削除することを提案するものです。主に次の2つの事を提案しています
- 関数がグローバル名前空間にあり、
extern "C"
リンケージを持たない場合、Cの可変長引数による関数宣言を非推奨とする - C++標準ライブラリから、
<cstdarg>
と<stdarg.h>
ヘッダを削除する
printf()
やscanf()
等の関数は非推奨としてサポートされ続けます。一方で、2つ目の変更によってC++プログラムがCの可変長引数機能を使用する能力が失われ、これによってC++でコンパイルできないCのコードが増加することになります。提案ではこれをC++のセキュリティを確保するためのコストであるとしています。ヘッダを削除せずに非推奨とする穏健なアプローチはセキュリティホールを開いたままにするとして推奨していません。
P3552R0 Add a Coroutine Lazy Type
std::execution
に対応したコルーチンlazy
型の提案。
lazy
型は他言語やライブラリなどではTask<T>
型とよく呼ばれているものに該当します。非同期処理の戻り値型として使用して、lazy<T>
オブジェクトにco_await
することで非同期操作の再開および完了待機を行い、完了したらその結果を取り出して待機元に返します。
これは以前にP2506R0等で提案されていたstd::lazy
と同等のものでもありますが、それらとの違いはstd::execution
(P2300)を考慮していることです。それによって、単にコルーチンとして扱えるだけでなくsender
として扱えることもでき、lazy
コルーチンの内部でsender
とコルーチンをco_await
したりすることができます。
// lazyコルーチン auto f1() -> std::execution::lazy<int>; // senderを返す何か auto f2() -> std::execution::sender { // 例えば return std::execution::just(1); } auto example() -> std::execution::lazy<int> { // これもできるし int r1 = co_await f1(); int r2 = co_await f2(); // これもできる auto [r3] = std::execution::sync_wait(f1()); auto [r4] = std::execution::sync_wait(f2()); co_return 0; } int main() { // senderとして実行 auto [r] = std::execution::sync_wait(example()); return r; }
この提案において、std::execution
を踏まえたコルーチンタスク機能として提供すべきとしている機能は次のものです
- コルーチンタスクは
awaiter/awaitable
フレンドリーでなければならない- ライブラリ提供・ユーザー提供に関わらず、
awaitable
をco_await
できなければならない
- ライブラリ提供・ユーザー提供に関わらず、
sender
の完了シグネチャを書き換えることによって、コルーチンタスクでのsender
利用を容易にする- 3つの完了チャネルをそれぞれコルーチンにおける完了・エラー・中断に翻訳する
- コルーチンタスクは
sender
フレンドリーでなければならない- 非同期コードは今後、
sender
を使用して書かれco_await
を使用して待機されることが多くなると思われる - 現在の実装(unifex/stdexecなど)では、複数の
set_value
完了シグネチャを持つsender
のco_await
をサポートしていない
- 非同期コードは今後、
- コルーチンタスクはデフォルトでスケジューラアフィニティを持つべき
- コルーチンは通常、中断したコンテキストと異なるコンテキストで再開されると問題を起こしやすい
- コルーチンタスクは、中断したスケジューラと同じスケジューラ(実行コンテキスト)で再開する
- コルーチンタスクはアロケータサポートを提供する必要がある
- コルーチンを利用する場合、少なくともコルーチンフレーム分のメモリ確保が必要になり、それを回避する最適化は常に利用可能であるわけではない
new/delete
がサポートされていない環境でも使用可能にするために、アロケータによってメモリ確保方法をカスタマイズできる必要がある
- 子操作においてユーザーがクエリ可能な環境を提供する
- コルーチンタスク内で
sender
をco_await
する場合、そこで接続されるreceiver
を介して環境を提供する - この環境は例えば、子操作に停止要求を転送したり、子操作が
get_scheduler()
をクエリする際にそれをカスタムしたりするのに使用できる
- コルーチンタスク内で
- コルーチンタスクはキャンセルされたことを表現できるべき
- 子操作に提供した
receiver
に対してset_stopped()
が呼ばれたことを親操作のコルーチン状態として表現できる
- 子操作に提供した
- 例外送出を伴わずに、内部のエラー発生を表現できると良い
- 例外送出によるコルーチンからの脱出を表現するために、コルーチンタスクの完了シグネチャには
set_error_t(std::exception_ptr)
を含める- コルーチンタスクに対してコルーチンが例外を送出しないことを表明できれば、これを回避することもできる
- 多数の子操作の
co_await
によって発生しうるスタックオーバーフローを防止する- 適切なスケジューラを用いてスタックオーバーフローを防止する
- コルーチン終了時の非同期クリーンアップを何かしらの方法でスケジュールできると有用な場合がある
- 標準
lazy
コルーチンは、ユーザーのニーズを100%満たすことを目指さない- ユーザーがその特定のニーズに沿う
lazy/task
型を実装する場合でも有用な汎用のコンポーネントを定義・使用する
- ユーザーがその特定のニーズに沿う
例えば、子操作sender
に対して環境カスタマイズを提供するために、std::lazy
は2つ目のテンプレートパラメータで環境についてのカスタマイズを行えるようにすることが提案されています。
namespace std::execution { template<typename T = void, typename Context = ...> class lazy { ... }; }
2つ目のコンテキストパラメータによって例えば
- 子操作の環境カスタマイズ
- スケジューラアフィニティを無効化する
- アロケータをカスタマイズする
- コルーチンが
noexcept
であることを指定する - 追加のエラー型を指定する
などのカスタマイズを行うことができます。
例えばアロケータをカスタマイズする例
using ex = std::execution; struct allocator_aware_context { using allocator_type = std::pmr::polymorphic_allocator<std::byte>; }; template <typename...A> auto fun(int value, A&&...) -> ex::lazy<int, allocator_aware_context> { // 環境を介して渡されたアロケータを取得できる auto alloc = co_await ex::read_env(ex::get_allocator); // co_await内で用意され接続されるreceiverを介して、`lazy<T, C>`の`C`の指定する環境にアクセスできる use(alloc); co_return value; } int main() { // アロケータを渡さずに実行 ex::sync_wait(fun(17)); // アロケータを渡して実行 using allocator_type = std::pmr::polymorphic_allocator<std::byte>; ex::sync_wait(fun(17, std::allocator_arg, allocator_type())); }
この例に示されているように、コルーチンタスクの環境はlazy
の第2テンプレートパラメータで指定されたコンテキストをベースとして、通常のstd::execution
のプロトコルに従った形でセット/クエリを行うことができます
struct context { int value{}; context(auto const& env) : value(get_value(env)) {} int query(get_value_t const&) const noexcept { return this->value; } }; auto f() -> ex::lazy<void, context> { auto sched(co_await ex::read_env(get_scheduler)); auto value(co_await ex::read_env(get_value)); std::cout << "value=" << value << "\n"; // ... } int main() { ex::sync_wait( ex::write_env( f(), ex::make_env(get_value, 42) ) ); }
この例では、context
で指定される環境に対してさらに書き込みを行った環境を、lazy
コルーチン中からクエリして取得しています。
スケジューラアフィニティを実現するためのスケジューラの取得もこの環境クエリによって行われます。具体的には、lazy
コルーチン本体内でsender
がco_await
されるときに、使用するreceiver
の環境(これはコンテキストパラメータの指定が反映されている)から通常のget_scheduler(get_env(rcvr))
クエリによってスケジューラを取得し、待機されたコルーチンの再開時(ネストコルーチンの完了時)にawait_transform()
でcontinues_on()
することで元のコンテキストのスケジューラを復帰します。
// await_transform() でのスケジューラ復帰の例 template <ex::sender Sender> auto await_transform(Sender&& sndr) noexcept { return ex::as_awaitable_sender( ex::continues_on(std::forward<Sender>(sndr), this->scheduler); ); }
await_transform()
はプロミス型で実装され、コルーチン中断時(ネストコルーチンのco_await
時)に取得したスケジューラはプロミス型で保持されます。その型はコンテキストパラメータC
からC::scheduler_type
で指定されるか、デフォルトではany_scheduler
(型消去スケジューラ型)が使用されます。
lazy
コルーチン内でsender
をco_await
する場合(co_await sndr
)、sender
の各チャネルは自動的にコルーチンの完了動作にマッピングされます。これはプロミス型のawait_transform()
内でas_awaitable(sndr)
(これは既存のライブラリ機能)を用いて行われ、そこでは接続されているレシーバーrcvr
を用いて次のようにマッピングされます
set_stopped(std::move(rcvr))
(キャンセル)- プロミス型の
.unhandled_stopped()
が呼び出され、待機中コルーチン(親コルーチン)は再開されない lazy
自身(親コルーチン)もset_stopped_t()
で完了する
- プロミス型の
set_error(std::move(rcvr), error)
(エラー)- 待機中コルーチンは再開され、
co_await sndr
式は例外としてerror
を送出する
- 待機中コルーチンは再開され、
set_value(std::move(rcvr), a...)
(完了)co_await sndr
式は次のいずれかの結果を生成するset_value()
の引数リスト(a...
)が空の場合、結果はvoid
- 引数リストが1要素のみの場合、結果は
a
- それ以外の場合、結果は
std::tuple(a...)
なお、lazy
コルーチンではco_yield
はサポートされないか、エラー報告にのみ使用される予定です。
set_value_t
完了シグネチャにはある程度の自由度があり、持たない場合と複数持つ場合があり得ます。持たない場合、待機対象のsender
がset_stopped()/set_error()
のどちらかで完了することになるため、co_await sndr
の結果はそのまま中断し再開されないか例外を送出するかのどちらかです。
// lazyコルーチン内でsenderをco_awaitする例 auto fun() -> lazy<> { co_await ex::just(); // void auto v = co_await ex::just(0); // int auto[i, b, c] = co_await ex::just(0, true, 'c'); // tuple<int, bool, char> try { co_await ex::just_error(0); } catch (int) {} // exception co_await ex::just_stopped(); // cancel: never resumed }
set_value_t
完了シグネチャを複数持つ場合、as_awaitable(sndr)
がエラーになることでそのようなco_await
式もエラーになります。この場合、co_await into_variant(sndr);
のようにして複数のset_value_t
完了シグネチャを単一に集約することでサポートされます。
並行キューのpop
操作の例(キューが閉じていなければ取り出した値を、キューが閉じられていればvoid
で完了する)
auto pop_demo(auto& queue) -> lazy<>{ // auto value = co_await queue.async_pop(); // doesn't work std::optional v0 = co_await (queue.async_pop() | into_optional); std::optional v1 = co_await into_optional(queue.async_pop()); }
into_optional()
の場合、set_value_t
完了シグネチャのうち値を持つものを有効値に(完了値が複数の場合はstd::optional<std::tuple<T...>>
型)、値を持たないものを無効値にマッピングします。
他の類似物としてはinto_expected
を候補に挙げています。ユースケースに応じてこれらをユーザーが適宜使い分けることで場所によって使いやすい完了シグネチャを選択できることを目的としており、この動作をas_awaitable()
でハードコードしてしまうことを避けています。
環境からストップトークンを取得できるため、コルーチンタスク(及びネストしたsender
操作)はそのトークンに応じて適応的にキャンセルを行える必要があります。この場合はset_stopped()
で完了すればよいため、co_await ex::just_stopped();
で自然にコルーチンをキャンセル(し、呼出し元に処理を戻す)できます。
ネストしたsender
がset_error
で完了した場合、あるいは直接的に例外が送出された場合、コルーチンの境界で例外はキャッチされset_error_t(std::exception_ptr)
の完了にマッピングされます。これはプロミス型のunhandled_exception()
で行われます。
この方法には
- サポートされるエラー完了型が
set_error_t(std::exception_ptr)
のみ- 他のエラー報告方法もサポートされるのが望ましい
- エラー報告のために例外を送出しなければならない
- 例外が無効化されている場合、エラー報告の手段がなくなる
std::exception_ptr
からエラー情報を取得するためには再スローしなければならない- 例外が利用できない場合に
set_error_t(std::exception_ptr)
がコンパイルエラーとなる可能性がある
これらの問題に対処するために、まずエラー完了シグネチャをコンテキストパラメータC
を通してカスタマイズできるようにします。C::error_signatures
型が宣言されている場合(この型はset_error_t<E>
のリストとなるcompletion_signatures<>
の特殊化である必要がある)、それをエラー完了型として使用するようにします。
そのうえで、コルーチン本体からエラー報告するためにまず、エラーを識別するためのwith_error<E>
クラステンプレートを用意して
// 例えばこんな単純な実装で良い template <typename E> struct with_error{ E error; };
コルーチンタスクから明示的にエラーを返すための構文として次の3つを提示しています
co_return with_error{err};
co_return
するとコルーチンはそこで終了し、オプションとして引数を渡すこともできる- プロミス型の
return_value()
内でset_error()
にマッピングする
- プロミス型の
lazy<void, C>
(lazy<>
)の場合に使用できない
co_await with_error{err};
awaiter
のawait_suspend()
が呼び出された時点でコルーチンは待機状態となり、安全に破棄可能になる。これはset_stopped()
のマッピングと同じ仕組みを用いている- コルーチンからエラーを返すのと、ネストしたコルーチン/
sender
の完了を待機するのが同じ構文になってしまう
co_yield with_error{err};
- プロミス型の
.yield_value()
に転送され、そこからawaiter
を返すことができる awaiter
のawait_suspend()
の呼び出しによってコルーチンは中断され、そこでset_error
にマッピング可能- エラーでコルーチンを完了させる目的で
co_yield
を使用するのはco_await
よりも適している
- プロミス型の
この提案ではどれを選択してもいませんが、3の方法を推しているようです。
このように、このlazy
型は以前のものに比較するとかなりstd::execution
との親和性が向上していることが分かります。ただし、今の時点では具体的な決定はまだ先ではあるので、設計は変化する可能性があります。
この機能(std::lazy
)は後のリビジョンでstd::task
となり、C++26に採択されたようです。
P3554R0 Non-transient allocation with vector and basic_string
std::string
とstd::vector
に限定した、非一時的なメモリ割り当てを許可する提案。
現在、定数式で動的確保したメモリ領域は同じ定数式の中で解放しなければなりません。これによって、定数式で使用したstd::vector
やstd::string
を実行時に持ち越すことはできなくなっています。これは現在でも不便ですが、C++26でリフレクションが利用可能になるとさらに不便さが増大する事が予想されます。
定数式で確保したメモリ領域を実行時に持ち越せるようにする(Non-transient allocation: 非一時的なメモリ割り当て)事についてもC++20時点から議論されているものの、合意を得られた一般的な設計はまだ存在していません。この提案では、一般的な設計以前にとにかくstd::string
とstd::vector
だけをその制限から解放して、定数式で使用したstd::string
/std::vector
オブジェクトをそのまま実行時でも利用できるようにしようとする提案です。
提案ではまず、次の条件を全て満たす場合のコア定数式E
の評価中に発生するメモリ割り当てA
をconstexpr-persistentとして定義します
E
は定数式で使用可能なオブジェクトO
の初期化するために使用されているA
はE
の評価内で解放されないA
がeligible for constexpr-persistentである
そのうえで、定数式におけるnew
/std::allocator<T>::allocate()
の規定にある「定数式の評価中に確保した領域はその評価の内部で解放されなければならない」という規定を「定数式の評価中に確保した領域は、それがconstexpr-persistentであるか、(さもなければ)その評価の内部で解放されなければならない」のように緩和して指定しなおします。
そして、std::vector
(特殊化)およびstd::string
の要素のために割り当てたメモリ領域をeligible for constexpr-persistentである、と規定します。
これにより、std::string
/std::vector
が定数式にで割り当てメモリ領域はeligible for constexpr-persistentとなり、eligible for constexpr-persistentであるような割り当てはconstexpr-persistentであるためその定数式の評価内で解放されている必要は無くなります。
そして、定数式で使用したstd::string
/std::vector
オブジェクトは、追加の操作なくそのまま実行時に引き継ぐことができます。
将来より一般的な非一時的なメモリ割り当ての方法が導入された時でも、std::string
/std::vector
は間違いなくその対象になることが分かっています。しかし、それを待っているといつになるか分からないため、細かい規定を省略しつつstd::string
/std::vector
だけはとにかく定数式から実行時に引き継ぐことができるようにしようとしています。
P3555R0 An infinite range concept
終端の無い範囲を検出するためのコンセプトの提案。
std::views::iota(1)
のように、終端の無い無限の範囲は割と簡単に得ることができます。このような範囲であってもRangeアダプタで使用することができるのですが、長さが無限であるという性質によって一部のアダプタがうまく動作しないことがあります。
auto a = views::iota(0) | views::reverse; a.begin(); // 無限ループ
views::iota(0)
はcommon_range
ではないため番兵から逆に進行することができず、views::reverse
によるview
からのイテレータ取得時(begin()
呼び出し時)に先頭から終端までイテレータを進行させることによって最後の要素を検出しようとします。しかし、views::iota(0)
は無限の範囲なのでそれは無限ループします。
このケースでは番兵型がunreachable_sentinel_t
であることを検出することで回避可能ですが、これが別のアダプタで隠蔽されていると結局同じ問題が発生します(views::zip(views::iota(0), views::iota(1))
など)。この場合に、範囲の無限性を検出することができれば、このようなバグを静的に回避することができます。
範囲の無限性を検出することのもう一つのモチベーションは、P3179R4で提案されている並列アルゴリズムによるバイナリtransform
における入力範囲長の自由度の確保があります。バイナリtransform
はranges::transform(execution::par, rng1, rng2, output, fn)
の様に2本の範囲を入力として使用するtransform
であり、この場合に両方の入力範囲がsized_range
であることを要求します。これは安全のためとのことですが、どちらかの範囲に整数インデックス(すなわちviews::iota(1)
)や定数列を渡して、もう片方の入力データによって入力長を指定する、の様なユースケースが存在しているため、この仕様は少し物議を醸しています。
この場合にも、範囲の無限性を事前に検出できれば、安全に入力範囲の長さを計算することができるようになります。
最後に、範囲の無限性を検出できればinfinite_range | views::take(n)
のような式をinfinite_range | views::unchecked_take(n)
(P3230R1で提案されているもの)に最適化するようなことが簡単にできるようになります(take
される長さn
よりも入力範囲が長いことが分かるため)。
これらのモチベーションより、この提案では範囲の性質としての無限性を検出するためのinfinite_range
コンセプトを提案しています。
この提案では、Range-v3で定義されている範囲の濃度の概念を簡易化したものによって、infinite_range
を定義づけています。
- 既知の有限長:
sized_range
- 無限長:
infinite_range
- 不明:
!sized_range
かつ!infinite_range
濃度のモデルが簡略化されていることで!infinite_range
は必ずしも有限長を意味しなくなっています(有限長 or 検出できなかった無限長、を表す)。一方で、infinite_range
が満たされている場合はその範囲は確実に無限長であることを表しています。
範囲の無限性は必ずしも確実に検出できないので、infinite_range
には何かしらのオプトインが必要になります。提案ではその2つのオプションを提示しています
enable_infinite_range
enable_borrowed_range
と同様の方法
size()
が特定のタグ型を返す- 無限範囲は
sized_range
ではないので整数値を返さなくても問題ない
- 無限範囲は
1の方法の例
namespace std::ranges { template <class R> constexpr bool enable_infinite_range = same_as<sentinel_t<R>, unreachable_sentinel_t>; template <class R> concept infinite_range = range<R> && enable_infinite_range<remove_cvref_t<R>>; } // ユーザーコードで template <> constexpr bool std::ranges::enable_infinite_range<my_infinite_range> = true; template <class R> constexpr bool std::ranges::enable_infinite_range<my_range_adaptor_that_does_not_affect_cardinality<R>> = std::ranges::infinite_range<R>;
2の方法の例
namespace std { // or std::ranges struct infinite_tag_t {}; inline constexpr infinite_tag_t infinite_tag; } namespace std::ranges { template <class R> concept infinite_range = range<R> && ( same_as<seninel_t<R>, unreachable_sentinel_t> || requires(R&& r) { { r.size() } -> same_as<infinite_tag_t>; } || requires(R&& r) { { /*ADL*/size(r) } -> same_as<infinite_tag_t>; } ); } // ユーザーコードで class my_infinite_range { public: auto size() const { return std::infinite_tag; } }; template <class R> class my_range_adaptor_that_does_not_affect_cardinality { R _r; public: auto size() const requires std::ranges::sized_range<R> { return std::ranges::size(_r); } auto size() const requires std::ranges::infinite_range<R> { return std::infinite_tag; } };
2の方法の方が、view
型のように他の範囲をラップする場合にこの性質を伝播させやすいというメリットがあるため、ここでは2の方法を押しています。
namespace detail { auto potentially_infinite_size(std::ranges::sized_range auto&& r) { return std::ranges::size(r); } auto potentially_infinite_size(std::ranges::infinite_range auto&&) { return std::infinite_tag; } template <class R> concept enable_size = requires(R&& r) { detail::potentially_infinite_size(r); }; } template <class R> class my_range_adaptor_that_does_not_affect_cardinality { R _r; public: // size()をこのように定義することで伝播を自動化できる auto size() const requires detail::enable_size<R> { return detail::potentially_infinite_size(_r); } };
ちなみにどの方法であっても、その番兵型がunreachable_sentinel_t
であるような範囲は自動的に無限長であると識別されます。
現在のアダプタの中だと、次のものは基底の範囲がinfinite_range
ならばinfinite_range
になるようにオプトインが必要であるとしています
ref_view
owning_view
as_rvalue_view
transform_view
drop_view
lazy_split_view
split_view
common_view
reverse_view
as_const_view
elements_view
enumerate_view
adjacent_view
adjacent_transform_view
chunk_view
slide_view
chunk_by_view
stride_view
cache_latest_view
そして、次のものに関しては濃度が不明であるか確実に有限であるため、変更は不要としています
filter_view
(unknown cardinality)take_view
(finite)take_while_view
(unknown cardinality)drop_while_view
(unknown cardinality)
残ったもののうち
join_view
/join_with_view
- 入力範囲の外側の範囲が
infinite_range
ならばinfinite_range
になるようにする
- 入力範囲の外側の範囲が
concat_view
- 少なくとも一つの範囲が
infinite_range
ならばinfinite_range
になるようにする
- 少なくとも一つの範囲が
zip_view
/zip_transform_view
- 全ての範囲が
infinite_range
ならばinfinite_range
になるようにする
- 全ての範囲が
とすることを提案しています。なお、言及されていないものは変更を提案していません。
P3557R0 High-Quality Sender Diagnostics with Constexpr Exceptions
std::execution
における、エラーメッセージ出力を改善する提案。
P3164では、sender
がreceiver
と接続されるよりも前、sender
による処理のチェーンが組まれた段階でsender
同士の接続関係についての型チェックが可能なタイプのsender
(非依存型のsender
)についてより早期の型チェックとエラー報告を提案していました。
この提案はそれに続いてstd::execution
におけるエラー報告を改善しようとするもので、sender
の完了シグネチャ(completion signatures、3つのチャネルが返す型)の計算における型エラーを定数式における例外を用いて報告するようにするものです。
sender
は標準のsender
ファクトリ/アダプタが返すもの以外にも、ユーザーが自由に定義することができ、ユーザー定義sender
が標準の要求するsender
の基準に達していない場合、その使用時に様々なエラーが発生します。この提案によれば、そのようなエラーの中で最も長大かつ意味不明なエラーが発生しやすいのはsender
の完了シグネチャの計算時であるとのことで、そこでのエラーをC++26で可能になった定数式における例外送出を用いて直接的にユーザーに報告するようにしようとしています。
1. get_completion_signatures()
をCPOからconsteval
関数テンプレートへ変更
P2300では、完了シグネチャはget_completion_signatures(sndr, env)
の型(関数呼び出し式の型)によってのみ決定され、関数呼び出しそのものは重要ではなく式は評価されません。
定数式において例外によってエラー報告するために、この式を定数式において実行し、その内部で完了シグネチャ計算とエラー報告を行うように変更します。そのため、get_completion_signatures<Sndr, Env...>()
の様な呼び出しに変更し、この呼び出し式を定数式で実行します。
これによるget_completion_signatures()
の定義の変更は次のようになります
現在 | この提案 |
---|---|
inline constexpr struct get_completion_signatures_t { template <class Sndr, class... Env> auto operator()(Sndr&&, Env&&...) const -> see below; } get_completion_signatures {}; |
template <class Sndr, class... Env> consteval auto get_completion_signatures() -> valid-completion-signatures auto; |
1.2. get_completion_signatures()
カスタマイズ方法の変更
get_completion_signatures()
はユーザー定義sender
のメンバ関数としてのカスタマイズが可能でしたが上記変更に伴ってカスタマイズ方法を変更します。現在this
であるsender
と環境env
を受け取る非static
メンバ関数であるところを、それらの型をテンプレートパラメータで受け取るstatic
メンバ関数に変更します。
現在 | この提案 |
---|---|
struct my_sender { template <class Self, class... Env> requires some-predicate<Self, Env...> auto get_completion_signatures(this Self&&, Env&&) { return completion_signatures</* … */>(); } ... }; |
struct my_sender { template <class Self, class... Env> static constexpr auto get_completion_signatures() { if constexpr (!some-predicate<Self, Env...>) { throw a-helpful-diagnostic(); // <--- LOOK! } return completion_signatures</* … */>(); } ... }; |
2. sender_in
コンセプトにおいて、get_completion_signatures()
が定数式であることをチェックする
上記の変更を反映するために、sender_in
コンセプト(sender_in<sndr, env>
は環境env
においてsender
であるsndr
が非同期操作を構成できることを表す)を調整します。
具体的には、get_completion_signatures<sndr, env>()
が定数式で評価可能であるかどうかをチェックするようにします
現在 | この提案 |
---|---|
template<class Sndr, class... Env> concept sender_in = sender<Sndr> && (sizeof...(Env) <= 1) (queryable<Env> &&...) && requires (Sndr&& sndr, Env&&... env) { { get_completion_signatures(std::forward<Sndr>(sndr), std::forward<Env>(env)...) } -> valid-completion-signatures; }; |
template <auto> concept is-constant = true; // exposition only template<class Sndr, class... Env> concept sender_in = sender<Sndr> && (sizeof...(Env) <= 1) (queryable<Env> &&...) && is-constant<get_completion_signatures<Sndr, Env...>()>; |
3. basic-sender
クラステンプレートにおいて、get_completion_signatures()
がenv
引数無しで呼ばれた場合にエラーとする条件を指定する
basic-sender
クラステンプレートは標準のsender
アルゴリズムの規定のために使用されているクラステンプレートです。上記の変更においてこのクラスのget_completion_signatures()
が例外を送出する場合を指定するために、basic-sender::get_completion_signatures<Sndr>()
がill-formedとなる条件を明示的に指定するようにします。
4. 依存型sender
を定義するdependent_sender
コンセプトを追加する
ユーザーがsender
を作成する場合、P3164がそうしているように非依存型sender
に対して早期の型チェックを行う事を簡易にサポートするために、非依存型sender
を識別するために、依存型sender
を表すコンセプトdependent_sender
を追加します。
namespace std::execution { template<class Sndr> consteval bool is-dependent-sender-helper() try { // exposition only get_completion_signatures<Sndr>(); return false; } catch (dependent-sender-error&) { return true; } template<class Sndr> concept dependent_sender = sender<Sndr> && bool_constant<is-dependent-sender-helper<Sndr>()>::value; }
依存型sender
はenv
がなければ完了シグネチャを求めることができないので、get_completion_signatures<Sndr>()
は例外を送出するようになります。dependent-sender-error
は有効な依存型sender
が単にenv
が欠けているために例外を送出していることを表す専用の例外型であり(これによって、型エラーを起こしている非依存型sender
特別できる)、get_completion_signatures<Sndr>()
がこの例外を送出している場合はSndr
が依存型sender
であると識別することができます。
5. transform_completion_signatures
コンセプトを削除する
これはオプショナルだとされています。
transform_completion_signatures
は完了シグネチャの型変換を簡易化するためのユーティリティメタ関数ですが、この提案の変更に伴ってこれはstd::apply
やif constexpr
+ラムダ式などで代替が容易になるため、不要になるとして削除を提案しています。
これらの変更(1~4まで)はメインの提案ですが、このほかにいくつかオプションの提案も提示されています。
- P3164R2 Improving diagnostics for sender expressions - WG21月次提案文書を眺める(2024年07月)
std::execution::completion_signatures
- cpprefjpstd::execution::get_completion_signatures
- cpprefjp- P3557 進行状況
P3558R0 Core Language Contracts By Default
未定義動作のContractsによるハンドリングを将来のC++のデフォルトにする提案。
この提案はP3100R1で提示された未定義動作を契約違反として捉えることで契約プログラミング機能の枠組みで扱うようにする方向性を推進し、さらにそれを将来のC++におけるデフォルトとすることを提案しています。
P3100については以前の記事を参照
このメリットとしては次のことが挙げられています
- C++エコシステムはどんなプラットフォームでもそのまま安全に使用できる
- 経験の浅い開発者や初学者が、複雑なコンパイラスイッチを学習することなく間違いが診断される
- ユーザーの学習サイクルおよびソフトウェアのライフサイクルの早い段階で、様々な欠陥が明らかになる
- このデフォルトの言語安全性は、必要に応じて無効化することもできる
- パフォーマンスが重要なプログラムにおいても、それが求められない場所で簡単にチェックを有効化できる
- デフォルトでチェックを有効化されていれば、何もしなくてもバグを検出できる
- バグがあるか分からない状況においては、追加のチェックを有効化するべき指示がない
- デフォルトで有効化されていれば、その指示や判断無しにバグが検出される
- 逆に、デフォルトで有効化されている場合、実行時パフォーマンスの低下という明確な証拠が示されることで無効化判断を行うことができる
- コードの変更が必要ない
- 違反ハンドラを活用できる
- (実装がサポートしていれば)違反ハンドラの差し替えによってwell-definedなフォールバックを動作を指定できるため、ある実行時点でバグが検出されず修正されない場合でもリスクを軽減できる
- ソースコードの変更を必要とせず、erroneous behaviorの恩恵を受けることができる
- このEBは言語仕様において指定する必要がない
提案では懸念点についても考慮されています
- 実行時検査のオーバーヘッド
- コア言語の事前条件をチェックするオーバーヘッドは一様ではなく、条件によって異なる
- コンパイラで行われた/進行中の様々な実験によって、そのようなチェックのほとんどは典型的なプログラムの有用性に影響を与えるほどのものではない
- チェックを現在のassumeセマンティクスに切り替える必要性の程度については実装経験によってのみ明らかになる
- 例外を送出しうる違反ハンドラ
- 違反ハンドラが例外を送出できることによって、任意の契約注釈から例外送出される可能性が発生してしまい、コードを悪い状態にする可能性がある事が懸念されている
- ただ、P3100R1のチェックで対処されない場合、その現在の状態は未定義動作であり、さらに悪い状態である
- 多くの種類の未定義動作において実際に観測される動作範囲は実際には合理的に限定されており、未定義動作の結果として例外を送出することはまずありえない
- そのため、例外を送出する状況は未定義動作よりも悪い状態ではない
- 多くの種類の未定義動作において実際に観測される動作範囲は実際には合理的に限定されており、未定義動作の結果として例外を送出することはまずありえない
- 既存のコードが、nullptrデリファレンスからのコアダンプといったような未定義動作の特定の振る舞いに依存している可能性も指摘されており、このような動作を保証されたもの(あるいはそのように)のように扱うのは賢明ではないものの、ハイラムの法則からそのような動作の変更はある程度真剣に検討する必要がある
- しかし一方で、この提案のデフォルト動作はまさにそのような動作を提供するものになる。例外を送出する違反ハンドラを選択するのは専門家ユーザーであり、そのようなユーザーこそがこのような違反ハンドラの柔軟性を最も必要としている
- 例外処理のオーバーヘッド
これらの事を踏まえて、ここでは次の3つの事を提案しています
- プロファイル提案から、実行時チェックに関する機能を削除する
- P3100R1の標準化を推進する
- P3100R1の完全な仕様策定を継続し、P2900R13の後にこれを標準化する
- enforceセマンティクスの推奨
- P3100R1では、コア言語の事前条件について他の指定が無い場合、デフォルトの評価セマンティクスとしてenforceを推奨する
対象にしているものが今日の未定義動作であるので、実際のところP3100及びこの提案の内容についてはすぐにでも作業を始めることができます。ひとまず必要な作業としては、プロファイル提案のように重複や衝突がある提案においてこのことを考慮するようにすることです。
P3559R0 Trivial relocation: One trait or two?
P2786の提案するis_trivially_relocatable
型特性の意味について反対する文書。
C++26に向けて、リロケーションという操作をサポートするための議論が進行しています。そのために、コア言語にてリロケーションという操作と型がリロケーション可能な場合の条件等を定義しようとしています。特に、トリビアルリロケーションというリロケーションの特別な場合の操作に重点が置かれています。
リロケーションとは、クラスオブジェクトのビット列を単にコピーすることによってオブジェクトの移動を行うことであり、それはムーブ+元オブジェクトの破棄と同等の操作として定義されます。トリビアルリロケーションはさらに、単なるmemcpy
のみによってリロケーションをすることを言います。どちらの場合でも、元のオブジェクトにもうアクセスしないような場合(例えばstd::vector
のキャパシティ伸長時など)に使用することで、ムーブよりもさらに効率的なオブジェクト移動を行うことができます。
C++26に向けたリロケーションの本命の提案はP2786R11であり、ここでは型がリロケーション可能かどうかを検出する型特性として2つのものが提案されています。
std::is_replaceable_v<T>
:T
のムーブ代入は、ムーブ構築+ムーブ元オブジェクトの破棄、によって行えるstd::is_trivially_relocatable_v<T>
: 上記操作はmemcpy
と同等である
一方で、P2786よりも前に最初にリロケーションを提案していたP1144では、同じ名前の型特性std::is_trivially_relocatable_v<T>
を提供しているものの、その意味はT
のムーブ代入がmemcpy
と同等であること、を表しています。すなわち、P1144のstd::is_trivially_relocatable_v<T>
はP2786のそれと異なる意味を持っており、std::is_replaceable_v<T> && std::is_trivially_relocatable_v<T>
の両方を満たして初めて同等の意味になります(P2786では、std::is_trivially_relocatable_v<T>
ならばstd::is_replaceable_v<T>
であるとは限らない)。
リロケーションの提案としてはP1144の方がかなり先行して公開(2018年)されていたため、リロケーションという最適化を独自に先んじて利用していた(当然未定義動作の危険性と隣り合わせですが)在野のライブラリはこのP1144のセマンティクスに基づく形で実装されている場合が多く、そこではライブラリ独自のis_trivially_relocatable_v
を定義し、標準で利用可能になったらstd::is_trivially_relocatable_v
に切り替えるような実装が既に行われています。
// 定義側 namespace lib { // Define a trait for property P, // by the name is_trivially_relocatable. template <class T> struct is_trivially_relocatable : #if __cpp_lib_trivially_relocatable // P1144 std::is_trivially_relocatable<T> {}; #else std::is_trivially_copyable<T> {}; #endif } ... // 利用側 if constexpr (lib::is_trivially_relocatable<T>::value) { ~~~~ memcpy ~~~~ }
このような既存先行実装のis_trivially_relocatable
のセマンティクスはP2786のものと異なっているため、このようなコードはP2786が今のまま採択された後ではその2つの型特性を用いて書き直す必要があります。
// 定義側 namespace lib { // Define a trait for property P, // by the name is_trivially_relocatable. template <class T> struct is_trivially_relocatable : #if __cplusplus >= 20XXYYL // P2786 // Unfortunately the STL's version is weaker than ours std::bool_constant<std::is_trivially_relocatable_v<T> && std::is_replaceable_v<T>> {}; #else std::is_trivially_copyable<T> {}; #endif } ... // 利用側 if constexpr (lib::is_trivially_relocatable<T>::value) { ~~~~ memcpy ~~~~ }
あるいは利用側で2つの型特性を満たすように書き換えてもいいですが、これを行わないと異なる意味において型のトリビアルリロケーション可能性を判定してしまう可能性があり、それは未定義動作となるため対応されない場合既存の実装はそのままバグを抱えることになります。
そして、P2786のis_trivially_relocatable
のセマンティクスは既存のライブラリが仮定するP1144のものとは異なっているため、このような対応は必須の作業となります。
この提案は、これらの問題からトリビアルリロケーションの型特性について、P2786のものではなくP1144のものを採用する事を主張するものです。
このことが問題となる既存のライブラリのほとんどはP1144の提供するis_trivially_relocatable
のセマンティクスに基づいているため、P1144の型特性を採用すればこれらの問題はほぼ回避されます。
また、既存のライブラリ実装を見ても、P2786のようなセマンティクスでis_trivially_relocatable
を定義しているライブラリはわずかであり、is_replaceable
に至っては実装例がありません。このことからも、P1144のis_trivially_relocatable
のセマンティクスが一般に望まれているものだと言えます。
P3560R0 Error Handling in Reflection
静的リフレクション機能におけるエラーハンドリング方法として例外を採用すべきとする提案。
C++26を目指してP2996で提案中の静的リフレクション機能においては、ライブラリのconsteval
メタ関数がエラーを報告する手段を決定する必要性が指摘されています。その方法として次の4つを提示していました
- 無効なリフレクション(
std::meta::info
)を返す - 固有のエラー型
E
に対して、std::expected<T, E>
を返す - 定数式ではなくする
E
の例外を送出する
1は全てのメタ関数がstd::meta::info
を返すわけではない(std::vector<std::meta::info>
やstring_view
を返すものがある)ため問題の解決にならず、2はハンドリングのためのコードの構文上の負担が大きいため最適ではなく、3はエラーから復帰するための方法が提供されない、といった難点があります。結局検討の末、定数式において例外を送出するのが最適であるとされました。
この決定がなされた当時(P2996R1)はまだ定数式で例外送出は許可されていなかったためP2996では3を採用していました。しかし、P3086R6がWDに採択されたことでそれが可能になったため、この提案はオプション4(例外)をリフレクションにおけるエラー報告メカニズムとして採用することを提案しています。
例外によってリフレクションのエラーを報告するようにする場合、メタ関数の戻り値型はそのままにしておくことができます。また、std::expected<T, E>
のように案ラップにユーザーの手間をかける必要はなく、catch
することでハンドリングすることができます。
実行時においてはバイナリサイズ増大やパフォーマンス低下のデメリットから例外は敬遠されがちですが、コンパイル時に使用する場合は例外のデメリットはほぼ関係なくなり、その他の方法と比較してメリットが勝ります。
提案では、リフレクション専用の例外型として次のような型を提案しています
namespace std::meta { class exception { public: consteval exception(u8string_view what, info from, source_location where = source_location::current()); // エラーに関する文字列 consteval u8string_view what() const; // エラーが発生した関数/関数テンプレートのリフレクション consteval info from() const; // エラーが発生した関数の呼び出し位置 consteval source_location where() const; }; }
コンパイル時にリフレクションのためのエラー報告として使用され、実行時に使用されないことから、この例外型はstd::exception
の派生クラスではなく、その必要がありません。
エラー文字列としてu8string_view
を使用しているのは、ソースコードで使用されうる識別子をすべて表現するためにエンコーディングとしてUTF-8を使用し、なおかつそれを型システムで表現するためです。ただし、char8_t
型のライブラリサポートが万全ではないため、これによってエラーメッセージ文字列の生成方法が難しくなるという欠点もあります。
しかし、std::string_view
を使用するようにする場合、識別子名の表現可能性によって別の問題が生じてしまいます。例えばstd::meta::identifier_of(x)
の実装において例外を使用するようにし、std::meta::exception
がstring_view
しかサポートしていないとすると
consteval string_view identifier_of(info x) { // xは識別子を反映していない if( !has_identifier(x) ) { throw meta::exception(u8"entity has no identifier", ^^identifier_of, ...); } auto id = u8identifier_of(x); // 識別子がリテラルエンコーディングで表現可能ではない if( !is_representable(id) ) { throw meta::exception(u8"identifier '"s + id + u8"'is not representable", ^^identifier_of, ...); } // convert id to the literal encoding and return it }
この時、2つ目のエラーケースにおいてエラーメッセージにエラーを起こした識別子を入れようとするのは自然ですが、そもそもそれができないからエラーを報告しようとしているため、これは結局不可能です。
標準ライブラリがchar8_t
をサポートしていないという問題はサポートするようにすれば解決できるため、u8string_view
を採用するのが現在のP2996R8の設計と一貫していて最適だとしています。
提案文書より、クラス型のリフレクションのみを受け入れるようにする例
consteval auto user_fn(info type, source_location where = source_location::current()) { if( !is_class_type(type) ) { throw std::meta::exception(u8"not a class type", ^^user_fn, where); } // carry on }
提案では、P2996R8にあるメタ関数でエラーを報告する必要があるものは基本的に例外を使用するように変更していますが、std::meta::define_aggregate()
だけはエラーからの復帰が困難(そのために必要な保証を提供するコストに見合わない)であるとして、現状のままエラーは非定数式になるとしています。
P3561R0 Index based coproduct operations on variant, and library wording
std::variant
に対してインデックスに基づくvisit()
操作を追加する提案。
std::variant
のvisit()
操作はvariant
の候補型に同じ型が2つ以上あると上手く動作しません。visit()
は関数オーバーロードとその解決によって静的に内包する値に応じた処理の呼び分けを行うため、異なる候補型に対しては同じオーバーロードがマッチングしてしまいます。
std::variant<int, int, int> v{std::in_place_index<0>, 10}; // ok // 候補型毎の呼び分けができない(どのintの値も同じ関数を呼び出す) std::visit(v, [](int i) { ... }); // ok // これは曖昧になりエラー std::visit(v, overloaded{ [](int i) { ... }, [](int j) { ... }, [](int k) { ... } }); // ng
このように、variant
の候補型に同じ型が含まれていてもその型毎に個別の処理を行いたいような場合、visit()
を使用することはできず、一番良い方法はswitch
文を使用することです。
std::variant<int, int, int> v{std::in_place_index<0>, 10}; // ok switch (v.index()) { case 0: a = std::get<0>(v); break; case 1: a = std::get<1>(v); break; case 2: a = std::get<2>(v); break; default: throw std::bad_variant_access(); }
これはかなり煩雑なコードですが、現状これよりもきれいな形で記述することができません。
この提案では、それに対してインデックスベースのケースマッチングを行う操作を提案しています。この操作においては、呼び出す対象の型のインデックス(variant
テンプレートパラメータの先頭からのインデックス)とその型のための処理を組にしたものを用いてvisit()
することによって、variant
が2つ以上の同じ型を候補型として持っていたとしても呼び分けることを可能にします。
std::variant<int, int, int> v{std::in_place_index<0>, 10}; // ok // variantの候補型毎に呼び出す処理をその順番で指定して、それによるオーバーロード関数オブジェクトを返す auto compute = invoke_cases( [](int i) -> int { return i; }, [](std::string const &s) -> int { return s.length(); }, [](int j) -> int { return j + 100; } ); std::println("result: {}", compute(v));
invoke_cases()
はここで提案されている操作の一つで、あるvariant
に対してその値に応じて適用したい処理を渡して、variant
オブジェクトを受けてそれらの呼び出しを適切にディスパッチする関数オブジェクトを返します。この時、後から渡されるvariant
オブジェクトの実行時の状態に応じて呼び出す関数を決定するのはインデックスベースであり、.index()
に対してinvoke_cases()
に渡した関数の先頭からのインデックスと(両方0-indexed)一致する関数を選択して、その値を渡す形で呼び出します。
variant<T0, T1, ..., Tn>
に対してinvoke_cases(f0, f1, ..., fn)
はfi
がTi
の値で呼び出し可能である必要があります。invoke_cases()
では渡された関数の引数順でのインデックスを暗黙的に対象variant
でのテンプレートパラメータ順の指定と見做すことで、処理とインデックスの紐づけを自動化しています。
この提案では、このような操作を微妙な性質の違いから次の6種類提案しています
visit_invoke()
- 対象の
variant
オブジェクトと、その各候補型に対して呼び出す処理をまとめたtuple
を受け取り、variant
のアクティブなインデックスに対応する値と処理(tuple
の先頭からのインデックスでマッチング)を呼び出す
- 対象の
visit_apply()
- 対象の
variant
オブジェクトと、その各候補型に対して呼び出す処理をまとめたtuple
を受け取り、variant
のアクティブなインデックスに対応する値と処理(tuple
の先頭からのインデックスでマッチング)を呼び出す - この時、呼び出しには
std::apply()
を使用するvariant
の候補型はtuple
-likeでなければならない
- 対象の
visit_apply_cases()
- 対象の
variant
オブジェクトと、その各候補型に対して呼び出す処理をその候補型の数受け取り、受け取った処理をtuple
にまとめてvisit_apply()
を呼び出す
- 対象の
apply_cases()
- 対象の
variant
オブジェクトの各候補型に対して呼び出す処理をその候補型の数受け取り、variant
オブジェクトを引数に取り受け取った処理によってvisit_apply()
を呼び出す呼び出し可能ラッパオブジェクトを返す
- 対象の
visit_invoke_cases()
- 対象の
variant
オブジェクトと、その各候補型に対して呼び出す処理をその候補型の数受け取り、受け取った処理をtuple
にまとめてvisit_invoke()
を呼び出す
- 対象の
invoke_cases()
- 対象の
variant
オブジェクトの各候補型に対して呼び出す処理をその候補型の数受け取り、variant
オブジェクトを引数に取り受け取った処理によってvisit_invoke()
を呼び出す呼び出し可能ラッパオブジェクトを返す
- 対象の
これらの関数の命名は、渡す関数が単一の引数を取るものである場合にinvoke
、複数の引数を取る場合はapply
を使用しており、可変長引数によって処理を受けるラッパである場合はcases
を使用しています。visit
は呼び出し対象のvariant
オブジェクトを直接取るものに対して使用されます(visit
ではないものは関数オブジェクトを返す)。
関数 | 処理の引数の数 | variant を受け取る |
処理の渡し方 | 結果 |
---|---|---|---|---|
visit_invoke() |
1 | yes | tuple |
呼び出し結果 |
visit_apply() |
複数 | yes | tuple |
呼び出し結果 |
visit_apply_cases() |
複数 | yes | 可変長引数 | 呼び出し結果 |
apply_cases() |
複数 | no | 可変長引数 | 関数オブジェクト |
visit_invoke_cases() |
1 | yes | 可変長引数 | 呼び出し結果 |
invoke_cases() |
1 | no | 可変長引数 | 関数オブジェクト |
少し順番は前後しますが、サンプルコード
std::variant<int, int, int> v{std::in_place_index<0>, 10}; // ok // 対象variantオブジェクトと、候補型毎の処理をその順番で渡してvisit auto result = visit_invoke_cases( v, [](int i) -> int { return i; }, [](std::string const &s) -> int { return s.length(); }, [](int j) -> int { return j + 100; } ); std::println("result: {}", result);
std::variant<int, int, int> v{std::in_place_index<0>, 10}; // ok // 対象variantオブジェクトと、候補型毎の処理をその順番でtupleに詰めて渡してvisit auto result = visit_invoke( v, std::make_tuple( [](int i) -> int { return i; }, [](std::string const &s) -> int { return s.length(); }, [](int j) -> int { return j + 100; } ) ); std::println("result: {}", result);
using message = std::tuple<int, int>; using message2 = std::pair<int, std::string>; std::variant<message, message2> args{ std::in_place_index<0>, std::make_tuple(3, 4) }; std::variant<message, message2> args2{ std::in_place_index<1>, std::make_pair(3, "teststring") }; // variantの候補型毎に呼び出す処理をその順番で指定して、それによるオーバーロード関数オブジェクトを返す // 候補型はtuple-likeであり、処理はその要素を全て順序通りに受け取れる必要がある auto analyze = apply_cases( [](int x, int y) -> int { return x + y; }, [](int x, std::string const &y) -> int { return x + y.length(); } ); int result = analyze(args); int result2 = analyze(args2);
using message = std::tuple<int, int>; using message2 = std::pair<int, std::string>; std::variant<message, message2> args{ std::in_place_index<0>, std::make_tuple(3, 4) }; std::variant<message, message2> args2{ std::in_place_index<1>, std::make_pair(3, "teststring") }; // 対象variantオブジェクトと、候補型毎の処理をその順番で渡してvisit // 候補型はtuple-likeであり、処理はその要素を全て順序通りに受け取れる必要がある int result = visit_apply_cases( args, [](int x, int y) -> int { return x + y; }, [](int x, std::string const &y) -> int { return x + y.length(); } );
using message = std::tuple<int, int>; using message2 = std::pair<int, std::string>; std::variant<message, message2> args{ std::in_place_index<0>, std::make_tuple(3, 4) }; std::variant<message, message2> args2{ std::in_place_index<1>, std::make_pair(3, "teststring") }; auto tup = std::make_tuple( [](int x, int y)-> int { return x + y; }, [](int x, std::string const &y)-> int { return x + y.length(); } ); // 対象variantオブジェクトと、候補型毎の処理をその順番でtupleに詰めて渡してvisit // 候補型はtuple-likeであり、処理はその要素を全て順序通りに受け取れる必要がある int result4 = visit_apply(args, tup); int result5 = visit_apply(args2, tup);
このような操作は圏論における余積(coproduct)から来ているものの様で、それについての数学的な対応付けの説明が提案ではなされています。
P3564R0 Make the concurrent forward progress guarantee usable in bulk
std::execution::bulk
アルゴリズムが保証する並行進行保証を改善する提案。
std::execution::bulk
は、sender
によって表現される処理を指定されたshape
の下でバルク実行するものです。ただし、現在(P2300R10)の規定ではstd::execution::bulk
による処理は一つの実行エージェント上で逐次実行されるため、使用されるscheduler
が並行進行保証としてconcurrent
(std::execution::get_forward_progress_guarantee
クエリの結果で最も強い保証)を示したとしてもbulk
実行時に処理の並行実行を仮定できません。
たとえば、個々の処理がブロッキングによる同期を行う場合、一つの実行エージェント上で実行される2つの処理はデッドロックなく実行できません。
この問題については、以前のP3481も参照すると理解が深まるかもしれません
この提案は、std::execution::bulk
において実行される各処理が異なる実行エージェント上で処理されることを規定するようにすることで、この問題を解消しようとするものです。
例えば、シングルスレッド実行コンテキスト上のscheduler
は、bulk
をシーケンシャルなfor
ループによって実行することになります。この場合、実行エージェントとは1ループであり、各ループを別の実行エージェントとして扱うことで、このようなbulk
実装は最大でparallel
前進保証を提供することができます(parallel
保証の場合、いずれかの実行エージェントがブロックするとデッドロックする可能性があります)。
この提案の言う実行エージェントとはスレッドよりもより汎用的な概念であり、スレッドのように他の実行エージェントと並行して作業できるエンティティ、と定義されており、必ずしもハードウェアやOSの提供する機能にマッピングされるものではありません。実行エージェントは、並行処理における処理の実行単位を抽象的に指定するためのものです。
そして、scheduler
に対するstd::execution::get_forward_progress_guarantee
クエリで得られる前進保証(forward progress guarantee)は、そのscheduler
が作成した個別の実行エージェントの処理の前進を指定するものです。
「std::execution::bulk
において実行される各処理が異なる実行エージェント上で処理される」とは、「使用されるscheduler
の持つ前進保証(forward progress guarantee)が個々の処理の呼び出しに適用される」という意味です。現在のbulk
は実質的に逐次実行を指定しているためこれを満たしておらず、またこれによってscheduler
がconcurrent
な前進保証を提供していたとしてもparallel
までの保証しか提供することができません。
bulk
をカスタマイズする場合でも現在の仕様では使用される実行エージェントの数が明確に規定されていないため、N
個の処理にN
未満の実行エージェントが用意される場合(タイリングが行われる)、やはりparallel
までの保証しか提供することができません。
bulk
が可能な最も強い前進保証(scheduler
の保証するもの、最大でconcurrent
)を持つべき理由としては
- ユーザーの意図表現の自由度
- アルゴリズムの複雑さの軽減
bulk
の全処理の一括同期をサポートすることで、ハードウェアの提供する実行エージェントの一括起動サポートを受けられるようになる- このコストは起動する数に依存しない
- サポートできない場合、ユーザーは
when_all
を再帰的に呼び出すなどによってN
個のエージェントを起動するためにlog N
回の起動を行うことになる
- SG1からの要請
- SG1では全会一致で、イテレーションごとに実行エージェントを起動する
bulk
のバージョンの必要性に合意している
- SG1では全会一致で、イテレーションごとに実行エージェントを起動する
- 独自の機能としての
bulk
N
個の実行エージェント上で実行されるバルク処理は、ループの並列化とは異なる他に類を見ない機能である
- 並列プログラミングモデルとの整合性
- 基本操作としての
bulk
bulk
は基本操作であり、非同期並行アルゴリズムを構成するために使用できるscheduler
上の最小の操作セットの1つ
等が挙げられています。
このためにこの提案では次の3つのことを提案しています
bulk
の各関数呼び出し(各処理)が個別の実行エージェント上で実行されるように指定するbulk
はfor_each
の単なる別記法ではなく、並行アルゴリズムを実装するために実行エージェントを作成する方法であり、bulk
はあるscheduler
上の他のいかなる処理とも異なる前進保証を持つべきではない- 各処理は
scheduler
の保証と同じ前進保証を持つべき - 最大で
concurrent
保証を提供可能であるべき
- 各処理は
- デフォルトの
bulk
をconcurrent
な前進保証を提供するscheduler
上で呼び出すことはill-formed- デフォルトの
bulk
では、N = 1
の場合にしかconcurrent
な前進保証を提供できないため
- デフォルトの
- 指定されたエージェント数に対して、カスタマイズ
bulk
が前進保証を満たせない場合、実行時に失敗することを許可する- これにより、
bulk
は任意のN
に対して提供可能な最も強力な前進保証の提供を約束し続けられる - この失敗は、
scheduler
固有のエラー完了を通して伝達される- エラー型は実装定義、例外でなくてもよい
- これにより、
提案にはbulk
についての長い歴史的な議論へのリンクや、bulk
によるループ並列化(並行for_each
)の実装例などが記載されています。
この提案は、LEWGの投票において方向性に合意が取れ、P3481R2にマージされてC++26導入を目指しています。
P3565R0 Virtual floating-point values
C++における浮動小数点数演算のセマンティクスについての提案。
現在のC++の規定では、浮動小数点数演算の結果についてほぼ何も保証していません。演算の精度は実装定義とされており、さらには浮動小数点数オペランドや演算結果がその型の値ではない場合もあるという規定もあります。このような規定は実際に、浮動小数点数演算を含む式を数式として解釈して最適化を行うことで、計算性能や精度を向上させることの根拠となってもいます。
一方で、well-definedな浮動小数点数演算を厳密に(コードの通りに)適用することは、一部の数値アルゴリズムにとって極めて重要な場合があります。
現在のC++のこのような規定はCから受け継いだものであり、CがFLT_EVAL_METHOD
等のマクロによってこの自由度から実装が何を選択しているのかを表明する方法を提供しているように、C++においてもC++特有の事情を考慮したうえでこの自由度をより一貫して解釈する方法についてを模索する試みが始まっています。しかし、それはまだ成果を伴うものではありません。
この提案は、そのようなセマンティクスの一案を提示するものです。ここで提案されているのは次の事です
- トリビアルコピー可能な型の定義の変更
- 浮動小数点数型がある値表現を取得すると、そのオブジェクトは対応する標準的な値を持つ
- より高い精度と範囲の規定([expr.pre]/6)を、丸め処理の文脈依存性に関する注記に置き換える
- 単項+演算子と
static_cast
は、浮動小数点数値を対応する標準的な値に丸める
これは、浮動小数点数型の値の集合を、その型がメモリ上で占有する領域に格納可能な値の範囲を超えて拡張するというアプローチを採用した場合の仕様の提案です。このセマンティクスによって、浮動小数点数値は浮動小数点数型の値表現に対して多対一で対応関係を持つものの、あらゆる浮動小数点数演算および浮動小数点数型オブジェクトは依然として単一の値を持つ、事になります。
これにより、a * b + c
という式(全てdouble
値とする)の部分式の結果はdouble
の表現可能な値の範囲外の値を取ることができ、この式の結果の値の選択はFMA命令によって実装されている丸めによって行われます。
このようなセマンティクスによって、FMAのような浮動小数点数演算を数式として解釈する最適化は依然として許可されるものの、その結果は実装定義なものではなくなり、最適化の境界を越えて伝播する値はその型によって制限され、標準的な値という単一の値を取るようになります。この提案では仮想的な値を取っている浮動小数点数値を明示的に標準的な値に丸めるための方法としてstatic_cast
(と単項+
)を採用しています。
単なる代入操作は標準的な値への丸めを実行するポイントではなく、これは浮動小数点数型のラッパ型においても同様のセマンティクスを適用するためです。
P3566R0 You shall not pass char*
- Safety concerns working with unbounded null-terminated strings
その長さが静的に既知ではない文字列(const char*
)を受け取るstd::string
/std::string_view
のコンストラクタを置き換える提案。
std::string
/std::string_view
はどちらにも、const char*
を受け取るコンストラクタが存在します。これは、実行時に受け取った文字列ポインタから文字列を取得する関数ですが、その終端は渡されるまで未知のため構築時に\0
を探して終端を決定します。しかしこの動作は、間違ったポインタが渡された場合などに無制限にメモリ空間を探索してしまう(当然UB)という問題があります。
auto f(const char* ntbs) { std::string str{ntbs}; // ntbsがnull終端文字列でない場合UB std::string_view strv{ntbs}; // 同上 }
この提案は、C++の安全性向上の一環として、そのようなコンストラクタをより安全な動作をするものと明示的なもので置き換えようとする提案です。
提案では、std::string
とstd::string_view
のconst char*
を取る現在のコンストラクタを非推奨にしたうえで、const char(&)[N]
(配列参照)を取るコンストラクタを追加し、const char*
から構築するコンストラクタをstd::unsafe_length_t
タグを明示的に取るコンストラクタとして追加します。
string_view
で単純に例示すると次のようになります
// 現在のコンストラクタ constexpr string_view(const char *p) noexcept : _data(p), _size(Traits::length(p)) {...}
// この提案による変更 [[deprecated]] constexpr string_view(const char *p) noexcept : _data(p), _size(Traits::length(p)) {...} template<size_t N> string_view(const char (&p)[N]) noexcept : _data(p), _size(Traits::length_s(p, N)) noexcept {...} explicit constexpr string_view(unsafe_length_t, const char *p) noexcept : _data(p), _size(Traits::length(p)) {...}
同様に、引数にconst char*
を渡させるインターフェースのうちで安全ではないもの(無制限の探索を行いうるもの)を非推奨とするとともにコンストラクタと同様の引数を取る新しいオーバーロードを追加します。
- 両方
find_first_of, find_last_of, find_first_not_of, find_last_not_of
std::string
のみinsert
append, operator+=
replace
- 非メンバ
operator+
- タグ付きオーバーロードはなし
そして、std::char_traits
にlength_s
を追加して最大文字数を指定しながら文字列長の計算(文字列終端の探索)を行えるようにします
template<size_t N> size_t length_s(const char_type (&s)[N]) {...} size_t length_s(const char_type* s, size_t N) {...}
これはstrnlen_s()
と同様に動作し、長さN
内でnull文字が見つかる場合はその前の文字数を返し、見つからない場合はN
を返します。
ここではstd::string
とstd::string_view
で例示しましたが、これらの変更はstd::basic_string
およびstd::basic_string_view
に対して行われます。
auto example1(const char* ntbs) { std::string str1{ntbs}; // コンストラクタは非推奨 std::string_view strv1{ntbs}; // コンストラクタは非推奨 std::string str2{std::unsafe_length, ntbs}; // ok std::string_view strv2{std::unsafe_length, ntbs}; // ok } template<std::size_t N> auto example1(const char(&ntbs)[N]) { std::string str{ntbs}; // ok、最大でもN文字の範囲内で終端チェックを行う std::string_view strv{ntbs}; // ok、最大でもN文字の範囲内で終端チェックを行う }
P3567R0 flat_meow
Fixes
flat_xxx
な連想コンテナに対するバグフィックス提案。
この提案はlibc++での実装経験をベースとしたstd::flat_map
をはじめとするflat_xxx
なコンテナに対する修正提案です。
同様の提案は以前にP2762R2でも提出されていましたが、そちらはより影響の大きい設計変更を含んでいたため合意に至ることができませんでした。libc++におけるstd::flat_map
等連想コンテナのリリースが完了したことに伴って、改めてその経験から得られたより純粋な問題点についてここでは報告されています。
修正は次のものです
flat_map::insert_range()
で挿入する範囲の要素型がpair
であることを明確にする- 効果を指定する式において要素型を
auto
で受けており、これだとtuple
等も使用可能だったものの、明らかにstd::pair
だけを対象にしていた - 要素型を明確に
value_type
(std::pair<Key, Value>
)で取るとともに、他のコンテナとの一貫性向上のためにranges::for_each
を使用するように修正する
- 効果を指定する式において要素型を
swap
の条件付きnoexcept
指定flat
なコンテナのswap
はすべて無条件noexcept
指定されているが、使用するコンテナのswap
が例外を送出した場合に不変条件を保護しながら取れることは次の2つしかなかった- 例外を握り潰し、不変条件を復元する(内部コンテナをクリアする)
std::terminate()
- 不変条件を復元しながら例外を伝播させることを許可するために、
swap()
を条件付きnoexcept
にする
insert_range(sorted_unique, rg)
の欠如flat_set::insert_range()
におけるコピーの回避flat_set::insert_range()
では入力範囲の要素型がKey
型(に変換可能な型)であればいいため、可能ならムーブすることができるが、現在の規定はそれを妨げている- 効果を指定する式において
auto&&
で受けて完全転送するように指定する
- 特殊メンバ関数が規定されていない
なお、flat
な連想コンテナの不変条件とは、内部コンテナがソート済みであることと、map
の場合は2つのコンテナの要素数が一致し、なおかつKey
のコンテナとValue
のコンテナで同じインデックス位置にあるものはKey
-Value
ペアの対応付けされた要素であること、です。
- P2762R2
flat_map/flat_set
omnibus - WG21月次提案文書を眺める(2023年12月) - LWG Issue 4000. flat_map::insert_range's Effects is not quite right
- P3567 進行状況
P3568R0 break label; and continue label;
名前付きループにより、ネストしたループからの脱出の簡易な構文をサポートする提案。
これはほとんど、C23で採用された名前付きループの機能をC++に持ってきたものです。label: for
やlabel: switch
のように制御文にラベルによって名前を付けて、その文の内側でbreak label;
やcontinue label;
のように制御文の名前を指定してbreak/continue
する機能です。
outer: for (auto x : xs) { for (auto y : ys) { if (/* ... */) { continue outer; // OK, continueはouterのループに対して適用される(xについてのループがcontinueする break outer; // OK, breakはouterのループに対して適用され、2重ループを終了させる } } } switch_label: switch (/* ... */) { default: while (true) { if (/* ...*/) { break switch_label; // OK, breakはswitchに対して適用され、switch文を抜ける } } } break outer; // error: ループの外からbreakできない goto outer; // OK, 以前から可能だったので影響を与えないようにする
このようなネストしたループから簡単に脱出する方法は強く需要があったものの言語機能としてはサポートされておらず、既存の代替案はどれも問題がありました。C++に対しても提案されていたものの採用に至ることは無く、そうしてる間に他言語(Java/Javascript/Rust/Kotlinなど)でこのようなラベル付き文がネストした文の制御のためのポピュラーな方法となり、C23に向けても採択されました(N3355)。C23での採用によって、元々合った要望や他言語での例に加えて、Cとの互換性を取るというモチベーションも生まれています。
さらに、C++におけるconstexpr
の多機能化によってネストループを定数式で記述する機会が増加しており、定数式ではgoto
による簡易な代替機能が利用できないことによって、このような機能の要求が増大していました。
これらの流れを受けて、この提案はC23に向けて採用されたN3355をベースとした名前付きループの機能をC++にも導入しようとするものです。
基本的な機能はC23に採用されたものを踏襲しているのですが、1点だけ調整されている点があります。それは、ラベルの再利用が可能とされている点です。
outer: while (true) { inner: while (true) { break outer; // outerループをbreakする } } outer: while (true) { // OK, ラベルの再利用が許可される inner: while (true) { break outer; // outerループをbreakする(1つ目のouterではなく2つ目のouterをbreakする } } goto outer; // error: ジャンプ先が曖昧となるためエラー
goto
は名前付き文のラベルに対してもジャンプできますが(これは後方互換性のため)、ラベルが再利用されている場合はill-formedとなります。
また定数式の対応については、この新しい名前付きループとbreak/continue
をそのまま(定数式で禁止するという条項を付けることなく)導入することによってgoto
を許可せずとも自然に定数式で使用できるようになります。
P3569R0 Split define_aggregate
from Reflection
define_aggregate()
をP2996のリフレクション提案から外す提案。
define_aggregate()
はその名の通り集成体型を定義するためのリフレクションメタ関数です。例えば次のように使用できます
// タプル型を実装する例 template<typename... Ts> struct Tuple { struct storage; consteval { define_aggregate(^^storage, {data_member_spec(^^Ts)...}) } storage data; Tuple(): data{} {} Tuple(Ts const& ...vs): data{ vs... } {} };
1つ目に不完全クラス型(もしくは共用体型)を受け取り、2つ目の引数で受け取ったデータメンバのリフレクションの列を受け取って、そのクラス型に対して指定されたデータメンバを集成体型として実装する形の定義のリフレクションを返す関数です。
このdefine_aggregate()
は非常に便利な機能ではあるもののその設計に関してはこれまで二転三転してきており、P2996の他の部分の安定性と比較すると明確に不安定かつリスクのある機能になっています。
その理由の1つはcomplete-class context(完全クラスコンテキスト)という概念についてのものです。完全クラスコンテキストの概念はP2996R9を見ても明確ではないですが、どうやら定数評価中にdefine_aggregate()
で定義することで完全型となったクラス型について、その完全性が観測可能になる場所とタイミングについてを規定しようとしているもののようです。
もう一つは、到達可能性(reachability)の新しい形式を導入しようとしていることです。到達可能性はモジュールの採択時に導入されたあるコンテキストからの宣言の利用可能性についての規格用語ですが、define_aggregate()
によって生成された定義がいつどこで到達可能になるかを規定するために到達可能性の定義を拡張しようとしています。
どちらの問題も設計や問題解消方法がまだ固まっておらず、この提案の提出時点でC++26のタイムフレーム内で解決できるか見通しが立っていない状況です。
それを受けてこの提案では、C++26にリフレクション機能を確実に間に合わせるために、P2996からこのdefine_aggregate()
を取り除くことを提案しています。
リフレクション機能のほとんどの部分はコンパイラが内部に持っているASTの状態をクエリする事で成り立っており、これは現在の型特性機能が行っていることでもあるため実装はそれを認識し、専用のインターフェースを用意しています。一方でdefine_aggregate()
は定数評価中にASTを書き換えることができる機能であるため、実装にとっても標準にとっても新たな試みであり、多くの課題があります。
このようなコード生成に関する機能は最終的にはリフレクション機能の一部として必要であるものの、現時点の他の部分のリフレクション機能(クエリ関連機能)だけでも十分に有用性があり、なおかつCWG内でもほとんど異論がありません。
このため、define_aggregate()
を切り離すことでリフレクション機能全体がC++26に間に合わなくなるリスクを大きく低減することができるとともに、define_aggregate()
の実装と規定に関しての探求作業を集中的に行うことができるようになります。
どうやら、この提案はEWGにてコンセンサスを得られなかったようです。
P3572R0 Pattern matching
C++26にむけてパターンマッチング機能の選択を促す提案。
現在C++のパターンマッチング提案はP1371(match
式)とP2392(is/as
)の2つが並立しています。これら2つは設計が異なっているため標準に採択するにはどちらかを選ぶ必要がありますが、今のところどちらかを選択することに合意が取れていません。
この提案は、C++のパターンマッチングとしてP2392(is/as
)の設計を支持し、これを簡素化したバージョンをC++26のパターンマッチングとして採択すべきとするものです。
この理由としては次のようなものを挙げています
is/as
はフレームワークであり、将来的に新しい構文を追加せずに新しい選択形式を追加できる- 簡単に使用できる
- 効率的に実装可能であり、既に実装がある
- シンプルかつ汎用的な構文を持つ
match
式が多様な新しい専用の記法を多数導入しようとしているのに対して、is/as
ではデリファレンスに*
が必要なのみ- 各パターンは
is/as
というプレフィックスパターンで始まるため、可読性とレイアウト性が向上する
なお、結局この提案を受けても選択はできなかったようで、パターンマッチング機能はC++29以降になります。
P3573R0 Contract concerns
P2900のContracts機能に対する懸念を表明する文書。
これは、P2900R13で提案中のContracts機能に対して懸念を持つ人々が集まり、その懸念点を列挙して報告する文書です。
P3573R0に列挙されているP2900のContractsに対する懸念点は以下の通りです。
const
化- 変更を防ぐのための方法として
const
化は役に立たない - これは実装者、ユーザー、教育者にとって大きなコストを伴う別の不規則性
- 警告によってほとんどの問題は対処可能
- 変更を防ぐのための方法として
- 例外の補足
- 契約述語からの例外送出は契約違反ではなく、契約が開始されなかったことを意味する
- 例外を送出する述語をContracts内の特別なケースとして処理されるべきではない
- 過度に複雑な階層型契約モデル
- このモデルは未検証であり、全員に強制すべきではない
- 仮想関数での入り口と出口で4つ以上の契約が評価されるモデルは、問題の一部の解決策に過ぎず、不完全であり、正しいと見做されていない
- 契約の継承を必要とする設計は数多く存在する
- 全体的な設計の不安定さ
- 2024年にP2900に対して、10以上の変更提案、1000以上のリフレクターメッセージ、数十回の変更が加えられており、議論を追うのが困難
- 実際に何が提案されているか知っているのは少数の人々だけであり、これでは国際標準の確固たる基盤とはいえない
- 実装定義が多すぎる
- 平均的なユーザーから専門家まで、何がチェックされ、失敗の結果どうなるかを理解するのは困難
- 実装定義とされている項目が多岐にわたるため、ポータブルな契約を書くのは難しく、初心者に対して契約が何をするかを説明・教育することは実質的に不可能
assert()
であれば、初心者に約1分で説明できる
- 契約のグループ化機能が提供されていない
- 契約の有効化/無効化はグローバルでONかOFFかのみであり、契約を個別に有効化・無効化できるグループに分類する方法がない
- デフォルト設定
- P2900は実装定義なセマンティクス等について、デフォルトの最適な設定があるとしているが、それは具体的に示されていない
- 未検証
- 実行時チェックと静的チェック
- 実行時チェックと静的解析における契約述語の使用との関係性は十分に検討されていない
- 例えば、実行できない述語をどのように指定するのか?
- 関数ポインタ
- C言語スタイルのプログラミングでは重要な機能であるため、省略すべきではない
- 安全性
- 契約が安全性とどのように相互作用するかが不明
- 安全性にはコードベース全体またはその一部に対する保証を伴う必要がある
- 例えば、SG23が推進するプロファイル機能では一般的な保証が提供されている
- 契約は主に、使用されている箇所での正確性のチェックを提供し、どちらも違反ハンドラーを必要とし、その違反ハンドリングが同じであることが理想的
- これらの問題が解決される前に、安全性に影響を与える重要な設計基準を固定すべきではない
- これは上の「デフォルト設定」に関連している
- 未定義動作
- 未定義動作は契約設計で直接対処されるべきか、それとも他の提案で一般的に対処されるべきか?
- 翻訳単位の構成
- 異なる契約設定を持つ翻訳単位をリンクした場合の影響が明確に規定されていない
- 特に、あるテンプレートが異なる契約設定を持つ2つの翻訳単位でインスタンス化された場合、それらは異なる設定になるのか?リンカーがそれを防ぐべきなのか?そうでない場合取得される設定は何によって決定されるのか?
- インライン関数、constexpr関数、consteval関数、およびコンセプトについても同様の疑問がある
- 契約とモジュール
- 翻訳単位と同様の疑問がある
- 契約設計はモジュールでテストされているのか?
- 契約と静的リフレクション
- 翻訳単位と同様の疑問がある
- 将来の改善の互換性
- これらの問題が現在の設計の枠組み内で将来対処できると言えれば良いが(これはMVPの基本的な目標だが)、いくつかの問題の解決には互換性のない変更が必要となるように思える
- 提案が大きすぎる
- 契約は(他言語のように)シンプルな機能であるべきだが、仕様は巨大で、実装は困難であり、一部の機能は学習曲線が急峻になっている
- スケーラビリティ
- コンパイラとリンカがバイナリに多くの情報を取り込む必要があるため、提案はスケーラブルではない可能性がある
- これは複数のツールチェーンの変更を意味する
そのうえで、これらの問題に対処する必要があり、なおかつその解決策はSG21の常任のメンバーよりもはるかに多くの人に理解されていなければならず、標準化の前にしばらく安定していなければならないものの、C++26サイクル中でそれが実現できるとは思えないため、国際標準の提案としては不完全で未検証である、と主張しています。
ただし、ここではこれら懸念点を列挙するのみで、具体的な解決策やそれについての提案が行われているわけではありません。合意に達することのできる単純な解決策の設計は見当たらないとしています。
P3574R0 Constexpr Callables
std::copyable_function
とstd::move_only_function
をconstexpr
対応する提案。
std::copyable_function
はC++26で、std::move_only_function
はC++23で追加されたクラスです。これらは、std::function
の問題点を改善した新しい型消去呼び出し可能ラッパクラスです。これは当然、まだconstexpr
対応されておらず定数式で使用できません。
同じくC++26では、定数式におけるvoid*
への/からのキャストが可能になっているため、これらのクラスをconstexpr
対応させる障壁は無くなっています。
通常のプログラミングで有用なものは定数式でも有用であるため、この2つのクラスをconstexpr
対応させようとする提案です。
P3575R0 SG14: Low Latency/Games/Embedded/Financial Trading virtual Meeting Minutes 2024/11/13
2024年11月13日に行われた、SG14のオンラインミーティングの議事録。
どのようなことを議論したのかが簡単に記載されています。
P3576R0 SG19: Machine Learning virtual Meeting Minutes to 2024/11/14-2024/12/12
2024年11月14日と12月12日に行われた、SG19のオンラインミーティングの議事録。
どのようなことを議論したのかが簡単に記載されています。
P3577R0 Require a non-throwing default contract-violation handler
デフォルトの契約違反ハンドラが例外を送出しないようにする提案。
P2900R13で提案中のContracts機能では、契約違反が起きた時に違反ハンドラが呼ばれそれを処理します。違反ハンドラはデフォルトのものがまず用意されますが、ユーザーが置換して任意のハンドラを使用できるようになっています。この違反ハンドラは例外を送出することによって契約違反を処理するようにすることができるように例外を送出しうることが考慮されています。そしてそれは、デフォルトの違反ハンドラにおいても同様でした。
C++29以降の拡張として、言語の未定義動作に対して暗黙の契約を付加することによって契約プログラミング機能の枠組みでUBに対処しようとする方向性があります。この場合、UBが起きる場合に代わりに違反ハンドラが呼ばれるようになりますが、その違反ハンドラが例外を送出する場合、UBの代わりに例外送出という別のコードパスが追加されてしまうことになり、これについて懸念を持つ人が少なくなかったようです。
この提案は、少なくともデフォルトの違反ハンドラは例外送出しないことを規定することによって、それらの懸念を払拭しようとするものです。
ただ依然として、ユーザー定義の違反ハンドラは例外送出によって終了することができます。あくまで、デフォルトの違反ハンドラの動作(実装定義とされている)について例外送出という選択肢を無くそうとするものです。
この提案はSG21では好まれたものの、EWGでのコンセンサスを得ることができなかったようです。
- P3100R0 Undefined and erroneous behaviour are contract violations - WG21月次提案文書を眺める(2024年05月)
- P3577 進行状況
P3578R0 Language Safety and Grandma Safety
安全性(Safety)という言葉の定義を明確にし、その使用方法に注意を払う事を推奨する提案。
色々ある安全性の中でも、言語安全性と機能安全性(ここではおばあちゃんの安全性(Grandma Safety)と呼ばれている)を区別し定義し
- 言語安全性: 言語設計者とコンパイラが強制できる全てのものの集合
- (型|メモリ|ライフタイム|スレッド|etc)安全性が含まれる
- おばあちゃんの安全性(機能安全性): プログラムがおばあちゃんに危害を加えないことを保証するものの集合
そのうえで次の事を推奨、あるいは説明しています
- ハイフン無しで“safe”/“safety”という言葉を使用しない
- 意図する内容を正確に表す業界で認められた完全な正しい用語を使用する
- 言語安全性とは何か、機能安全性とどう違うのかを理解する
- 言語安全性と機能安全性はどちらも必須だが、同じ機能で満たす必要はない
- 契約チェックは機能安全性に必須だが、言語安全性にも役立つ
- 言語安全性だけに重きを置き機能安全性を気にしない場合、それに注力し続けたうえで機能安全性を向上させようとしている機能を阻害しない
これは主に、C++ Contracts機能がプログラムの正確性の向上を介して機能安全性を向上させるためのものであり、言語安全性の向上のための機能ではなく、言語安全性の向上が果たされないからと言ってContractsが不要というわけではないことを説明するための文書の様です。
P3579R0 Fix matching of non-type template parameters when matching template template parameters
テンプレートテンプレートの特殊化のマッチング時に、NTTPの縮小変換を許可しないようにする提案。
この提案はP0522R0(C++17)で修正された部分特殊化のマッチングルールの悪影響を緩和しようとするP3310R5の提案をフォローアップするものです。その問題とはNTTPを含むテンプレートテンプレートパラメータに対して、テンプレートをマッチングしようとする際に、NTTPの縮小変換が起きてしまうというものです。
template<template<short> class> struct A {}; template<short> struct B; template struct A<B>; // OK, exact match template<int> struct C; template struct A<C>; // #1: OK, all 'short' values are valid 'int' values template<char> struct D; template struct A<D>; // #2: error, not all 'short' values are valid 'char' values
P0522R0では#1を許可することを意図していたものの、意図せずに#2も許可してしまっていたようです。この提案はこの#2を拒否しようとする(サンプルコードコメントの通りの動作になるようにする)ものです。
P0522R0で導入されたルールでは、このようなマッチングをテンプレートパラメータから変換した関数テンプレートのオーバーロード解決によって指定していました。しかし、そのように生成された関数テンプレートではNTTPの変換時にその値依存性(具体的な値によって変換可能性が決まる)により縮小変換のチェックが行われないようになっていたため、NTTPの縮小変換が許可される形でマッチングが成功してしまっていたようです。
このマッチングはテンプレートテンプレート仮引数P
がテンプレートテンプレート実引数A
と「P
is at least as specialized as A
」という関係にあるかどうかを指定するところで行われており、この規定の意味は「テンプレートテンプレート仮引数に指定可能なテンプレート引数とは、テンプレートテンプレート実引数に対しても適用可能なものである」ということだったようですが(すなわち、仮引数と実引数を逆にしても指定可能なはず、ということ)、それを関数テンプレートのオーバーロード解決に委譲してしまった事によってその意図が正しく表現されていなかったようです。
この提案の修正では、上記関数テンプレート生成時にNTTPの縮小変換について明確に規定することで問題を解消しようとしています。
P3580R0 The Naming of Things
C++の新機能(主にライブラリ機能)の名前付けについてのガイドラインを提供使用とする文書。
この文章では、名前付けは設計の重要な要素であるとして命名の重要性を説いています。
- 良い名前はソフトウェア設計の重要な要素
- 設計は名前を中心に構築される
- 良い名前を見つけるのに苦労する場合、問題は設計にある可能性が高い
- 名前の選定はBikesheddingではない
そのうえで、次の事を推奨しています
- 提案の著者は、明確かつ強固な、設計を反映した命名について提案すること
- 提案の著者は、全てのフィードバックを検討し、最終的な推奨事項をまとめる
- 著者が名前について確信が持てず、委員会からのフィードバックや提案リストを必要とする場合、積極的に尋ねるべき
- 提案の読者は命名について疑問を持つ場合、他の主要な設計要因と同様に、提案で提示される論理的な議論によって対処する
- 名前の投票という慣行をやめるべき
- この方法を選択する場合、本当にBikesheddingに陥っている可能性が高い
おそらくこれはLEWG/EWGのポリシーに関連する提案に該当すると思われます。
P3581R0 No, inplace_vector
shouldn't have an Allocator
inplace_vector
にアロケータサポートを追加することに反対の立場を示す文書。
P3160R2ではinplace_vector
にアロケータサポート(要素へのアロケータの伝播)を追加することを提案しています。この提案はEWGではコンセンサスを得られていませんが、P3160の著者の方は粘り強く推進しているようです。
この文書ではSG14で反対意見が無かったことは議論の方向性を示しておらず、P3160R0が否決された時から新しい情報(動機やユースケースなど)は示されていないとして、反対意見を表明しています。
どうしてもしたい場合、basic_inplace_vector<T,N,A>
の様な別の型として追加すべきとしています。
P3582R0 Observed a contract violation? Skip subsequent assertions!
Observeセマンティクスにおいて、契約違反発生後は後続の契約アサーションを評価しないようにする提案。
例えば次のような関数があるとき
Tool* selectTool(Tool* ta, Tool* tb) pre (ta != nullptr) pre (ta->is_configured()) pre (tb != nullptr) pre (tb->is_configured()) post (r: r != nullptr) post (r: r->is_configured()) { if (preferFirst) return ta; else return tb; }
ある時点において、この関数は契約アサーション無しで運用されており、この関数にはnullptr
が渡されている可能性があります。この関数の事後条件(戻り値はnullptr
ではない)はどうやら有効活用されていなかったことで問題は表面化していませんでした。
その後、この関数に契約アサーションを導入することにして上記例のように契約を付加して、Observeセマンティクスによって様子を見ることにします。すると、契約アサーション内でnullptr
チェックの後ポインタのデリファレンスを行っていることによって、この関数は未定義動作を伴うようになります。
この場合のUBは、Ignore/Enforceセマンティクスでは発生せず、Observeセマンティクスでのみ発生します。
この提案は、このようなUBを回避するために、ある契約シーケンスの中でObserveセマンティクスの下で契約違反が起きた場合、そのシーケンス内で後続の契約アサーションのチェックをスキップする(あるいはIgnoreセマンティクスで評価する)ようにしようとするものです。
契約シーケンスとは、ある関数の事前条件の全体、事後条件の全体、あるいは本体内のアサーションの全体、などの小さなまとまりの事です。
この提案はSG21のレビューにおいて否決されています。
契約条件が正しいとすると、契約違反が起きている状態というのはプログラムが正しくない状態であるため、Observeセマンティクスでそれが検出された後にUBになるのは想定された動作です。この場合、少なくとも契約違反に気づくことはできるため、関数の利用側を修正するべきです。また、契約条件が正しくない(指定された条件は事前条件と言えるものではないなど)場合、それは関数内で明示的にif
などでガードするべきです。いずれにせよ、Observeセマンティクスの動作を弄ることによって表面的なUBを回避する方向性が好まれなかったものと思われます。
P3583R0 Contracts, Types & Functions
関数型に契約注釈を持たせるようにすることで、関数ポインタに対する契約注釈の指定を可能にする提案。
P2900R12でC++26に向けて提案中のContracts機能においては、関数ポインタに対する契約注釈の指定をサポートしていません。これは関数ポインタに対する契約注釈の扱いについて解決すべき様々な問題があることから、C++29以降の機能として議論していくことにしているためです。
関数ポインタに対する契約注釈についての検討はP3327R0で詳細に検討されており、そこでは関数ポインタに対する契約注釈を型の一部にする、値の一部にする、変数のプロパティとする、の3つの方向性が挙げられています。
この提案はその3つのいずれでもなく、関数型に契約注釈を埋め込むアプローチについて検討しているものです。
関数型に対する契約を可能にするために解決する必要がある問題点は次の3つです
- 2つの契約の同一性を判定する方法
- 名前マングル処理の実装方法
- 関数型の契約を使用するようになった場合に、使用していない既存のコードに悪影響を及ぼさないこと
現在の関数型は構造的な型(structural type、NTTPの文脈の用語とは若干異なる意味)であり、関数の構造からその型が決定されます。例えば、異なる場所で定義された関数であってもそのシグネチャが同一であれば同じ型を持ちます。この関数型には名前がなく、同じ型に対しては名前マングリングによって一意の識別子が生成される必要があります。
関数型に契約注釈を追加しようとする場合、コンパイラは2つの関数が同一の型を持つかどうかを判断できる必要があり、このためには契約注釈を決定的な形で正規化できる必要があります。名前マングリング時にも同じ事が求められます。
この提案では、契約注釈付きの関数型については構造的な型としないことによってこれらの問題(特に1と2)を解決しようとしています。このアプローチはすでにクラス型において運用されています。クラス型の型は、定義時の名前によって区別され、クラス構造が完全に同一であっても名前が異なれば異なる型として扱われます(このような型をnominal type、公称型と呼ぶ)。
すなわち、契約注釈付きの関数型はその宣言時にユニークな名前を与えられ、それによって型の区別が行われます。したがって、同じシグネチャと同じ契約注釈をもつ契約注釈付きの関数型であっても、その名前が異なれば異なる型として扱われます。これによって、契約注釈の正規化の問題を回避することができます。
また、このような型付けの導入は専用の構文を用いて非侵入的に行うことを提案しています。すなわち、契約注釈の無い関数は従来どおり構造的型付けが行われ、シグネチャが同じなら同じ型を持ちます。しかし、契約注釈が付加された時点で記名的型付けによって公称型となります。
型に付与された契約注釈はオーバーロード解決に寄与せず、異なる型のポインタ(契約注釈付き関数ポインタ)の間には暗黙変換が定義されません。また、P2900R12が提案している現状(契約注釈を持つ関数のアドレスを関数ポインタに入れられる)を保護し、なおかつ型に対する契約注釈情報の伝播を行わないようにするために、関数は型の一部とは別に実装契約を持つことができるようにします(これは、P2900R12において関数ポインタ経由の呼び出し時に契約チェックを行うための実装方法であるサンクのような仕組みをサポートする意図)。これらのことによって、後方互換性の問題を回避しています(上記の3)。
この新しい、契約注釈付きの関数型を宣言する構文としては、function class
という文を提案しています。
// 通常の関数型 function class Fa = int(int,int); function class Fb = int(int,int); static_assert(is_same_v<Fa, Fb>); // 契約注釈付き関数型 function class Fc = int(int a,int) pre(a < 4); function class Fd = int(int a,int) pre(a < 4); static_assert(not is_same_v<Fc, Fd>);
異なる関数が同じ契約注釈を持つと判定されてほしい場合、同じ型宣言を使用して作成する必要があります。しかし、C++には定義済みの型から(クラスのオブジェクトを構築するように)関数を定義する方法はありません。この提案ではそのための構文としてキャプチャレスラムダの構文を借用します。
// 契約注釈付き関数型 function class Fa = int(int a,int) pre(a < 4); // 関数型による関数定義 Fa fa = [](auto a,auto b) { return a+b; } // 有効な定義例 Fa fa = [](auto a, auto b) { return a+b; } // Ok Fa fa = [](int a, int b) { return a+b; } // Ok Fa fa = [](auto... a) { return a+...; } // Ok Fa fa = [](a, b) { return a+b; } // Ok? Fa fa = [](float a, float b) { return a+b; } // Error
これはキャプチャレスラムダの関数ポインタへの変換のようにも見えますが、この提案では直接ラムダを宣言する形のみを許可することを意図しており、任意のラムダ式から変換する形で定義するものではありません。
この関数インスタンスの定義においては引数型はすでに関数型としてわかっているため、厳密に一致させるかauto
にするか名前のみを指定するかのいずれかが許可されます。
関数ポインタは通常の型に対するポインタのように構築できます
function class Fa = int(int a, int) pre(a < 4); // 関数型による関数定義 Fa fa = [](a, b) { return a+b; } // 契約注釈付き関数ポインタ定義 Fa* pa = &fa; // 通常の関数型 function class Fb = int(int a, int); Fb* pb = &fa; // Error, 関数型が異なる
pb
の例は、異なるユーザー定義型のポインタ型の間で暗黙変換が用意されないのと同様に拒否されます。
型に対する契約は安全かつ適切であるものの、契約が変更されるとこの型を使用するコードを変更する必要がある場合があります。これを軽減するために、関数の実装(インスタンス)に対しても契約を指定することができます。
function class Fa = int(int a, int) pre(a < 4); Fa fa = [](auto a, auto b) post(b<13) { return a+b; } Fa* pa = &fa;
このfa
は関数型に指定された事前条件と、関数実装に指定された事後条件の2つの契約を持っています。変数fa
を直接使用して呼び出す場合は両方の契約が可視になりますが、関数ポインタpa
を使用する場合は必ずしもそうではありません。
この提案では、この場合の実装に指定された契約注釈を実装契約(Implementation contracts)と呼び、実装契約を持つ関数(契約注釈付き関数型のインスタンス)のアドレスを取得する時、この実装契約がチェックされる処理を持つ、同じ型の関数ポインタが作成されます。そのような関数ポインタ経由の呼び出しは、
型の事前条件 -> 実装契約の事前条件 -> 関数本体 -> 実装契約の事後条件 -> 型の事後条件
の順で評価されます。
通常の関数に対する契約注釈とそのアドレス取得時にもこの延長で処理することを提案しています。すなわち、通常の関数の関数型はそこからfunction class
による関数型(ただし契約注釈はない)を求めて、その型のインスタンスとして定義される、と解釈します。これによって、通常の関数に指定された契約注釈は実装契約として扱うことでP2900R12の動作を維持できます。
// 通常の関数宣言 int fc(int a, int b) pre(b<13) { return a+b; } // ↑はこのような関数型とそのインスタンスとして解釈される function class __FC = auto (int, int)-> int; __FC fc = [](a, b) pre(b<13) { return a+b; } // 契約注釈付き関数型と同じルールにより関数ポインタが作成される int (pc*)(int, int) = fc;
このような__FC
は同じシグネチャを持つ通常の関数宣言による関数型の間で共有されます。
関数テンプレートの場合もクラステンプレートと同じような扱いによって処理できます。
// テンプレート化された関数型 template<typename E> function class Fe = int (int, E e) pre(e < 4.14); // 関数テンプレートのインスタンス(関数テンプレートの定義 template<typename E> Fe<E> fb = [](a, b) { returna+b;}; // 呼び出しによる型推論とインスタンス化 fb(A{} ,3.14); // 型は `Fe<double>` // 関数テンプレートの明示的特殊化による定義 Fe<int> fi = fb; // fiという名前でintによりインスタンス化 // ジェネリックラムダでの使用 auto fg = []<typename T>Fe<T>(a, b) { return a+b; };
この関数型とそのインスタンスによる関数定義における前方宣言は、引数を参照するインスタンスプロパティに名前を付ける必要性から、これまでとは少し異なった形になります。
// 関数型の定義 function class Fa = int(float, int); // 前方宣言 Fa fa; int fa(float, int); int fa(float, int b) pre(b == 4); // 関数定義 Fa fa = [](float a, int b) pre(b == 4); Fa fa = [](auto a, auto b) pre(b == 4);
メンバ関数の場合、契約注釈内でクラスメンバを参照するために、明示的オブジェクト引数(deducing this
)を持つメンバ関数のみを
ただし、メンバ関数の場合は少し複雑になってしまいます
struct X { function class Xf = int (int); // 関数型 function class Zf = int (this X, int, int); // メンバ関数型 function class Xg = int (int) pre (x == 3); // Error、メンバアクセスできない function class Xg = int (this X ,int) pre (x==3); // Ok int x; // 関数型インスタンス static Xf my_static_function; Xf my_member_function; }; // メンバ関数ポインタ X::Zf X::* pc; pc = &X::my_member_function; // X::Xf* ps; ps = &X::my_static_function;
X::Xf
は文脈に応じてフリー関数型としてもメンバ関数型としても使用できるようにすることを提案しています。しかし、このような性質はその契約注釈からメンバが参照されたときに問題となるため、契約注釈からメンバを参照可能な関数型はthis
を明示的に取る形式に限定することを提案しています。このために、関数型を拡張してメンバ関数型の表現のために明示的なthis
引数型を許可するようにします。
契約注釈付き関数型によって宣言された仮想関数においては、P2900R12で提案されているのと互換性を持つように
- 基底型の契約の事前条件
- 基底型の実装契約の事前条件
- 派生型の実装契約の事前条件
- 派生型の実装契約の事後条件
- 基底型の実装契約の事後条件
- 基底型の契約の事後条件
の順序でチェックすることを提案しています。
P3584R0 Enrich Facade Creation Facilities for the Pointer-Semantics-Based Polymorphism Library - Proxy
P3086R3からfacade_builder
というクラステンプレートだけを導入する提案。
P3086では、通常のインターフェースと継承のような動的な多態性ではなく、静的な多態性を実現するためのライブラリ機能について提案しています。P3086については以前の記事を参照
この提案は、その中の一つの機能であるfacade_builder
(basic_facade_builder
)だけを導入しようとする提案です。
basic_facade_builder
はP3086R3の機能のベースとなっているProxyライブラリにおいて、std::proxy
にインターフェース型を登録する際に、その性質を細かく指定してファサード型を導出するためのクラステンプレートです。これは、Proxyライブラリを使用するにあたって複雑な設定を使いやすくするための入り口となる機能です。
basic_facade_builder
のメンバはすべて何らかのエイリアステンプレートでありfacade_builder
はその設定済みのエイリアスになっています。
template <class Cs, class Rs, proxiable_ptr_constraints C> struct basic_facade_builder { template <class D, class... Os> requires(see below) using add_indirect_convention = see below; template <class D, class... Os> requires(see below) using add_direct_convention = see below; template <class D, class... Os> requires(see below) using add_convention = see below; template <class R> using add_indirect_reflection = see below; template <class R> using add_direct_reflection = see below; template <class R> using add_reflection = add_indirect_reflection<R>; template <facade F, bool WithUpwardConversion = false> using add_facade = see below; template <size_t PtrSize, size_t PtrAlign = see below> requires(see below) using restrict_layout = see below; template <constraint_level CL> using support_copy = see below; template <constraint_level CL> using support_relocation = see below; template <constraint_level CL> using support_destruction = see below; // 導出結果を得る型エイリアス定義 using build = see below; basic_facade_builder() = delete; }; using facade_builder = basic_facade_builder<tuple<>, tuple<>, proxiable_ptr_constraints{ .max_size = default-size, .max_align = default-size, .copyability = default-cl, .relocatability = default-cl, .destructibility = default-cl }>;
facade_builder::build
あるいは各種設定を指定したbasic_facade_builder<...>::build
からstd::proxy
で使用可能なファサード型を得ることができます。見ての通りbasic_facade_builder
は複雑であり設定の指定方法が難しいので、その単純なインターフェースとしてfacade_builder
が用意される形になります。
P3585R0 allocator_traits::is_internally_relocatable
std::allocator_traits
をリロケーション対応させる提案。
P2786R11ではC++26に向けてオブジェクトのリロケーションという操作を言語レベルで許可しようとしています。これが導入されると、コンテナにおいて内部で要素移動を行う必要がある場合(std::vector
のキャパシティ伸長時や.erase()
時)のオブジェクト移動コストを大きく下げることができます。特に、トリビアルリロケーションが可能であれば、クラス型であってもその移動(リロケーション)はmemcpy
にまで単純化することができるようになります。
しかし、実際の標準コンテナの動作はアロケータに依存するところが大きく、コンテナ要件ではコンテナ要素のオブジェクトの構築と破棄はstd::allocator_traits<Alloc>::construct()
とstd::allocator_traits<Alloc>::destruct()
を使用して行わなければならないと指定されています。この要件は標準コンテナにおけるリロケーションの使用を妨げることになり、また、トリビアルリロケーションを不可能にします。
この提案は、std::allocator_traits
にアロケータからコンテナへのリロケーション許可権限を通知するためのインターフェースを追加することで、アロケータおよび標準コンテナをリロケーション対応させようとするものです。
std::allocator_traits
に追加されるインターフェースは次の2つです
std::allocator_traits<Alloc>::internally_relocate()
std::relocate()
(P2786R11)と同じインターフェースをもつAlloc
がinternally_relocate()
メンバ関数を提供する場合それを呼び出し、そうでない場合はstd::relocate()
を呼び出す- リロケーション操作の前後にアロケータからの処理を挟みたい場合に使用できる
std::allocator_traits<Alloc>::is_internally_relocatable<T>
- と対応する
std::allocator_traits<Alloc>::is_internally_relocatable_v<T>
- コンテナが
T
型の要素をconstrcut()/destroy()
を介さずにリロケーションすることを許可する場合、true
を返す - アロケータが
is_internally_relocatable<T>
メンバ(メタ関数)を提供する場合、それに委譲 - そうでない場合次のいずれかの場合に
true
T
がis_nothrow_relocatable_v<T>
を満たしていて、Alloc
がconstrcut()/destroy()
を提供していないAlloc
がis_internally_relocatable
メンバを提供する
- と対応する
コンテナでは、std::allocator_traits<Alloc>::is_internally_relocatable_v<T>
の値によってリロケーションを行うかどうかを決定し、リロケーション操作はstd::allocator_traits<Alloc>::internally_relocate()
を使用して行います。このようにすることで、アロケータによるカスタマイズ性を維持しながら、リロケーションの恩恵を受けられるようになります。
std::vector<T, A>::erase(first, last)
における利用例
この提案がない場合 | この提案 |
---|---|
using AT = allocator_traits<A>; if constexpr (is_nothrow_relocatable_v<T> && ! requires(A& a, T* p) { a.construct(p, std::move(*p)); } && ! requires(A &a, T* p) { a.destroy(p); }) { AT::destroy(m_alloc, first); m_end = relocate(last, m_end, first); } |
using AT = allocator_traits<A>; if constexpr (AT::template is_internally_relocatable_v<T>) { AT::destroy(m_alloc, first); m_end = AT::internally_relocate(m_alloc, last, m_end, first); } |
また、std::vector
の.erase()
でリロケーションを許可するために、その要件を緩和する文言変更も同時に提案しています。
P3586R0 The Plethora of Problems With Profiles
議論中のプロファイル機能についての問題点を指摘する文書。
指摘は多岐にわたり、安全性向上への取り組みが必要であることに同意しつつも、プロファイル機能をほとんど全否定しています。
ここでは問題点の詳細を転記しませんが、例えば既存の実行時検査メカニズムの利活用、構文の無視可能性、構文の冗長さ、長期的に運用した場合の後方互換性についての考慮不足、危険とされる構文をどこまで禁止すべきかの考慮不足(あるいはその困難性)、標準で警告を制御しようとすることの有害性、安全性とスタイル上の懸念の混同、言語のサブセット化メカニズムとしての貧弱さ、などが指摘されています。
この文書は何かを提案するものではないものの、C++の言語安全性の向上には次のような多角的なアプローチが必要としています
- 危険な構文を削除/非推奨化し、より明確な代替手段を提供する
- 危険な構文の削除が困難な場合には、エポック(P1881R1)のような仕組みを検討する
- P3100などで提案されているように、実行時に言語のUBを検出しハンドリングするのに契約機能を使用する
- 標準ライブラリ実装との連携を継続し、実行時に事前条件チェックの強制適用を徹底する
- 制御フロー解析ツールが十分に成熟し、どの部分を標準化すべきか判断できる段階にいたるまで、実装/ツールベンダとの協力関係を維持する
- 長期的な視点から、メモリ安全性についての言語レベルの解決策を検討する
- 委員会が、CHERI/MTE/pointer認証技術の普及支援にどの程度貢献できるか検討する
- メモリ安全な言語との互換性向上/インターフェースの改善について検討する
- このような言語は一部の業界で主要な開発ツールとして採用されつつあるため
- 時期尚早に扉を閉ざさない
- たとえそれが一見困難だったり、野心的なアイデアだったりしたとしても、多くの有用な提案は慎重に検討する価値がある
C++の安全性向上策としては、標準仕様の変更とベンダ提供ソリューションを組み合わせた多角的なアプローチを採用し、核問題に対して適切なツールを選択するようにすることを推奨しています。
また、言語安全性の向上はC++の将来にとって重要な問題点であるものの切迫感に駆られて判断を誤るべきではなく、規制当局からの圧力は非常に複雑で多面的なこの問題を数週間で解決しようとする理由にならない、としています。
P3587R0 Reconsider reflection access for C++26
リフレクション機能において、クラスのプライベートデータへのアクセスを禁止する提案。
C++26に向けて静的リフレクション機能の導入がP2996R8で議論されています。リフレクションは、これまで手作業で行われてきたボイラープレートコードの自動化などを目指しており、プログラマの生産性向上に大きく貢献することが期待されています。
しかし、リフレクション機能の中でも特に、プライベートメンバへのアクセス制御の扱いに関しての議論が紛糾しています。P2996のリフレクションでは、任意のクラスのプライベートメンバに無制限でアクセスすることができますが、リフレクション機能が既存のC++のルールに反してクラスのアクセス制御を簡単に無視できてしまうことについて反対する声が大きくなっています。
この提案もリフレクションがアクセス制御をバイパスできてしまう現状に反対を表明するものです。
この提案では、クラスのプライベートメタデータ(型のサイズやアライメント、メンバ変数の型とオフセット、など)とプライベートデータ(メンバ変数オブジェクトに格納される値、メンバ関数のアドレス値)へのアクセスを区別して考えることを指摘しています。
プライベートメタデータへのアクセスはすでに型特性メタ関数によってアクセス可能になっていますが、そのためには部分特殊化などのボイラープレートコードを書く必要があり、これはリフレクション機能による自動化対象のタスクです。
これらの理由により、この提案は任意のプライベートメタデータへのアクセスには反対していません。
プライベートデータへのアクセスもその必要性と有用性は認めつつも、許可を得てアクセスすることと無制限かつ無秩序にアクセスできることは異なるとして、特に後者について反対しています。
クラスにはその型固有の不変条件が明示的にも暗黙的にも存在しており、言語が提供するアクセス制御によってそれは通常保護されています。不変条件に関してよく理解しないコンテキストからプライベートデータにアクセスすることは、不変条件を容易に棄損してしまうことになります。プライベートデータの変更を伴わない読み取りのみだったとしても、そのプライベートデータが複数のスレッドからアクセスされるものだったとすると読み取りさえも安全ではありません。
結局、P2996R8の時点でのリフレクション機能は、プライベートメンバは外部アクセスから安全であるという言語が提供する非常に古く根本的な前提を覆そうとしています。
提案には、これらの事を踏まえたうえで既知のアクセス制御をバイパスする機能への賛成意見に対してそれがその機能を正当化しないことを指摘しています(シリアライズライブラリでは型に侵入的なアノテーションが必要なのでオプトインのアクセス許可は苦にならないはずである、など)。
ここでの提案としては、C++26で任意のプライベートデータにアクセスする機能を提供すべきではないとしています。ただし前述のように、任意のプライベートメタデータと許可されたプライベートへのアクセスは許容されます。ただし、この機能を26に間に合わせようとはしておらず、26ではアクセス制御をバイパスする様な機能を一切入れるべきではないとしています。
C++29以降で、任意のプライベートメタデータと許可されたプライベートへのアクセスを許容する方向性としては、いくつかのオプションを提示しています
- スプライシングはアクセス制御を尊重する(P3473R0)
- P3473R0ではスプライシング(
[: refr :]
)式によるアクセスの際に、通常のアクセス制御を適用することが提案されている
- P3473R0ではスプライシング(
- P3547R0の
access_context::unchecked()
を削除したバージョン- P3547R0では、型のメンバへのアクセス権限を表現するための
std::meta::access_context
が提案されている - そこでは、無制限アクセス権限を得るための
access_context::unchecked()
が提案されているが、これを削除したバージョンを受け入れる- ただし、プライベートメンバにアクセスする方法が無くなる
- P3547R0では、型のメンバへのアクセス権限を表現するための
この提案の筆者の方は、1の方法を推奨しています。これは現在のアクセス制御ルールに従ったうえで、許可された場合にのみプライベートデータへのアクセスを許可するものです。これがC++26に含まれたとしても、C++29でプライベートアクセスの方法が変更された場合には簡単に制限を解除することができます。
C++26にアクセス制御をバイパスする機能が導入されて発行された場合、後からそれを削除することは不可能になります。一方で、26に含めずに発行しても29などで後からそれを追加することは容易なことです。そして、アクセス制御をバイパスする機能が無かったとしても、P2996のリフレクションはC++にとってかなり良いインパクトのある機能追加であり、プログラマの生産性を大きく向上させることは間違ない、としています。
SG7の議論と投票においては、おおむねオプション2の方向性が採択されたようです。
P3588R0 Allow static data members in local and unnamed classes
ローカルクラスでstatic
メンバ変数を宣言できるようにする提案。
ローカルクラス(関数内で定義されたクラス)と無名クラスは、その制限としてstatic
メンバ変数を持つことができません。これはその定義を提供する方法がないことから課されていた制限のようです。しかしC++17以降、クラスの静的メンバ変数はinline
定義を持つことができるようになっているため、定義を提供する方法は現在存在しています。
また、N2657の採択によってC++11以降、ローカルクラス/無名クラスはどちらもテンプレートパラメータに渡すことができるようになっています。これはローカルクラス/無名クラスに対して適切な名前マングルの方法が確立されていることを示しており、さらにはローカルクラス/無名クラスのメンバ関数のアドレスを取得できていることから、それらの関数は弱い外部シンボルを持つ必要があるため、やはり関数定義を提供することがすでに可能になっています。
このように、定義を提供できないという実装上の問題点は解消されており、かつ非ローカルクラス(通常のクラス)で静的メンバ変数が有用であるようにローカルクラスでも有用性があるとして、この提案ではローカルクラスおよび無名クラスのこれらの制限を取り払い、静的メンバ変数を宣言できるようにしようとしています。
定義の方法に関しては前述のようにstatic inline
(static constexpr
)変数と同じ方法でinline
定義を提供するようにする方向性のようです。この場合、非inline
な静的メンバ変数は依然として定義を提供する方法がないわけですが、評価されない文脈での使用などで有用であるため、(定義を必要としない使用法に限られるものの)許可するようにすることを提案しています。
一方リンケージ目的のtypedef
名を持つクラスでは許可しないようにしています。
typedef struct { inline static int x = 0; } S;
これは、このようなクラスに対してはすでに、C-likeな性質を達成するために様々な制約が課されているためです。Cには静的メンバ変数というものは存在しないため、これを許可する理由はないようです。
なお、このように許可される変数の初期化順序は、その変数が非ローカルクラスで宣言されている場合と同じ規則に従います。
inline int x = ...; void f() { struct S { static inline int y = ...; }; } struct T { static inline int z = ...; };
この場合、ローカルクラスの静的メンバ変数y
の初期化は、x
の後、z
の前に順序付けられます。この時、f()
がテンプレートであるならば、その初期化は任意のタイミング(おそらくテンプレートがインスタンス化されるとき)に行われます。
この提案の内容は、GCCでは-fpermissive
の場合にすでに利用できるようです(ただし、明示的なinline
が未実装)。clangに対しては筆者の方がパッチを書いたようで、実装に当たっては特に問題ないようです。
P3589R0 C++ Profiles: The Framework
フレームワークとしてのプロファイル機能の概説文書。
C++29以降を目指してプロファイルという機能が検討されています。これは主にC++のコードに特定の保証を追加するためのもので、これによってC++コードの安全性やセキュリティを高めることを目的としています。
この文書は、プロファイル機能のフレームワークとしての部分に着目して大まかに意義や目的等を説明しているものです。特に、個別のプロファイルの詳細についての議論にプロファイル機能そのもの(プロファイルを指定する構文やその意味論)が巻き込まれることで議論が停滞したり発散したりするのを回避するために、具体的な特定のプロファイルから分離されたプロファイル機能そのものに焦点を当てています。
提案文言も含まれていますが、これも標準の変更についてを例示するもので、文言自体はP3081から借りてきているようです。
- P3081R0 Core safety Profiles: Specification, adoptability, and impact - WG21月次提案文書を眺める(2024年10月)
- P3038R0 Concrete suggestions for initial Profiles - WG21月次提案文書を眺める(2023年12月)
- P2687R0 Design Alternatives for Type-and-Resource Safe C++ - WG21月次提案文書を眺める(2022年10月)
- P3589 進行状況
P3590R0 Constexpr Coroutines Burdens
コルーチンのconstexrp
対応を遅らせる提案。
P3367ではコルーチンのconstexpr
対応が提案されていますが、その実装可能性についての懸念が多く上がっているようです。
この提案は、コルーチンのconstexpr
対応の実装についての懸念を簡単に説明したうえで、その採択を当分遅らせることを提案しています。
提案によれば、現在の主要な実装はおそらくすべて、式とconstexpr
関数の解析から構築されたASTをその頂点ノードから再帰的に評価することで定数式実行を実装しているようです。当然ながら、このような定数式の実行方法はパフォーマンスやスタック消費においてかなりのデメリットを抱えています。
また、一部のC++フロントエンドは自身あるいはほかのツールから使用されるコンポーネントとして提供されています(例えばclang)。このため、ASTの表現を外部ツールが解析可能であるように保つ必要があり(それによりconstexpr
コルーチン実行に適した形に変更できない)、またコンポーネントの実行のためにその導入する環境に対して過度の負荷をかけないように配慮されています(追加のライブラリへの依存の忌避や、自身を記述するコードのC++バージョンの制限など)。
これらの理由により、P3367で挙げられているような実装方法は採用されない可能性が高いとしています。
そのうえで、現在のC++の実装では定数式の実行基盤を上記のようなASTの直接評価から、仮想マシンベースの評価機(バイトコードインタプリタのようなもの)へ移行しようとする動きがあるようです。これは、静的リフレクションの導入によって定数式への依存度がかなり高まっていくことが予想されるためです。
ただし、このような移行は少なくとも今後10年の単位での話であり、C++26や29にはとても間に合うものではありません(clangはすでにこの作業を開始しているようですが、既に数年間が経過しています)。
これらの理由から、コルーチンのconstexpr
化はC++26に対しては時期尚早であり、仮に導入したとしても既存の実装は(上記のような以降が完了する)今後10年の大半の間実装することはなく、それによって標準への準拠を放棄するだろうと予想されます。
このため、この提案では定数式の実装が上記のようなVMライクな実装に移項するまでの間、コルーチンのconstexpr
対応を標準化するのは待つべきとしています。
この提案提出後のP3367R3に対するEWGでの投票では、C++26に導入することにコンセンサスは得られなかったものの、C++29導入を目指すことについてはコンセンサスが得られたようです。