文書の一覧
SG22のWG14からのものを除いて、全部で102本あります。
↓
ミューテックスを用いた値への同期アクセスをラップするユーティリティの提案。
この提案のsynchronized_value<T>
はT
の値とミューテックスをペアで保持する型で、保持するT
の値へのアクセスを保持するミューテックスによって同期化するものです。
namespace std::experimental::inline concurrency_v2 {
template<class T>
class synchronized_value {
public:
synchronized_value(synchronized_value const&) = delete;
synchronized_value& operator=(synchronized_value const&) = delete;
template<class ... Args>
synchronized_value(Args&& ... args);
private:
T value;
mutex mut;
};
template<class T>
synchronized_value(T) -> synchronized_value<T>;
}
読み出しと書き込みを直接サポートしていませんが、それがあったとしてもそれだけならstd::atomic
で十分であり、存在理由がありません。
ミューテックスを用いた値アクセスの同期化がアトミックアクセスと異なるところは、ミューテックスのロックと解放によって1度のアクセスを超えた範囲のクリティカルセクションを確保できることにあります。synchronized_value<T>
はそのためにapply()
非メンバ関数を提供します。
namespace std::experimental::inline concurrency_v2 {
template<class F,class ... ValueTypes>
invoke_result_t<F, ValueTypes&...> apply(F&& f, synchronized_value<ValueTypes>&... values);
}
apply()
は、1つ以上のsynchronized_value<T>
とそれと同じ数のT...
の値から呼び出し可能なf
を受けて、f(T...)
の呼び出し前後でvalues
の全てのミューテックスのロックと解放を自動でかつ適切に行うことでT...
の値に対するクリティカルセクション内でf
を実行します。
synchronized_value<std::string> s;
std::string read_value() {
return apply([](auto& x){ return x; }, s);
}
void set_value(const std::string& new_val) {
apply([&](auto& x){x=new_val;}, s);
}
synchronized_value<T>
の保持する値へのアクセスはこのapply()
を通してのみ行うことができ、使用間違いを防ぐために構築以外の操作は提供されていません。
ミューテックスを用いたアクセスの同期化においては同期対象の値とミューテックスオブジェクトがセットで扱われることになることが多いですが、コード上での記述はどうしても複数の変数宣言に分かれてしまうためセットは意味的なものとしてしか表現できません。また、実際のクリティカルセクションの作成においても、std::lock_guard
などである程度自動化できるとはいえ、少なくともロックは手動で行う必要があり、その際に使用するミューテックスも明示的に指定しなければなりません。
synchronized_value<T>
とapply()
を用いると、同期対象の値とそのためのミューテックスのペアを型によって表現することができ、クリティカルセクションの作成においても手動でミューテックスを触る必要がなくなります。これによって、コードの可読性向上や記述ミスの防止などを図ることができます。
提案文書よりサンプルコード
より複雑な処理の例
synchronized_value<std::queue<message_type>> queue;
void process_message(){
std::optional<message_type> local_message;
apply([&](std::queue<message_type>& q) {
if(!q.empty()) {
local_message.emplace(std::move(q.front()));
q.pop_front();
}
}, queue);
if(local_message) {
do_processing(local_message.value());
}
}
複数の値を処理する例
void transfer_money(synchronized_value<account>& from_,
synchronized_value<account>& to_,
money_value amount)
{
apply([=](auto& from, auto& to) {
from.withdraw(amount);
to.deposit(amount);
}, from_, to_);
}
このような複数のsynchronized_value<T>
に対する操作では特に、複数のミューテックスを用いたアクセスにおけるデッドロックを回避できるというメリットもあります。
このように、複数のsynchronized_value<T>
に対して何か関数を適用するという形は、std::tuple
に対するstd::apply()
とよく似たものなので、名前もそこから取っています。
この提案はConcurrency TS v2向けに提案されており、2月のIssaquah会議でConcurrency TS v2に採択されています。
要素が削除されない限りそのメモリ位置が安定なコンテナであるstd::hive
(旧名std::colony
)の提案。
以前の記事を参照
このリビジョンでの変更は、ブロック容量の制限がstd::hive
オブジェクト間でコピーされる条件についてDesign Decisionsセクションに追記し提案する文言に正式に記載した、Appendix Fの修正、Design Decisionsセクションのタイトルを修正、などです。
std::atomic
に対して、指定した値と現在の値の大小関係によって値を書き換えるmaximum/minimum操作であるfetch_max()/fetch_min()
を追加する提案。
以前の記事を参照
このリビジョンでの変更は
- 使用していなかったベンチマークの削除
- 非メンバ関数についてフリースタンディングであることを明記
fetch_max(), fetch_min()
にremarkを追加
- ポインタの比較についてnoteを追加
- ポインタの操作について説明を追記
などです。
この提案はC++26をターゲットして、LWGによるレビューを終えています。次の全体会議で投票にかけられる予定です。
↓
Callableを所有しないstd::function
であるstd::function_ref
の提案。
以前の記事を参照
このリビジョンおよびR13での変更は、LWGのフィードバックによる文言の調整と、フリースタンディング指定の修正などです。
この提案はすでにLWGのレビューをパスして、次の全体会議にかけられることが決まっています(C++26ターゲットです)。
型T
が別の型U
へ縮小変換(narrowing conversion)を起こさずに変換可能かを調べるメタ関数is_convertible_without_narrowing<T, U>
を追加する提案。
以前の記事を参照
このリビジョンでの変更は、LWGのフィードバックの反映、変換元が定数式であることを考慮しないという意図的な選択についての解説を追記したことなどです。
この提案はLEWGのレビューをパスしたLWGに転送されています。
スタックフルコルーチンのためのコンテキストスイッチを担うクラス、fiber_context
の提案。
以前の記事を参照
このリビジョンでの変更は、fiber_context
からstop_token
サポートを取り除いたこと、呼び出し側の提供する未初期化メモリ領域をファイバーのコールスタックとして使用するためのコンストラクタを追加したことです。
stop_token
サポートが取り除かれたのはそれについて実装の懸念が生じたためのようです。各ファイバー(そのコールスタック)自体はfiber_context
の寿命とは無関係な永続的なエンティティですが、fiber_context
はそうではありません。fiber_context
の新しいオブジェクトは常に中断状態で生成され、これによってファイバーを一時停止するコードは関連するstop_source
共有状態を見つけられなくなります。
stop_token
を使用したいユーザーは、自身でstop_source
を管理した上でそこから取得したstop_token
をfiber_context
に渡すラムダ式に渡しておけばよく、fiber_context
で直接サポートする必要はない、とのことです。
namespace std::experimental::inline concurrency_v2 {
class fiber_context {
public:
fiber_context() noexcept;
template<typename F>
explicit fiber_context(F&& entry);
template<typename F, size_t N>
explicit fiber_context(F&& entry, span<byte, N> stack);
~fiber_context();
fiber_context(fiber_context&& other) noexcept;
fiber_context& operator=(fiber_context&& other) noexcept;
fiber_context(const fiber_context& other) noexcept = delete;
fiber_context& operator=(const fiber_context& other) noexcept = delete;
fiber_context resume() &&;
template<typename Fn>
fiber_context resume_with(Fn&& fn) &&;
bool can_resume() noexcept;
explicit operator bool() const noexcept;
bool empty() const noexcept;
void swap(fiber_context& other) noexcept;
};
}
構造化束縛可能なオブジェクトをパラメータパックに変換可能にする提案。
以前の記事を参照
このリビジョンでの変更は、CWGのレビューに伴うフィードバックを反映したことです。
標準ライブラリにいくつかの統計関数を追加する提案。
以前の記事を参照
このリビジョンでの変更は
- オーバーロードを活用して、重み付きと重みなしの関数を呼び分けるようにした
- 導出の表示を簡略化
- 歪度と尖度の導出を追加
- 文言の調整
などです。
std::conditional_t
の定義を修正する提案。
C++14で導入されたconditional_t
は、std::conditional<B, T, F>::type
に簡易にアクセスするためのものです。それは次のような実装になるように指定されています
namespace std {
template <bool B, class T, class F>
struct conditional {
using type = …;
};
template <bool B, class T, class F>
using conditional_t = typename conditional<B, T, F>::type;
}
conditional_t
はconditional<B, T, F>::type
のエイリアスでなくてはならないわけですが、このように指定していることがconditional_t
のより効率的な実装を妨げています。
conditional_t<B, T, F>
の現在の実装では、テンプレートパラメータB, T, F
毎にstd::conditional
のインスタンス化が必要となります。3つのパラメータのうちいずれか1つが異なっているだけで、std::conditional
の新しいインスタンス化が必要となります。これは、conditional_t
を多用する環境において、コンパイル時間の増大やデバッグ情報の肥大化を招きます。
例えば、conditional_t
の実装を次のように変更したとすると
template<bool _Bp>
struct __select;
template<>
struct __select<true> {
template<typename _TrueT, typename _FalseT>
using type = _TrueT;
};
template<>
struct __select<false> {
template<typename _TrueT, typename _FalseT>
using type = _FalseT;
};
template <bool _Bp, class _TrueT, class _FalseT>
using conditional_t = typename __select<_Bp>::template type<_TrueT, _FalseT>;
この実装では、conditional_t<B, T, F>
が異なるパラメータの組み合わせで何度使用されても、インスタンス化されるのは__select<true>
と__select<false>
の2つのクラステンプレートだけです(エイリアステンプレートはインスタンス化されないため)。conditional_t
がどれだけ多用されようともこの2つのクラステンプレートがインスタンス化された後はその定義を使いまわすことができ、最終的な型の決定においてはエイリアステンプレートの実引数による置換だけしか発生しません。これによって、コンパイラのメモリ使用量を抑えるだけでなく、デバッグのために出力するデバッグ情報に記録される型情報も削減することができます。
筆者の方の(Googleにおける)調査では、特にTMPが多用されているファイルに対してclangが出力するデバッグ情報の1部として記録されているクラス名の約1/6がstd::conditional
のインスタンス化で占められていたそうです。
この提案は、これらの理由から、conditional_t
の実装をstd::conditional
から切り離し、より効率的な実装を選択可能にするものです。
ただし、Google社内でこのような変更を行ったところ、この変更は観測可能であることが判明しています。
template<bool B>
long to_long(conditional_t<B, int, long> param);
...
template<bool B>
long to_long(typename conditional<B, int, long>::type param) {
return param;
}
この時、conditional_t
がstd::conditional
によって定義されていない場合、この2つの関数宣言は異なるシグネチャを持つことになり、to_long()
の呼び出しは2つのオーバーロードの間で曖昧となりコンパイルエラーを起こします。
ただし、この例が記述ミスを含むものであるように、このような例はかなり稀であるため実際の影響は非常に小さいと思われます。
標準ファイルストリームに、OSやプラットフォームネイティブのファイルを示すものを取得する方法およびその型エイリアスを追加する提案。
以前の記事を参照
このリビジョンでの変更は、ほぼ設計と提案する文言のみに文書を絞ったこと、対象の型(.native_handle())
を持つ型)としてstd::stacktrace_entry
を考慮し、それを他のものと比較する記述を追記した事です。
C++23で追加されたスタックトレースの1行を表す型であるstd::stacktrace_entry
もまた、その実装のハンドルを取得するために.native_handle()
を持っています。ここから得られるネイティブハンドル型とstd::thread
のそれとを比較して、ネイティブハンドル型について次のような要求を追加することを提案しています
native_handle_type
はsemiregular
でありトリビアルコピー可能かつstandard_layout
型
- ファイルのネイティブハンドルが何を意味しどのように動作するかを定義する
このことは、この提案の対象のファイルハンドルのnative_handle_type
に対してのみ要求されています。
文字列リテラルのエンコーディングを実行時エンコーディングに変換する際、文字表現が失われる場合をコンパイルエラーとする提案。
以前の記事を参照
このリビジョンでの変更は、CWGのフィードバックを適用した事です。
この提案は、CWGのレビューを終えて次の全体会議で投票にかけられる予定です。
std::simd<T>
をParallelism TS v2から標準ライブラリへ移す提案。
以前の記事を参照
このリビジョンでの変更は
hmin()/hmax()
の代替案を提案
<bit>
との一貫性のために、simd_mask
の削減を議論。曖昧さを避けるためによりよい名前を募集
some_of
を削除
simd_mask
に単項~
を追加
- マスク付きオーバーロードの名前と引数順序について議論と回答を追加
fixed_size
/resize_simd
のNTTPをint
からsize_t
へ変更
- ロード/ストアの変換について議論を追加
- P2509R0を関連提案として追加
- ロード/ストアをポインタから
contiguous_iterator
へと一般化
element_reference
の過剰な制約についてOpen questionsに移動
などです。
std::lexicographical_compare_three_way
のRange版を追加する提案。
std::lexicographical_compare_three_way
は、与えられた2つのイテレータ範囲を辞書式順序で三方比較するイテレータアルゴリズムです。この関数はC++20で一貫比較とともに導入されたこともあり、対応するRangeアルゴリズムは用意されていませんでした。
この提案は、それを追加するものです。
namespace std::ranges {
template<
ranges::input_range R1,
ranges::input_range R2,
class Comp = compare_three_way,
class Proj1 = identity,
class Proj2 = identity
>
requires is-lexicographical-compare-three-way-result-ordering<
iterator_t<R1>, iterator_t<R2>, Comp, Proj1, Proj2
>
constexpr auto ranges::lexicographical_compare_three_way(
R1&& r1,
R2&& r2,
Comp comp = {},
Proj1 proj1 = {},
Proj2 proj2 = {}
) -> common_comparison_category_t<
decltype(
comp(proj1(ranges::begin(r1)), proj2( ranges::begin(r2)))
),
strong_ordering
>;
}
他のRangeアルゴリズムと同様に、イテレータ範囲を受け取るものとそれをrange
で受け取るものの2種類が用意され、射影操作をサポートしています。is-lexicographical-compare-three-way-result-ordering
というのは説明専用のbool
定数の変数テンプレートで、それぞれの範囲の要素と比較関数オブジェクトcomp
による比較結果が比較カテゴリ型を返すことを調べるものです。
Allocator Awareなstd::optional
である、std::pmr::optional
を追加する提案。
以前の記事を参照
このリビジョンでの変更は、提案する文言やHTMLの調整です。
この提案はこれ以上議論されません。
Numbers TS (P1889R1)に対して、10進多倍長浮動小数点型std::decimal
を追加する提案。
以前の記事を参照
このリビジョンでの変更はよくわかりませんが、SG6のレビューではこの提案の主張するユースケースに関心がないとして、これ以上議論しないことになったようです。
P0443R14のExecutor提案を置き換える、任意の実行コンテキストで任意の非同期処理を構成・実行するためのフレームワークおよび非同期処理モデルの提案。
以前の記事を参照
このリビジョンでの変更は
- 修正
get_completion_signatures
は一貫性のために、connect
で使用されるものと同様のpromise型で待機可能性をテストするようにした
- コルーチンpromise型は直接クエリするものではなく、環境プロバイダ(environment provider)(
get_env()
を実装するもの)であることを明確化
- 機能拡張
sender
クエリはsender
をget_attrs()
に渡すことによってアクセスされる、個別のクエリ可能な属性オブジェクトに移動される
sender
コンセプトは、get_attrs()
を必要とするように再表現され、ある型が特定の実行環境内でsender
であるかをチェックするsender_in<Snd, Env>
コンセプトから分離されている
- プレースホルダ型
no_env
とdependent_completion_signatures<>
は不要になったため削除された
- 入力
sender
でget_attrs()
を呼び出した結果を永続化するために、ensure_started
とsplit
を変更
scheduler
とreceiver
コンセプトの定義を制約の再帰を回避するように修正
sender_of
コンセプトをより人間工学的で汎用なものに再表現
- エイリアステンプレート
value_types_of_t
とerror_types_of_t
の指定、及び変数テンプレートsends_done
を、新しい説明専用エイリアステンプレートgather-signatures
を使用することで簡潔にした
などです。
この提案での大きな変更は、sender
が直接クエリ可能ではなくなり、get_attrs()
を介して個別のクエリCPOによってクエリを行うように変更されたことです。以前は、scheduler
・receiver
・sender
の3つのものは全て直接クエリ可能でした(クエリCPOに直接渡せた)。R4でreceiver
のクエリは別の環境オブジェクトを介する形に変更され、それはreceiver
をget_env()
に渡して取得できます。環境オブジェクトを介するようにしたのは、型の再帰が起こるのを回避するためでした。
このリビジョンでは、sender
に関しても同様に属性オブジェクトを介して各種クエリCPOに渡して各種特性をクエリするように変更されました。これは、split
とsecure_started
アルゴリズムの設計上の問題解決のためのようです。
クエリCPOをQ
、sender
オブジェクトをs
、receiver
オブジェクトをr
、クエリのための追加の引数をargs...
とすると、sender/receiver
に対するクエリは次のように行えます
Q(get_attrs(s), args...);
Q(get_env(r), args...);
Q
としては、std::get_allocator
(関連づけられたアロケータを取得)やstd::get_stop_token
(関連づけられたstop_token
を取得)、std::execution::get_scheduler
(関連づけられたscheduler
を取得)などがあります。クエリとは、scheduler
・receiver
・sender
などに対してその実行環境に関する情報や実行時に使用するものなどを問い合わせ、取得するための操作です。
このリビジョンでもまだscheduler
は直接クエリ可能であり、それを変更しようとする動機は今のところ無いようです。またそのほかに、operation_state
(receiver
とsender
をconnect
して得られるもの)もこのリビジョンで直接クエリ可能とされています。
この提案は現在LWGでのレビュー中です。
非型テンプレートパラメータの初期化に関しての規定を充実させる提案。
現在に至るまで、非型テンプレートパラメータ(NTTP)の初期化に関しての規定は、指定された初期化子がNTTPの型に変換可能であること、及び、その変換は定数式であること、くらいしか指定されていませんでした。それでも、C++17まではNTTPに取れるのは一部の組み込み型の値に限られていたたためあまり問題にはならなかったようです。
しかし、C++20から非型テンプレートパラメータとしてクラス型のオブジェクトを扱うことができるようになりました。NTTPとして扱えるクラス型には制限があるものの、コンストラクタを持つことができる他ポインタ型のメンバを持つこともできます。すると、左辺値NTTPをとるクラステンプレートの初期化時にそのアドレスを調べることができ、それによってある種のパラドックスが発生します。
template<auto n>
struct B { };
struct J1 {
J1* self = this;
};
B<J1{}> j1;
このJ1自体はNTTPで使用可能なクラス型で、その初期化も問題なさそうに思えます。しかし、J1::self
はthis
によってデフォルト初期化されており、J1{}
とすると初期化にあたって自身のアドレスを要求します。普通の変数としておいた場合などではこれは問題にはならないのですが、ことNTTPだとこれが深刻な問題となります。これは簡単に言えば、J1
のthis
を決めるためにはまずそのNTTPを持っているテンプレートがインスタンス化されなければならず、テンプレートがインスタンス化するためには全てのNTTPの初期化が完了しなければなりません。
これは、テンプレートはインスタンス化に際して(そのオーバーロードの適切な処理、あるいはODRのために)テンプレートパラメータ毎の同一性を判定する必要があり、NTTPの場合はその値の同一性によって判定され、クラス型のNTTPの場合その型名及び全てのメンバの値によって同一性が判定され、ポインタ型の同一性はそのアドレスによって判定されるためです。
現在の(C++20時点の)規定はこのようなことを考慮しておらず、このNTTP初期化に伴う矛盾を解決することができません。
この提案は、この問題を含むNTTPの初期化に関する規定を適切に書き直すことで、いくつかのコア言語Issueを解決するものです。上記問題の他にも、{}
初期化がクラス型NTTPで使えるのかどうか不透明な問題も解決を図っています。
この提案によるアプローチではまず、テンプレート実引数で使用可能な構文(template-argument)として{}
初期化子(braced-init-list)を許可します。
その上で、プレースホルダ型(auto
)あるいはテンプレートパラメータを推論する必要のある形で宣言(C++17 CTAD)されているNTTPの型の推定について次のように変更します。そのような推論を必要とする型名/プレースホルダ(auto
)を仮にD
とすると
D x = template-argument;
D x = E;
ここで、E
はtemplate-argumentかデフォルト引数に指定されている{}
初期化子のいずれかの式です。このような仮のx
の初期化式を構成し、この時にx
の型として推論される型をそのNTTPの型(仮にT
とする)として推定します。
このようにすることで、NTTPのデフォルト引数も含めてNTTPの実引数として{}
初期化子が使用できることを明示的にしています。
次に、NTTPの初期化においては、まず模範(exemplar)となる値をその初期化式(NTTPの実引数A
)から決定します。模範となる値の型U
をT
もしくはT
が参照型ならその参照される型として
U
がクラス型ではなく、A
が波括弧初期化ではない場合
- それ以外の場合
const U v = A;
と初期化される一時変数v
を導入して
- 模範となる値は、
v
そして、NTTPは模範となる値からコピー初期化(copy-initialization)されます。
U
がクラス型の場合、NTTPの同一性は模範となる値v
によって決定されます。
このように、NTTPの初期化のための一時変数(模範となる値)を初期化して、それを用いてNTTPの同一性を判定し、またNTTPの値はそこからコピーして初期化することで、まずテンプレートの同一性が判定されてから初期化が起こるようにするとともに、上記J1
メンバself
のような例では一時オブジェクトのアドレスを保持してしまうためエラーとなるようになります。
提案より、サンプルコード
template<int i>
struct C { };
C<{ 42 }> c1;
struct J1 {
J1* self = this;
};
B<J1{}> j1;
struct J2 {
J2* self=this;
constexpr J2() {}
constexpr J2(const J2&) {}
};
B<J2{}> j2;
<charconv>
とstd::char_traits
をはじめとするいくつかのヘッダをフリースタンディングライブラリ指定する提案。
以前の記事を参照
このリビジョンでの変更は、非推奨とされたerrc/errno
を取り除いたことです。
この提案は既にLWGでのレビューを終えており、次の全体会議で投票にかけられる予定です。
可変長テンプレートの畳み込み式において、() []
の2つの演算子を使用可能にする提案。
以前の記事を参照
このリビジョンでの変更は
pack[...[abc]]
のような一貫性のない畳み込みを禁止
- インデックスの例の構文を修正
pack[...][expr]
のexpr
における式としてassign-or-braced-init-listの代わりにinitializer-clauseを指定
などです。
コンパイル時にのみ使用され、実行時まで残らない文字列リテラルについての扱いを明確化する提案。
以前の記事を参照
このリビジョンでの変更は、CWGレビューに伴うフィードバックの反映です。
この提案は既にCWGのレビューを終えており、次の全体会議で投票にかけられる予定です。
連想コンテナの透過的操作を、さらに広げる提案。
以前の記事を参照
このリビジョンでの変更は、LWGのフィードバックを反映した事です。
この提案はLWGのレビューを完了しており、次の全体会議で投票にかけられる予定です。
↓
↓
std::counted_iterator
を安全に使用可能にする提案。
以前の記事を参照
R3での変更は
counted_iterator
とは異なり、iterator_concept/iterator_category
を定義した
R4での変更は
input_or_output_iterator
からinput_iterator
への変更忘れを適用
- 2つのイテレータが同じシーケンスを参照する場合の単純化
- 後置
++
の定義を単純化
- 追加の設計や疑問点をまとめたセクションを追加
- 実装経験リンクの追加
このリビジョンでの変更は
- LEWGでの投票結果の追記
- 代替設計について追記
- 機能テストマクロの追加
などです。
この提案は、P2799のソリューションによって置き換えられるようで、議論は停止されています。
std::stringstream
がstd::string_view
を受けとれるようにする提案。
以前の記事を参照
このリビジョンでの変更は
- LWGのガイダンスに従って、クラスごとにオーバロードされたコンストラクタの文言をマージした
typename
の代わりにclass
を使用する
- 文言のEffects節のスタイルの調整
などです。
この提案は現在C++26をターゲットとしてLWGのレビュー中です。
std::to_chars_result
/std::from_chars_result
に成否を簡単に問い合わせるためのbool
インターフェースを追加する提案。
std::to_chars_result
/std::from_chars_result
はstd::to_chars()
/std::from_chars()
の結果型で、std::errc
とポインタの2つのメンバを持っています。
多くの場合、それらの結果を構造化束縛で受けて、メンバのerrc
オブジェクトをstd::errc{}
(デフォルト値、成功を表す)と比較することで処理の成否を判断するコードが書かれます。
auto [ptr, ec] = std::to_chars(p, last, 42);
if (ec == std::errc{}) {
...
}
std::errc
は単なるスコープ付き列挙型(enum class
)でしかなく、これ以上に良い書き方は現状ありません。しかし、この比較は少し冗長かつ煩雑で、より読みやすい成功判定方法が求められました。
この提案はそのために、両結果型にoperator bool()
を追加して改善を図るものです。
auto [ptr, ec] = std::to_chars(p, last, 42);
if (ec) {
...
}
if (std::to_chars(p, last, 42)) {
...
}
if (int v; std::from_chars(p, last, v)) {
...
}
namespace std {
struct to_chars_result {
char* ptr;
errc ec;
friend bool operator==(const to_chars_result&, const to_chars_result&) = default;
constexpr explicit operator bool() const noexcept { return ec == errc{}; }
};
struct from_chars_result {
const char* ptr;
errc ec;
friend bool operator==(const from_chars_result&, const from_chars_result&) = default;
constexpr explicit operator bool() const noexcept { return ec == errc{}; }
};
}
この提案はすでにLWGのレビューを終えており、C++26ターゲットとして次の全体会議で投票にかけられる予定です(事務手続きのミスによりC++23にまにあわなかったとのこと・・・)
C++に最小の契約プログラミングサポートを追加する提案。
以前の記事を参照
このリビジョンでの変更は、
- タイトルの変更
- SG21における副作用の許可に関する決定を追記
- 契約条件式からの例外送出についての問題を追記
などです。
2月のIssaquah会議においてSG21は、契約条件に含まれる副作用を認めるとともに、プログラムがそれに依存しないように、契約条件は0回以上呼ばれる可能性があるとすることを決定したようです。これによって、これ以外の副作用の方針(副作用の完全禁止、評価の内側に止まっているもののみ許可、など)は否決されました。
また、現在のところ、契約条件がその評価時に例外を投げた場合にどう扱うかは決まっていません。ある提案ではstd::terminate()
を呼び出して終了することが提案されていましたが否決されており、別の提案では例外送出を契約違反として扱うことが提案されています。
std::variant
に対して、型からそのインデックスを取得するための方法を追加する提案。
以前の記事を参照
このリビジョンでの変更は提案する文言の改善のみです。
標準ライブラリにRead-Copy-Update(RCU)サポートを追加する提案。
以前の記事を参照
このリビジョンでの変更は
- メモリリーク検出機の使用についてユーザーガイドを追加
- 非同期コードとの対話のためのユーザーガイドを追加
rcu_obj_base
の説明から「クライアントが提供するテンプレート引数」という言葉を削除
rcu_obj_base
に5つのコンストラクタ、代入演算子、デストラクタを追加
rcu_obj_base
のテンプレートパラメータはrcu_obj_base
が参照される前に完全型でなければならないようにした
.retire(), .unlock(), rcu_retire()
に関するコメントをNoteに移動
- そのた文言の調整や改善
などです。
この提案はLEWGでのレビューを終えて、LWGに転送されています。
@ $ `の3種類の文字をソースコードの基本文字集合に追加する提案。
以前の記事を参照
このリビジョンでの変更は、よくわかりません。
この提案は既にCWGのレビューを終えて、次の全体会議で投票にかけられる予定です(C++26ターゲットです)。
std::format
において、文字列のアライメント(左寄せ、中央寄せ、右寄せ)の際に空白を埋める文字として使用可能な文字を制限する提案。
以前の記事を参照
このリビジョンでの変更は
- 既存実装の振舞いとして、GCC13での
<format>
実装による出力(エラー)の例を追加
- フィールド幅の単位(field width unit)、最小フィールド幅(minimum field width)、推定フィールド幅(estimated field width)、パディング幅(padding width)の正式な定義の追加
- ↑のための調整
- alignオプションとの一貫性向上のため、
0
オプションの文言の調整
- std-format-specという文法要素を参照するための一貫した用語の導入
- 22.14.2.2 [format.string.std]/11に対して変更していた、コードポイントという用語のUCSスカラ値への変更を削除
- 文言変更の意図を明示的にするためにドラフトメモを追加
- その他フィードバックの適用
などです。
この提案は、2月のIssaquah会議で全体投票をパスしてC++23に適用されています。
std::barrier
のバリアフェーズ完了時処理が、同じバリアで同期する任意のスレッドから起動できるようにする提案。
以前の記事を参照
このリビジョンでの変更は、LWGのフィードバックに基づく文言の修正、機能テストマクロの追加、Annex Cセクション(C++20との非互換)の追記、などです。
この提案は、2月のIssaquah会議で全体投票をパスしてC++23に適用されています。
std::string
とstd::string_view
を+
で結合できるようにする提案。
以前の記事を参照
このリビジョンでの変更は読みやすさの改善のみです。
<chrono>
の時間や日付を表す型に対してハッシュサポートを追加する提案。
以前の記事を参照
このリビジョンでの変更は、LWGのレビュー受けての文言の修正です。
この提案はLWGのレビューを終えて、次の全体会議で投票にかけられる予定です。
static_assert(false)
がテンプレートの実体化前にエラーとならないようにする提案。
以前の記事を参照
このリビジョンでの変更は、これを許可するためのセマンティクスとして考えられる他の方法を追記したことです。
この提案は、2月のIssaquah会議で全体投票をパスしてWDに適用されており、C++11へのDRとなっています。
P2477(コルーチンの動的メモリ確保章竜最適化の制御のための機能の提案)の解説スライド
このリビジョンでの変更はおそらく、P2477の更新に伴う内容の更新です。
↓
射影(プロジェクション)を取るアルゴリズムについて、その制約を緩和する提案。
以前の記事を参照
R2での変更は、indirect_value_t
がネストした射影をハンドルできることを明確にしたことなどです。
このリビジョン(R3)での変更は、提案する文言の修正のみです。
この提案は、2月のIssaquah会議で全体投票をパスしてC++23に適用されています。
std::atomic
のnotify_one()
とwait()
操作を使いづらくしている問題を解消する提案。
以前の記事を参照
このリビジョンでの変更は
- 既存の
notification/wait
操作の非推奨化をやめた
notify_token::notify_one/all
の生存期間に関する言及をNoteに移動
- P2689R1の文言を含めるようにした(採択される場合)
などです。
字句解析するだけで未定義動作を引き起こすものについて、未定義ではなくする提案。
以前の記事を参照
このリビジョンでの変更は、CWGのレビューによるフィードバックの適用です。
この提案は既にCWGのレビューを終えており、C++26に向けて次の全体会議で投票にかけられる予定です。
定数式において、union
のどのメンバがアクティブメンバかを調べるためのstd::is_active_member()
の提案。
以前の記事を参照
このリビジョンでの変更は、提案する機能をunion
のアクティブメンバ検出に限ったものではなく、定数式であるオブジェクトが生存期間内にあるかどうかを調べるstd::is_within_lifetime()
に変更されたことです。
union
の特定のメンバがアクティブメンバであるかを問い合わせるということは、オブジェクトが生存期間内にあるかを問い合わせることの特殊なケースです。以前のstd::is_active_member()
をそこまで一般化させても実装可能性やこの提案の元の同期に対してデメリットもないと判断されたため、アクティブメンバのチェックから特定オブジェクトの生存期間チェックに対象を広げ、関数名もstd::is_within_lifetime()
に変更されました。
とはいえ、使用感はほぼ同じで宣言も変化しません。
namespace std {
template<class T>
consteval bool is_within_lifetime(T*) noexcept;
}
提案にあるOptBool
の例も以前とこの関数名以外変わりありません。
struct OptBool {
union { bool b; char c; };
constexpr OptBool() : c(2) { }
constexpr OptBool(bool b) : b(b) { }
constexpr auto has_value() const -> bool {
if consteval {
return std::is_within_lifetime(&b);
} else {
return c != 2;
}
}
constexpr auto operator*() -> bool& {
return b;
}
};
std::allocator_traits
のユーザーによる特殊化を禁止する提案。
以前の記事を参照
このリビジョンでの変更は、allocate_at_least()
がallocator_traits
の非テンプレートメンバではなくネストしたテンプレートメンバとなっていたのを修正したことです。
この提案は、2月のIssaquah会議で全体投票をパスしてC++23に適用されています。
↓
std::reference_wrapper<T>
とT&
の間のcommon_reference
がT&
になるようにする提案。
以前の記事を参照
R2での変更は、CV修飾されたプロクシ型のcommon_reference
に関する問題を修正したことなどです。
このリビジョン(R3)での変更は、新しい機能テストマクロ(__cpp_lib_common_reference
)を追加し、古いものをリネーム(__cpp_lib_common_reference_wrapper
)したことです。
この提案は、2月のIssaquah会議で全体投票をパスしてC++23に適用されています。
C++実装(コンパイラ)と周辺ツールの相互のやり取りのための国際規格を発効する提案。
以前の記事を参照
このリビジョンでの変更は、SG15での投票を受けて、最初のISの目標を絞り、それらについての解説を追記したことです。
このリビジョンでの最初のISの目標は次の6項目です
- 定義
- 標準の仕様を記述し、またその範囲を制限するための言葉や概念などの定義
- ビルドシステムとパッケージマネージャの相互運用のための基盤
- ビルドシステムとパッケージマネージャが相互に対話するためのメッセージのフォーマットとインターフェースの仕様
- ファイル拡張子
- ツールが認識し理解する必要のあるファイル拡張子の最小セットと、その役割
- イントロスペクション
- ツールがサポートするEcosystem ISのバージョンを問い合わせ、回答するためのフォーマットとインターフェースの仕様
- ポータブルな診断メッセージのフォーマット
- ツールが出力するエラーメッセージ等のユーザーに出力するメッセージのフォーマット
- SARIFという形式を取り込むことを目指す
- コマンドラインの移植性
- 異なるツールやプラットフォームの間で認識可能な、ツールコマンドを表現する共通言語
- C++コンパイルの際の最適な通信手段となる、標準的な構造化応答ファイル形式を定義することを目標とする
これによって、示されているタイムラインの第一段階(計画の策定)はクリアしたことになりそうです。
std::simd
でstd::complex
をサポートできるようにする提案。
以前の記事を参照
このリビジョンでの変更は、SG1での関連する提案の投票結果を追記したことです。
Parallelism TS v2にあるstd::simd
に、permute操作のサポートを追加する提案。
以前の記事を参照
このリビジョンでの変更は、SG1での関連する提案の投票結果を追記したことです。
定数式で確保したメモリ領域を実行時に持ち越すことをできるようにする提案。
以前の記事を参照
このリビジョンでの変更は、propconst
修飾子の代わりにpropconst
指定子を説明し、それをメインの提案としたことです。
以前に提案されていたpropconst
修飾子(qualifier)は、現在メンバ変数宣言に対してconst
を指定する場所に置くことができ、そのポインタのconst
性がdeep constとなるようにするものです。対して、propconst
指定子(specifier)は、現在メンバ変数宣言に対してmutable
を指定する場所に置くことができ、意味は同じになります。
指定子であることの利点は、多重ポインタに対してどこがpropconst
なのかを指定できるようになることです。
propconst
修飾子の場合、int propconst**
やint propconst* propconst*
のように指定することができます。このとき、propconst int** p
という宣言はp
がconst
としてアクセスされる際の意味として、次のような候補があります(propconst
の役割から、const
性は参照先のみを考慮する)
int const* const*
(全てのレイヤでconst
)
int const**
(1層目のみconst
)
int* const*
(2層目のみconst
)
しかし、int const**
はint**
に変換できないため、最後の選択肢のみが残ります。
その上で、std::vector<T>
の実装を考えてみます。std::vector<T>::data() const
はT const *
を返しますが、T
自体がポインタ型(T*
)の場合はT* const *
を返します。これはT* const
へのポインタであり、std::vector<T*>
の要素であるポインタへのポインタかつ、要素がconst
修飾されています。複雑ではありますが、意図通りになっています。
std::vector<T>
の所有するストレージへのポインタ(T* begin_
とする)をpropconst
指定(propconst T* begin_
)したとき、全てのレイヤにconst
を付加するとすると、T const* const*
が得られ、これは本来のT* cosnt*
に変換できません。これによって、std::vector<T*> const
は後方互換を破壊し、実質的に使用できなくなってしまいます。
このことは、propconst
がconst
を適用するのは1番外側のみであることを示唆しています。例えば、propconst int** p
はint* const*
になり、propconst int*** q
はint** const*
になります。
この場合に、簡単な行列型の実装を考えてみます。
struct Matrix3D {
propconst int*** p;
int n;
constexpr ~Matrix3D() {
for (int i = 0; i != n; ++i) {
for (int j = 0; j != n; ++j) {
delete [] p[i][j];
}
delete [] p[i];
}
delete [] p;
}
};
この場合、Matrix3D::p
はint** const*
として扱われます。すると、p
そのものやp[i]
は変化できませんが、真ん中のレイヤは変更可能になります(例えば、p[0][0] = new int(42);
)。これによって、このクラスは定数式で使用することができません。
この例の場合は、外側のレイヤのみではなく一番内側を除く全てのレイヤでconst
が必要になります。
宣言 |
外側のみconst |
一番内側以外const |
propconst int* |
int const* |
int const* |
propconst int** |
int* const* |
int* const* |
propconst int*** |
int** const* |
int* const* const* |
propconst int**** |
int*** const* |
int* const* const* const* |
しかし、std::vector<T**>
ではT** const*
となる必要があり、この方法もうまくいかないことがわかります。
結局、propconst
1つでこれらのユースケースを全て満足することはできません。
そこで、propconst
指定子によって、追加するconst
のレイヤ数を指定できるようにすれば、ほぼ同じ構文によってどこまでconst
を追加するのかを選択できるようになります。
宣言 |
意味 |
propconst int |
int |
propconst int* |
int const* |
propconst(1) int* |
int const* |
propconst(2) int* |
int const* |
propconst int** |
int const* const* |
propconst(1) int** |
int* const* |
propconst(2) int** |
int const* const* |
propconst(3) int** |
int const* const* |
propconst int*** |
int const* const* const* |
propconst(1) int*** |
int** const* |
propconst(2) int*** |
int* const* const* |
propconst(3) int*** |
int const* const* const* |
このpropconst
指定子を使用する場合、std::vector
はpropconst(1)
を常に使用し、先ほどのMatrixND
のようなクラスはpropconst(N)
(もしくは単にpropconst
)を使用することで、const
付加位置の問題を解決し、定数式でも使用可能となります。
また、propconst
修飾子の場合、std::unique_ptr<propconst T>
のような宣言が行えてしまい、この時にメンバ関数が正しく動作するためにはメンバの宣言を変更する必要があります。これは、std::tuple
で使用すると、const
伝播tuple
として使用できるメリットもありますが、より広い部分での後方互換の考慮と既存ライブラリの変更が必要になります。
propconst
指定子の場合そのような使い方はできず、現在のmutable
とほぼ同様の使い方しかできません。しかし、そのメリット(本当にメリットかどうかはわかりませんが)が得られないものの、propconst
の本来の目標である定数式で確保したメモリの実行時への持ち越し(非一時的なメモリ割り当て)の許可の大部分を達成できており、その導入に伴って考慮すべきスコープを狭めています。
これらの理由から、この提案では、R0のstd::mark_immutable_if_constexpr()
でもpropconst
修飾子でもなく、propconst
指定子を非一時的なメモリ割り当ての許可のための言語機能として提案しています。
std::start_lifetime_as
の既存機能等との非一貫性を正す提案。
以前の記事を参照
このリビジョンでの変更は、提案する文言の修正のみです。
この提案は、2月のIssaquah会議で全体投票をパスしてC++23に適用されています。
標準ライブラリにいくつかの統計関数を追加する提案。
以前の記事を参照
このリビジョンでの変更は、線形回帰が将来の別の提案へ遅延された(より大きな、回帰分析ファミリーの一部であるため)ことです。
std::thread::id
とstd::stacktrace
をstd::format()
及びstd::print()
で出力できるようにする提案。
以前の記事を参照
このリビジョンでの変更は、LWGからのフィードバックの適用と、stacktrace_entry
フォーマットのfillオプションの必要性についてのLEWGへの質問を追記した事です。
以前のリビジョンから、stacktrace_entry
のフォーマットではfillオプションが有効とされていました。これは有用である可能性があり、後から追加すると(std::formatter
に状態を追加する必要があるため)ABI破壊を招くことから、そのままにすることがLEWGによって決定されています。
この提案は、2月のIssaquah会議で全体投票をパスしてC++23に適用されています。
C++ Contracts(契約プログラミング)のC++26導入へ向けた予定表。
以前の記事を参照
このリビジョンでの変更は、契約機能の構文決定(R0では2023.02)と契約違反時のサポートするビルドモードの決定(R0では2023.03)の予定を入れ替えたことです。
現在ダングリング参照を生成しているものについて、定数初期化可能なものを暗黙的/明示的に定数初期化する提案。
以前の記事を参照
このリビジョンでの変更は、関連する提案へのリンクを追加したことです。
イテレータを簡単に書くためのヘルパクラスの提案。
以前の記事を参照
このリビジョンでの変更は
- 基底クラスの入れ子型が派生クラスでも定義されているかに関する議論を追加
- 名前の候補を追加
などです。
ローカルスコープの一時オブジェクトの寿命を文からスコープまで延長する提案。
以前の記事を参照
このリビジョンでの変更は、関連する提案へのリンクを追加したこと、TemporariesとDesign Alternativesのセクションを追加したことです。
↓
↓
std::format()
のフォーマット文字列内の置換フィールドが空である場合に、パースを行わなくてもよくする提案。
以前の記事を参照
R1での変更は、ネストしたrange
/tuple
のフォーマットバグ解消のための代替案の比較を追記したことです。
R2での変更は
- オプションなし(
{}
や{:}
)の場合にフォーマット文字列解析をスキップする許可を削除
tuple
要素のフォーマッターのためのparse()
呼び出しを追加
- フォーマット文字列(
{}
内:
の右側)は{
から始まることができないことを明確化
set_debug_format
の文言改善
このリビジョン(R3)での変更は
- 実装可能性の問題の発覚のため、ネストした
range
/tuple
のフォーマットバグ解消の解決方法を変更した事です。
ネストしたrange
/tuple
のフォーマットバグというのは、range
のrange
やtuple
のtuple
などに対するフォーマットにおいて、その要素(内側のrange
/tuple
)に対してデバッグ出力を有効化することができない(本来はするべきだったが実装不可能になっていた)問題の事です。
auto s = fmt::format("{}", std::make_tuple(std::make_tuple('a')));
以前のリビジョンではこの解決のために、ネストした要素型でデバッグ出力が可能な場合にset_debug_format()
を正しく呼ぶように規定、のようなことをしていましたが、その実装が難しいことから、range
とtuple
のフォーマッターにset_debug_format()
を追加し、そのふparse()
はset_debug_format()
を常に呼ぶ、のような基底に変更されました。
この提案は、元の目的(空のフォーマット文字列解析の省略)を失っているためこれ以上追及されないようです。ネストしたrange
/tuple
のフォーマットバグについては、ここでの知見を基にした別のIssue報告によって解決するようです。
ISO 10646(UCS)の代わりにユニコード標準を参照するようにする提案。
以前の記事を参照
R1での変更は、SG16フィードバックによる文言の改善です。
このリビジョンでの変更は、SG16フィードバックによる文言の改善とP2713R1との文言衝突をマージした事です。
この提案は、2月のIssaquah会議で全体投票をパスしてC++23に適用されています。
定数式において、void*
からポインタ型への変換を許可する提案。
以前の記事を参照
このリビジョンでの変更は、文言の修正のみです。
この提案は現在、CWGでレビュー中です。
↓
関数からローカル変数の参照を返してしまうケースをコンパイルエラーにする提案。
以前の記事を参照
R1での変更は、文章の修正です。
このリビジョンでの変更は、関連する提案へのリンクを追加したことです。
この提案は、SG23のレビューでこれ以上時間をかけないことが決定されています。
static_assert
の診断メッセージ(第二引数)に、コンパイル時に生成した文字列を指定できるようにする提案。
以前の記事を参照
このリビジョンでの変更は、コンパイル時の文字エンコーディングに関する議論の拡充と、volatile
型に関する文言の削除です。
↓
戻り値の参照やポインタの有効期間が別の変数の生存期間に依存していることを表明する属性の提案。
以前の記事を参照
R1での変更は、文章の改善とTooling Opportunitiesセクションを追加したことです。
このリビジョンでの変更は、関連する提案へのリンクを追加したことです。
この提案は、SG23のレビューでこれ以上時間をかけないことが決定されています。
規格署中で使用されるcharacterという言葉を正しく使用しなおす提案。
規格署中で使用されるcharacterという言葉は多くの場所で不正確かつ曖昧な用法となっており、また、translation setというC++の規格署でしか使用されない言葉の使用にも問題があります。この提案は、適切な技術用語を使用してそれらを置き換えることで、表現を明確にするとともに解釈を容易にし、誤った意味にとられないようにするものです。
この提案では主に、単体あるいは近い形で使われているcharacterという言葉をUnicodeの用語(Unicode scalar value/Unicode code point)によって置き換えようとしています。一方、文字リテラルや文字型などのC++の要素を指す言葉や、改行文字など既に明確なものは変更しようとはしていません。
この変更は言葉遣いの正確さを向上させるためのもので、何か動作の変更を意図したものではありません。そのため、ほとんどのプログラマには影響はないでしょう。
↓
Cの言語機能の範囲で発生するダングリングポインタを抑制する提案。
以前の記事を参照
R1での変更は、文章の修正です。
このリビジョン(R2)での変更は、サンプルコードの修正です。
この提案は、SG23のレビューでこれ以上時間をかけないことが決定されています。
契約条件のチェックに関する細かいルールの提案。
以前の記事を参照
このリビジョンでの変更は
提案の2.3、2.4、3.4を明確化
- 2.3 : 契約条件式の評価時に例外が投げられる場合は契約違反が発生している
- 契約条件評価時に例外が伝播することを許可すると、
noexcept
が付加されている関数が契約条件をもつ場合、その関数評価を含むような式を渡したnoexcept
演算子は何を返すべきか?
- 想定される振る舞いには、様々な影響があり、それを考慮しなければならない
- 2.4 : 契約条件式がUBを含む場合、欠陥が発生する
- UBによるタイムトラベル(制御フローパスの破棄)が契約条件内で発生する場合、違反ハンドラの呼び出しに置き換えることを意図している
- 3.4 : 複数の契約条件が連続して評価される場合、それらは相互に並べ替えて評価される可能性がある
- 並べ替えられた契約条件の評価は、0ではない回数評価される可能性がある
P2751 進行状況
未初期化自動変数のゼロ初期化に関するいくつかのソリューションをまとめ、比較する提案。
この提案は、P2723R0の提出をうけて寄せられたいくつものフィードバックから特にその代替手段について調査し、それらの特徴や利点を比べることで、P2723とその問題の議論を促進しようとするものです。
P2723R0については以前の記事を参照
P2723R0の目標は、非クラス型の自動変数を初期化する前に不定の値を読み取ることによるセキュリティリスクを排除することです。P2723でもこの提案でも対象は未初期化の自動変数のみであり、その他のもの(動的確保したメモリや構造体のパディングなど)については考慮していません。
提案では、問題を理解するために、ごく簡単な未初期化変数読み取りの例を記載しています
void f1() {
int p;
int q = p + 1;
}
void f2() {
int y;
int z = b ? y + 1 : 0;
}
void g3(int);
void f3() {
int x;
g3(x);
}
void g4(int);
void f4() {
int s;
if (c) s = 0;
g4(s);
}
void g5(int*);
void f5() {
int t;
g5(&t);
}
void f6() {
char buffer[1000];
BufferAllocator a(buffer, sizeof buffer);
std::vector v(&a);
char buffer2[1000];
snprintf(buffer2, sizeof buffer2, "cstring");
}
template <typename T>
void f7() {
T t;
cout << t;
}
この提案で挙げられているこのような問題の解決策の候補は次の7つです
- 常にゼロ初期化
- 非クラス型の自動変数が初期化されない場合、常にゼロ初期化される
- ゼロ初期化もしくは診断
- 無条件に不定値を読む場合は診断(コンパイルエラー)
- 条件次第で不定値を読む可能性がある場合はゼロ初期化
- ソースでの初期化を強制
- 後から初期化されることを考慮しつっつ、ソースでの初期化を強制
- 注釈なしの非クラス型の未初期化変数はill-formed
- 未初期化変数は明示する
- 実装定義の値で初期化するものの、書き込み前の読み取りは未定義動作
- 実装定義の値で初期化するものの、書き込み前の読み取りは誤った動作
- 書き込み前の値の読み取りは誤っているものの、UBではない
- コンパイラフラグなどによって、テストのために検出しやすい値で初期化したり、実運用のために安全な値で初期化したりする
- あるいは、誤った動作を未定義動作として扱うこともできる
- 値初期化に一本化
- 仕様からデフォルト初期化を削除する
- これによって初期化は常に値初期化となり、仕様が単純化され、未初期化を含む初期化周りの問題が解決される
この提案では、これらを実現可能性、下位互換性、表現可能性の3つの観点から比較しています
- 実現可能性 : そのソリューションが既存のC++標準に対して一貫しているかどうか。つまりは、C++標準に適用可能であるかどうか
- 実現可能
- 実現不可能
- 不透明 : 現時点では判断できない
- 下位互換性 : そのソリューションが採用された場合に、既存のコードを壊すことが無いかどうか。
- 互換性がある : 以前にコンパイル可能なコードは引き続きコンパイル可能であり、UBの場合のみ動作が変更される
- 正しいコードと互換性がある : 以前にコンパイル可能でUBを含まないものは引き続きコンパイル可能だが、UBを含むコードはコンパイルエラーとなる場合がある
- 互換性がない : 以前に正しいコードもコンパイルが通らなくなる
- 不透明 : 現時点では判断できない
- 表現可能性 : そのソリューションが採用された場合に、既存コードの意味が変更されるかどうか。
- 良い : 初期化を遅らせる意図を明示、あるいはロジックエラー(初期化忘れ)を修正するためにコードを更新する必要がある
- 悪い : 意図的な初期化遅延とロジックエラー以外の可能性が発生することで、現在よりも状況が悪くなる
- 変わらない : 意図的な初期化遅延もしくはロジックエラーを含むような(未初期化変数を含む)既存コードが曖昧ではなくなる
- 不透明 : 現時点では判断できない
次の表は、先程の7つのソリューションに対してこれを比較したものです
ソリューション |
実現可能性 |
下位互換性 |
表現可能性 |
1. 常にゼロ初期化 |
実現可能 |
互換性がある |
悪い |
2. ゼロ初期化/診断 |
不透明 |
正しいコードと互換性がある |
変わらない |
3. 初期化の強制 |
実現可能 |
互換性がない |
良い |
4. 遅延初期化を考慮した初期化の強制 |
実現可能 |
互換性がない |
良い |
5. 実装定義の値で初期化+その読み取りは未定義動作 |
実現不可能 |
互換性がある |
変わらない |
6. 実装定義の値で初期化+その読み取りは誤った動作 |
実現可能 |
互換性がある |
変わらない |
7. 値初期化に一本化 |
不透明 |
不透明 |
不透明 |
この表から、次のようなことが分かります
- 未初期化変数にまつわるセキュリティホールを塞ぐのに有効であるが、表現可能性が悪くなる
- 現在の初期化されていないローカル変数は、初期化忘れ(ロジックエラー)か意図的な未初期化(遅延初期化のため)のどちらかですが、このソリューションの後での初期化されていない変数は、初期化忘れと意図的なゼロ初期化の区別がつかなくなるため
- ゼロ初期化とコンパイルエラーを組み合わせることは、コンパイラ間で拒否されるコードが変化しうることにつながる
- 意図的な遅延初期化のための注釈(ソリューション4)の有無にかかわらず、既存のコードを修正する多大な努力を強いることになる
- ともすれば、未初期化変数を初期化するようにするスクリプトの安易な使用を招き、それによって元のコードの意図を見失う恐れがある
- 既存のコードベースが抵抗する可能性がある
- 同上
- UBに定義された意味と動作を要求することは不可能
- 欠点が無く最も利得が高いが、誤った動作(erroneous behavior)という新しい概念を標準に導入する必要がある
- 未初期化変数読み出しをEBとして指定することで、ソリューション7のような将来の変更をさたまげない
- より詳細な調査が必要
この提案は、あくまで代替案を含めたソリューションをまとめ比較するもので、どれを推しているわけでもありません。提案というよりも、議論を促すための文書です。
WG21 Direction Group (DG)の安全性についての見解を説明する文書。
以前の記事を参照
このリビジョンでの変更は、寄せられたフィードバックを適用した事です。
std::layout_stride
のデフォルトコンストラクタの生成するレイアウトマッピングを修正する提案。
以前の記事を参照
このリビジョンでの変更は
- LWGの要請により、全てのエクステントが静的であるかにかかわらずデフォルトコンストラクタが同じ振る舞いをするようにした
- デフォルトコンストラクタは常に、
std::layout_right::mapping
と同じ方法でストライドを初期化する
- 必要なスパンサイズがインデックス型で表現可能であることを保証するための事前条件の追加
などです。
この提案は、2月のIssaquah会議で全体投票をパスしてC++23に適用されています。
stashing iteratorというイテレータカテゴリに属している標準ライブラリ内イテレータについて、そのカテゴリや振る舞いを修正する提案。
stashing iteratorとは、その間接参照がイテレータ自身の内部にあるもの(そのイテレータの生存期間と関連付けられているもの)を返すイテレータのことです。stashing iteratorはinput_iterator
にしかならず、forward_iterator
ではstashingは認められません。
stashing iteratorである標準ライブラリのイテレータにはstd::regex_iterator
とstd::regex_token_iterator
がありますが、これらイテレータのカテゴリは現在Forward Iteratorになっており、カテゴリ指定が間違っています。
このイテレータカテゴリの間違いは、C++20のRangeアダプタで使用したときに深刻な問題を起こすことがあります。
#include <ranges>
#include <regex>
#include <iostream>
int main() {
char const text[] = "Hello";
std::regex regex{"[a-z]"};
std::ranges::subrange regex_range(std::cregex_iterator(
std::ranges::begin(text),
std::ranges::end(text),
regex),
std::cregex_iterator{}
);
auto lower = regex_range
| std::views::join
| std::views::transform([](auto const& sm) {
return std::string_view(sm.first, sm.second);
});
for (auto const& sv : lower) {
std::cout << sv << '\n';
}
}
このコードは一見すると問題なさそうに見えますが、実際にはダングリングイテレータが静かに生成されており、アドレスサニタイザーを使用しているとtransform_view
のイテレータの最初の間接参照でheap-use-after-freeが発生します。
std::regex_iterator
はそのメンバとしてstd::match_results
というサブマッチオブジェクトの範囲(std::vector<std::submatch>
のようなもの)を所有しており、間接参照はそのメンバへの参照を返し、イテレータのインクリメントのたびにそのメンバは次のマッチ結果で上書きされます。
std::match_results
自体も範囲であり、これはstd::submatch
の配列のようなものです。そのため、上記のragex_range
は範囲の範囲となっており、それをviews::join
に通すと平坦化によって直接std::submatch
の範囲を得ることができます。
join_view
のイテレータは、入力view
の外側イテレータ(std::regex_iterator
)と内側イテレータ(std::match_results
のイテレータ)をコピーして保持しています。
class iterator {
iterator_t<R> outer;
iterator_t<range_reference_t<R>> inner;
};
class iterator {
std::regex_iterator outer;
std::submatch* inner;
};
このようになっている時にjoin_view
のイテレータをコピーすると、outer
(及び内部のmatch_resultsオブジェクト)もコピーされますが、inner
はコピー元のouter
の要素を参照し続けます。コピー元イテレータのインクリメントや破棄によってそれが寿命を終えると、コピー後のjoin_view
のイテレータはダングリングイテレータとなります。
コピーが起きなければ問題にならないのですが、残念ながら上記例ではtransform_view
のイテレータ構築時にコピーが起きるとともにコピー元イテレータが即座に破棄されており、それによってダングリングイテレータが生成されてしまっています(とのことですが、正直どこでコピーが起きているのかわかりませんでした・・・)。
この問題はregex_iterator
がカテゴリについて嘘をついてるだけではなく、join_view
がstashing iteratorを正しく扱えないことから起こっています。
この提案はこの問題の解決のために、それらに次のような変更を行います
regex_iterator
とregex_token_iterator
のiterator_concept
の変更
iterator_category
の変更は破壊的かつ影響が大きいので行わない
join_view
とjoin_with_view
がinput_itetaror
(input_range
)に対して外側イテレータをそのview
オブジェクト内部にキャッシュするように変更
- stashing iteratorを判定する方法が無いため、全ての
input_iterator
に対して適用
同時に、LWG Issue 3700(begin()
が内側範囲に対してconst
を考慮していない)とLWG Issue 3791(--
内部で内側範囲が右辺値で得られる場合のハンドリング)の2つのjoin_view
/join_with_view
にまつわる小さなイシューも解決しています。
この提案は既に2月のIssaquah会議で全体投票をパスしてC++23に採択されています。
依存関係を追跡することによる時間的なメモリ安全性保証を言語に導入する提案。
時間的なメモリ安全性保証とは、破棄済みのオブジェクトへのアクセスや無効な参照の使用などのことで、例えば次のようなものです
int main() {
std::string s1 = "abcdefg";
std::string_view s2 = s1;
std::cout << s2.length() << " " << s2 << std::endl;
s1.erase(2,2);
std::cout << s2.length() << " " << s2 << std::endl;
}
対して、バッファオーバーランなど有効ではないメモリ領域へのアクセスは空間的なメモリ安全性の問題と言えます。空間的なメモリ安全性はランタイムのチェックや危険な機能(生ポインタやreinterpret_cast
など)の使用禁止などの措置によって比較的簡単に防止することができます。
この提案の目的は、Rustのborrow checkerの導入(C++の現在のプログラミングモデルにそぐわない)やライフタイム注釈のような不十分なものの導入をしなくても、C++をメモリ安全にすることが可能であり、その議論を促すことです。
この提案のアプローチは、オブジェクトの依存関係を追跡することにより時間的なメモリ安全性を保証するものです。例えば、オブジェクトA(reference_wrapper
など)がオブジェクトBに依存するとき、Bが破棄された後にAを使用してはならないこと、あるいは、オブジェクトA(string_view
など)がオブジェクトBのコンテンツに依存している時、Bの変更後にAを使用してはならないこと、を追跡し、それを静的に解析することで時間的なメモリ安全性を保証します。
この提案による安全性保証メカニズムは、何かしらの注釈によってスコープ単位で有効にするオプトインなもので、たとえば次のような構文によります
namespace oldcode {
}
namespace newcode {
[[memorysafety]];
}
namespace newcode {
}
このmemorysafety
な領域内では、同じくmemorysafety
なコードを呼び出す限り、コンパイラは時間的なメモリ安全性を保証します。ただし非メモリ安全なコードを呼び出すとそのチェックは無効化されます。
memorysafety
な領域ではまず、メモリ安全性のために参照(ポインタ含む)のエイリアシングに制約を加えます。例えば次のような普通なコードはエイリアシングを考慮すると安全ではありません
void foo(vector<int>& a, const vector<int>& b) {
if (!b.empty()) {
a.push_back(b.front());
}
}
memorysafety
な領域内ではデフォルトで安全にするために、あるオブジェクトの非const
参照を関数に渡す際には他の参照はそのオブジェクトをエイリアスできなくします。この違反はコンパイラによってチェックされます。
void bar(int x, int y) {
std::vector<int> a, b;
std::array<std::vector<int>, 2> c;
foo(a,b);
foo(a,a);
foo(c[x], c[y]);
auto& r1 = c[x];
auto& r2 = c[y];
if (&r1 != &r2) {
foo(r1, r2);
}
}
エイリアシングを許可したい場合は明示的にコンパイラに表明します。例えば
void swap(auto& a, [[mayalias(a)]] auto& b) { ... }
このswap()
はオブジェクトに対して呼び出されても安全であるとみなされます。
多くのコードは引数がこのようなエイリアシングを起こしていると安全ではないため、エイリアシングを許可するのはオプトインである必要があります。
ただし、この制約は直接のエイリアスにのみ作用し、間接的なエイリアスの問題は依存関係の追跡によって対処されます。
void foo(vector<string>& a, const string& b) {
a.push_back(b);
}
void bar(vector<string>& a) {
foo(a, a.front());
}
オブジェクトが破棄された後にその参照からのアクセスが発生してはなりません。参照がローカル変数のスコープの外側にある場合などはすべての用途について安全であると確信が持てない場合は参照はオブジェクトよりも長く存在してはならない。このチェックのために、オブジェクトと参照の生存期間を追跡する必要があります。
生存期間は依存関係としてモデル化され、グローバルなオブジェクトは(とりあえず)無限の寿命を持ち、ローカルなオブジェクトはそのスコープに応じた寿命を持ちます。ローカルオブジェクトが破棄された時、それに依存する全てのオブジェクトは
- トリビアルデストラクタを持つ
- そのオブジェクトを用いてメンバ関数呼び出しがなされない
必要があります。
void foo() {
int* a;
{
int d = 2;
a = &d;
*a = 3;
}
*a = 5;
}
関数呼び出し時に何かを渡す時、明示的な生存期間に関するアノテーションが必要になる場合があります。
void bar(int* x),
void foo(int& x, int* y) {
int* a = &x;
bar(a);
y = a;
}
ここでは、x
が破棄された後でy
が使用されないことを証明できないため、y
への参照の代入は禁止しなければなりません。許可するためには、依存関係を伝播するために注釈が必要になります
void foo(int& x, [[maycapture(x)]] int* y) {
y = &x;
}
メンバ関数の場合、this
に参照を保存する場合は同様の注釈を関数そのものに行います。また、戻り値で参照を返す場合も関数そのものに注釈が必要です。
[[maycapture(x)]] void Foo::bar(int* x) {
this->y = x;
}
[[dependson(x,y)]] char* foo(char* x, char* y) {
return x < y ? x : y;
}
これらの注釈によって破棄後のオブジェクトにアクセスしている場合を検出することができますが、最初のstring_view
の例のようにコンテンツの変更を検出することはできません。
このために、オブジェクトのコンテンツのキャプチャを明示する注釈を行います
[[dependson(*x)]] std::string_view foo(std::string& x) {
return x;
}
これによって、次のようなルールを確立することができます
- オブジェクトAがオブジェクトBのコンテンツに依存する場合、Bの非
const
関数が呼び出された後でAを使用してはならない
これは、オブジェクトの状態を変更するのは非const
関数のみであり、あるオブジェクトの非const
関数が呼ばれたらそのオブジェクトに依存する全てのオブジェクトは無効になる、という単純なルールです。しかし実際には、非const
でありながらオブジェクトの状態を変更しない関数が存在してるためこのルールは少し厳しいものです。例えば、コンテナのbegin()/end()
などがあり、明らかにbegin()/end()
の呼び出しでその時点で取得されているイテレータを無効にしたくはありません。
そのため、ここでもそれらの関数がnon-mutatingであることをマークする注釈が必要になります。これによって、コンテンツに依存するオブジェクトはそのような非const
関数の呼び出しの後でも有効なままでいることができるようになり、コンパイラはそのような関数が他の注釈なしの非const
関数を呼び出さないように強制する必要があります。
template <class T>
[[dependson(*this), nonmutating]] myvec<T>::iterator myvec<T>::begin() {
return iterator(this->ptr);
}
この上で、次のルールを確立することで、a.push_back(a.front())
のような例を検出することができます
- あるオブジェクトによる関数呼び出しの引数は、そのオブジェクトに対する他の非
const
関数引数に依存してはならない
a.front()
はa
のコンテンツに依存しているので、a.push_back()
に渡すことはできません。
ここまでの形式はほとんどのユースケースに対応していますが、標準ライブラリでも使用されている用例で禁止されているものが1つあります。例えばstd::vector::insert()
の次の様な呼び出しです
a.insert(a.end(), b.begin(), b.end());
ここまでのルールによって、b.begin()
とb.end()
がa
に依存してはならないことが要求され、それは正当なものです。しかし、isnert()
の最初の引数は異なり、依存関係を受け入れる必要があるほかイテレータが同じコンテナのものであることを要求しています。
多くの場合、これを静的に証明することはできず、insert()
に伴うイテレータ無効化によって実装自体も厄介です。ここでは、insert()
の実装には安全ではないコード(およびイテレータがそのコンテナのものであることを検証するアサート)が必要になることを受け入れ、依存関係を許可するためのアノテーションを導入するにとどめています。
template<class InputIt>
[[dependson(*this)]] iterator insert([[maydependon(this)]] const_iterator pos, InputIt first, InputIt last);
ここまでのルールによって、多くの時間的なメモリ安全性を壊すコードを検出することができるようになります。しかし、コンパイル時にすべてのバグを検出するには十分ではありません。エイリアスによって、まだ時間的な安全性を損ねる可能性が残されています
void f1() {
A a;
B b;
C c;
a.push_back(123);
f2(a, b, c);
f3(b);
f4(c);
f5(b);
}
void f2(A& a, [[maycapture(a)]] B& b, [[maycapture(a)]] C& c) {
b.a=&a;
c.a=&a;
}
void f3(B& b) {
b.i = b.a->begin();
}
void f4(C& c) {
c.a->clear();
}
void f5(B& b) {
b.e = *b.i;
}
生存期間の制約は満たされていますが、a
のエイリアスとそれを介した操作によってダングリングイテレータが発生しています。これらの関数が別々の翻訳単位に定義されている場合、これを検出することはできません。この挙動を検出するために非常に精巧なアノテーションを導入することもできるかもしれませんが、それはあまり現実的ではないようです。
その代わりに、この問題は実行時のチェック(サニタイザーのようなもの)によって検出することにしており、このチェックは例えばデバッグ時のみとすることができるようにすることを想定しています。
これらのようにして、コンパイル時になるべく多くの問題を発見しつつ、それが難しい部分は実行時チェックに任せることで、時間的なメモリ安全性を保証します。
コンパイル時定数オブジェクトを生成するクラスとリテラルの提案。
この提案は、P2725のstd::integral_constant
リテラルの提案を受けてのもので、P2725のソリューションではカバーしきれないユーズケースに対応可能なようにP2725の提案を拡張するものです。P2725に関しては以前の記事を参照
P2725がカバーできていないユーズケースとは、次のように、非整数のNTTPをconstexpr
引数として関数に渡したい場合です
template<auto N>
inline constexpr std::integral_constant<decltype(N), N> Const = {};
template<typename T>
struct my_complex {
T re , im;
};
template<typename T>
struct X {
void f(auto c) {
}
};
inline constexpr short foo = 2;
template<typename T>
void g(X<T> x) {
x.f(Const<1>);
x.f(Const<2uz>);
x.f(Const<3.0>);
x.f(Const<4.f>);
x.f(Const<foo>);
x.f(Const<my_complex(1.f,1.f)>);
}
この例の最後の関数g()
内部での最初の4つのx.f()
呼び出しはP2725のstd::integral_constant
リテラル(1ic
など)でも可能ですが、最後の2つの呼び出しはサポートされていません。P2725はあくまで整数定数のNTTPを同様の形で渡すためのリテラルを提案しているだけで、より一般的なNTTP(特に、C++20で許可されたクラス型)を渡すことができません。
関数にcosntexpr
引数を渡したいケースは多くC++20以降は特にそれは整数型に限りません。従って、P2725が解決を目指している問題空間はより広いものであり、P2725の内容だけでは不完全です。また、このようなNTTP渡しにおいてはより簡単に行えることが望ましく、1ic
(P2725のリテラル)とstd::cnst<1>
(上記例のConst
)の両方が必要です。
また、上記例では整数以外のものもstd::integral_constant
に渡していますが、これは現在の定義でも行うことができます。その場合、このソリューションのためにintegral_constant
を引き続き使用すべきではなく、そのための新しい型が必要になります。提案では、次のような型を例示しています
template<auto Value>
struct constexpr_t {
using value_type = decltype(Value);
using type = constexpr_t;
static inline constexpr value_type value = Value;
constexpr operator value_type() const noexcept { return Value; }
static constexpr value_type operator()() noexcept { return Value; }
};
新しい型を追加すれば、P2725で問題となっている単項-
をstd::integral_constant
に追加する破壊的変更についても解決され、また一貫性のために他の演算子も追加することができます。
この提案ではさらに、これらの機能によって可能となるAPIについて記載しています。たとえば
std::array
や固定サイズstd::span
(及び提案中のstd::simd
)などのsize()
メンバ関数をこの定数型(std::integral_constant
/constexpr_t
)に置き換える
- これらの型が
operator()
を持つことによって、APIレベルでは破壊的変更にならない
- メンバポインタを取られている場合のみ破壊的となるが、それは禁止されている
- 非メンバ
operator[]
によって、contiguous
な範囲からスライスを取得するAPI
この提案はまだ問題提起に留まっていますが、LEWGのレビューではこの提案の方向性が支持されているようで、この提案を勘案しながらP2725のソリューションを検討していくようです。
P2728/P2729で提案されているユニコードアルゴリズム(変換・正規化)について、推奨事項等のフィードバックの提案。
P2728/P2729については以前の記事を参照
この提案は、P2728/P2729の提案するユニコードアルゴリズム(文字列範囲に対するアルゴリズム)について、筆者の方の実装経験や考えをもとに推奨事項を記述するものです。
この提案の推奨事項の要旨は次のようなものです
- ほとんどのユニコードアルゴリズムは
view
として公開する必要がある
- ユニコードアルゴリズムは、標準の他の
view
やアルゴリズムとうまく構成できる必要がある
- 正規化・クラスタ化・ケーシング(大文字小文字変換)を最初の作業の焦点とするべき
- UTF相互変換を前提とする(それがなければ何もできない)
- ユニコードアルゴリズムはコードポイント(ユニコード空間上の32bit整数値)で動作する
- 調整済と未調整(Tailored and non-tailored)のアルゴリズムではそれぞれ異なる要件と実装上の課題があるため、類似しつつも別々のインターフェースで公開されるべき
- 調整済アルゴリズムに取り組む前に、ロケール表現についてよく理解しておく必要がある
- Rangeアダプタオブジェクト(
|
構文)は暗黙的なUTFデコード/エンコード手順を導入するのに最も適した場所。このような暗黙の手順はユーザビリティのために必要
char32_t
はコードポイントを表現するのに適切な型である
char
やstd::byte
の消費も可能だが、明示的であるべき
- コードユニット(UTFエンコーディングの1単位の整数値)のシーケンスはデフォルトで検証すべき
- ICUを使用した実装を可能とするために、未調整アルゴリズムを制約すべきではない
- ICU4xは調整済アルゴリズムに対する長期的な最善の答えである
- 将来のユニコードバージョンで変更されうる仮定を公開するのは避けるべき
- プロパティルックアップを最適化し非
sized_range
のメモリを巧妙に確保することで、既存のフレームワークと遜色ないパフォーマンスを実現できる
- ユニコードアルゴリズムは、インプレースの変換や文字列コンテナの恩恵を受けられない
- UTFのデコード/エンコードを回避しても、明確にパフォーマンスが向上するとは限らない
この提案はP2728/P2729の内容を否定したり批判したりするものではなく、ユーザビリティやユニコード固有事情などの面からC++の標準ライブラリとして望ましい方向性を示すものです。
std::string_view
のrange
コンストラクタのexplicit
を条件付きに緩和する提案。
std::string_view
(std::basic_string_view
)に対する文字の範囲から構築するrange
コンストラクタは、C++23で追加されており、そのコンストラクタはexplicit
指定されています。その目的は、文字の範囲を常に文字列として変換することには問題があるためで、文字の範囲からの構築時にその意図を確認するために明示的なものとするためです。
なお、std:string
はstd::string_view
への暗黙変換演算子を備えているため、このコンストラクタと関係なくstd::string_view
へ暗黙変換できます。
しかしこのことによって、独自定義された文字列型をstd::string_view
へ暗黙変換することも禁止されています。
void use(std::string_view v);
std::string str1;
use(str1);
my::string str2;
use(str2);
このような在野のstring
(あるいは同様のstring_view
)型は必ずしも自分が定義したものではなく、別のライブラリに属するものかもしれません。その場合、自分で暗黙変換を提供することもできません。また、この問題は逆に、独自定義のstring_view
をライブラリのインターフェースとしているようなライブラリにおいて、std::string
で問題になる可能性もあります。
void lib::very_useful_algorithm(lib::string_view v);
std::string str;
lib::very_useful_algorithm(str);
これらのコードのコンパイルが妨げられる理由はなく、この提案は、現在のrange
コンストラクタのexplicit
にこれらの文字列型を検出する条件を指定することでこの問題を解決しようとするものです。
その際問題となるのは、どのようにしてそれら在野の文字列型を識別するかという点です。
しかし残念なことに、std::string
とstd::vector<char>
が名前以外ほとんど同じクラスであるように、在野の文字列型を識別することは困難です。そのため、必然的に何かしらの方法でオプトインする手段が必要となります。そのために、次の2つが提案されています
std::ranges::enable_view<T>
またはstd::ranges::view_base
のように、特殊化/継承して有効化する特性の導入
- 文字列型に共通する、何かしらのユニークな特性を利用する
- 入れ子型
traits_type
を活用する(P2499で以前に提案されていたが採用されなかった)
どちらにも
- どちらのオプションでも既存コードからのオプトインのための作業が必要となる。
- ただし、オプション1を実装するコードは存在していないが、オプション2は既に実装しているものが存在する
traits_type
は必ずしも文字列型に固有のものではなく、全く異なる意味で同じ名前を使用している可能性がある
- オプション1は、それが適用され利用可能になるまでに時間がかかり過ぎる
- それが採択され、実装されて利用できるようになるまでに1つのC++リリースサイクルの間問題が解決しない
オプション2の欠点については、std::string_view
のrange
コンストラクタは現在でも制約によって厳密に文字の範囲を判定しており、その判定をパスしたうえでtraits_type
を持つようなクラスというのは実際にはほぼ存在しえないと思われます。この提案では在野のライブラリを調査することで、traits_type
を持たない文字列型はあっても、traits_type
持ちながら(std::string_view
のrange
コンストラクタの制約をパスして)文字列型であると認識される型は見つからなかったようです。
この提案はオプション2を推しており、それをC++23へのDRとすることを提案しています。
値初期化をゼロフィルに置き換えることが安全な型を検出するための型特性を追加する提案。
値初期化とは型の値を初期化するときにゼロ相当の値で初期化することです。値初期化は初期化対象の領域にゼロ相当の値を代入しなければならないため、std::uninitialized_value_construct
アルゴリズムに見られるように、たくさんの要素を値初期化する場合にパフォーマンスが低下する可能性があります。
template<class ForwardIt>
void uninitialized_value_construct(ForwardIt first, ForwardIt last) {
using Value = typename std::iterator_traits<ForwardIt>::value_type;
ForwardIt current = first;
try {
for (; current != last; ++current)
::new std::addressof(*current) Value();
} catch (...) {
std::destroy(first, current);
throw;
}
}
初期化対象領域が連続している(contiguousである)時で、初期化対象の型(Value
)が単純なデータ型の場合、この初期化ループはmemset(ptr, 0, bytes_size)
のようなコードで置き換えることができます。これは初期化する領域サイズが大きければかなりのパフォーマンス向上につながることがあり、コンパイラはこのようなコードに対してそのような最適化を行うことがあります。
単純なデータ型とは例えばint
型のような数値型の事ですが、クラス型の場合はメンバやコンストラクタの宣言の仕方などによって変化するため簡単には判断できません。また、組み込み型でも浮動小数点数型やポインタ型等のそのオブジェクト表現が標準で義務付けられていない型の場合も(ゼロ相当の値がゼロフィルによって生成される値と同等であるかが分からないため)、単純なゼロフィルによって初期化することが適切であるかは分かりづらいところがあります。それらの型がゼロフィルで値初期化可能であるかどうかは最終的には実装あるいはプラットフォームによって決まります。
そのため、コンパイラはこのような最適化が可能かどうかを適切に判断することができ、可能な場合には値初期化ループをゼロフィルに置き換えるコードを出力してくれるのですが、次のような理由によってコンパイラのオプティマイザを信頼するには問題があります
- コンパイラは時々最適化できるはずのコードを見逃すことがある
- 最適化を有効にする必要がある(GCCなら-O2以上など)
- 最適化によってデバッグ効率が低下する
- 最適化はコンパイル時間増大につながる
これらの理由から、標準ライブラリを含めた多くのライブラリ実装がそのような最適化を手動で実装しています。つまり、先ほどのuninitialized_value_construct
の実装のようなコードでは、初期化対象の型がゼロフィルによって安全に初期化することができる(値初期化と同等になる)ことが検出できた場合にのみ、memset
を用いたコードへディスパッチするようにしています。
特に、そのようなコードはBoost.ContainerやFolly、Qtなどのライブラリで見ることができますが、現在のその判定は何かしらの間違いがあるようです。値初期化をゼロフィルに置き換えることが安全ではない型の値初期化をゼロフィルで行ってしまうと、ともすれば深刻で見つかりづらいバグにつながる可能性があります。
この提案は、そのような型の検出を行う型特性を追加することで、それが正しくかつ完全に検出可能となるようにしようとするものです。それによって、標準ライブラリ以外のライブラリにおけるこの手の最適化コードの正確さを向上させることができ、潜在的なバグを削減することができます。
提案されているのは、std::is_trivially_value_initializable_by_zero_filling<T>
という型特性で、これはT
の値で初期化をゼロフィルに置き換えることが安全である場合にtrue
となり(true_type
から派生し)ます。
namespace std {
template<class T>
struct is_trivially_value_initializable_by_zero_filling;
template<class T>
constexpr bool is_trivially_value_initializable_by_zero_filling_v
= is_trivially_value_initializable_by_zero_filling<T>::value;
}
これをtrue
にするT
はtrivially value-initializabile by zero-fillingとよばれる型で、次のいずれかに該当する型として指定されます
- 整数型
- 列挙型
- そのほかのスカラ型で、trivially value-initializabile by zero-fillingである実装定義の型
- trivially value-initializabile by zero-fillingな型の配列型
- trivially value-initializabile by zero-fillingなクラス型
- 資格のある(eligibleな)トリビアルデフォルトコンストラクタを持ち、かつ
- 全ての非静的メンバおよび基底クラスは、trivially value-initializabile by zero-fillingな型
前述のように、浮動小数点数型やポインタ型などはその値初期化される値をゼロフィルによって生成可能であるかが実装定義であるため、この型特性はコンパイラマジックで実装される事になるでしょう。また、同様の理由によりその結果は対称のプラットフォームによって変化する可能性があります。
契約違反が発生した際にすぐに停止せずにプログラムの実行を継続する機能についての提案。
現在進行中の契約プログラミング機能においては、契約違反が起きた場合(契約条件がfalse
を返した場合)にその時点でプログラムを停止させ、他の選択肢を提供していません。契約違反が起きたと言うことはその指定された意図から外れて実行されており、そのまま継続したとしても結果は予測できないものになります(クラッシュや未定義動作など)。このデフォルトは厳しいですが適切なもので、契約違反時の継続モードについてはC++20契約の際にも問題となったことの一つでもあります。
しかし、実際には必ずしもそのデフォルトが最適とは言えない場合があります。たとえば
- プログラム内の分離されたサブコンポーネントの1つで契約違反が起きた時でも、他の部分に影響を与えないことが確信できる場合
- 上記の特殊なケースとして
main()
を1つのサブモジュールと見做した時、1回目のmain()
実行における契約違反が2回目のmain()
実行の正しさに影響しないことは、どうにかしてそのプログラムを再起動させたときにわかる可能性がある
- 単体テストの場合、契約チェックのような安全策の存在を確認するために意図的に契約違反を起こす場合がある
- プログラムが本番環境で長い間使用されており、その制御パスのほぼ全てを使用している可能性が高い場合、プログラムは内部仕様(を表現した契約)に固執しなくてもユーザーの期待通り動作していると確信できる場合
最後の例はともかく初めの3つの場合には、プログラムの実行を止めたくないが、契約違反が検出された場所とは異なる場所からプログラムの実行を再開することができ、それが望ましくすらある、と言う点が共通しています。この提案はこのユースケースに焦点を当てて、これを満足させるために取れるソリューションについて検討するものです。
ユースケースは少し異なりますが、ほぼ同様のことはP2698R0でも提案されており、そこではその方法として例外を用いるEval_and_throwモードを提案しています。ただし、これについてこの提案では次のような問題点や疑問点を提示しています
- 例外送出によってスタック巻き戻しが発生しデストラクタが呼ばれるが、デストラクタもまた契約を持つ可能性があり、同様に契約が破られうる
- スタック巻き戻し中のデストラクタで例外が発生すると、結局
std::terminate()
される
- 契約(特に事前事後条件)の存在と
noexcept
についてが未解決
- 例外を投げうる契約指定に対して
noexcept
はどう言う意味を持つのか、あるいは持たせるのか?
- 例外を無効にしてコンパイルされているプログラムにおいて、契約違反後の継続モードを提供する方法がない
- 例外を無効にしているプログラムは少なくはないが、そのようなプログラムにおいても同じ要求があるはず
この提案の契約違反後の継続モードは、スタック巻き戻しよりも厳しくstd::abort()
よりも柔軟な機構を提案しています。それは、2つの標準ライブラリ関数から構成されます
template <invokable F>
void abortable_component(F&& f);
[[noreturn]]
void abort_component() noexcept;
abortable_component()
は、渡された関数f
をほとんど通常通りに実行しますが、その実行は別のコンポーネントで実行されているものとして扱われます。abortable_component()
は例外中立であり、f
の呼び出し中の全ての例外はここから送出されます。この関数は、プログラマが想定するコンポーネント境界をコンパイラに指示するためのものです。
abort_component()
を呼び出すと、abortable_component()
によって指定されたコンポーネント境界に到達するまで、スタック巻き戻し(それに伴うデストラクタ呼び出し)を伴わずに現在のコールスタックから離脱する処理が開始されます。その処理が完了すると到達したコンポーネント境界の直後、すなわちabort_component()
の呼び出しが発生したabortable_component()
呼び出しの直後の地点からプログラムの実行が再開されます。もしこのとき、対応するコンポーネント境界が見つからない(abortable_component()
の内部ではない)場合は、単にstd::abort()
が呼ばれます(これは、違反後即終了と同じ動作)。
すなわちこれらのものは、例外送出に伴う大域脱出を行いつつもスタック巻き戻しに伴うデストラクタ呼び出しや例外オブジェクトのコピーを回避し(それによってネストする契約違反の発生を回避し)、なおかつ大域脱出は指定されたコンポーネント境界で止まる、と言うことを実現するものです。
提案より、サンプルコード
struct Guard {
~Guard() { std::printf("A"); }
};
int fun() {
Guard g;
std::abort_component();
std::printf("B");
}
int main() {
std::abortable_component(&fun);
std::printf("C");
}
これらの機構はどこでコンポーネントが中断したかの情報やコンポーネントが正しく完了したかについての情報を伝達する方法を持たないほか、abort_component()
呼び出しによるコンポーネントの中断はデストラクタの実行を伴わないことから簡単にリソースリークを発生させ、プログラムの継続を危うくします。
とはいえこれは、契約違反が起きた場合を前提とした、既にプログラムの継続が危うい状況における被害を最小限に抑えるためのツールであり、その場合にやるべきことはプログラムを正しく動作させることではなく、契約違反の原因(おそらくバグ)の影響を最小限に抑える策をとることです。これは危険な機能であり、そのような状況以外で使用することは推奨されません。abortable_component()
をプログラム中に配置することは、それによって呼び出された関数の任意の部分(RAIIも含めて)がスキップされた時でも(あるいはその処理の成否に関わらず)プログラムの実行を継続することが合理的に安全であると、プログラマが判断したことを意味します。
この機構は、現在の契約機能(MVP)に専用のビルドモードを追加する必要がなく、現在のEval_and_abortモードで契約条件違反が検出された際に呼び出されるstd::abort()
をabort_component()
に置き換えるだけでサポートできます(abortable_component()
の呼び出し内部でなければ、その呼び出しはstd::abort()
と等価なため)。
この場合abort_component()
は通常使用可能である関数である必要がない(任意の使用を抑制できる)ほか、MVPの正式な策定(C++26予定)の後から後方互換性を保ちながら導入することもできます。
trivially relocatableをサポートするための提案。
trivially relocatableとは、memcpy
あるいはビット毎コピーによって再配置(relocation)することができる型の性質のことで、relocationとはムーブとムーブ元オブジェクトのデストラクタ呼び出しが複合したかのような操作のことです。この提案は、P1144などで以前に提案されていた同様の性質についてを筆者の方の実装経験をもとに変更・改善を加えるものです。
P1144及び再配置操作については以前の記事を参照
この提案のトリビアルな再配置操作(trivial relocation operation)とは、ソースオブジェクトのストレージが別のオブジェクトによって使用されたかのようにそのソースオブジェクトの生存期間を終了するようなビットコピー操作です。これはムーブ構築の直後にソースオブジェクトを破棄するのと意味的には等価ですが、実際にはソースオブジェクトに対して何も行わず、デストラクタは実行されません(例外は許可されています)。
trivially copyableな型はそれが可能なのはすぐに想像がつきますが、実際には非トリビアルなムーブコンストラクタ/デストラクタを持つような型であっても可能なものがあり、std::vector
やstd::unique_ptr
などの多くのリソース所有型が該当します。トリビアルな再配置操作では、ソースオブジェクトのデストラクタ呼び出しをスキップすることでムーブ先オブジェクトがムーブコンストラクタで行うべきムーブ後操作をスキップすることができます。
現在のC++の仕様においては、trivially copyableではない型でこのようなビットコピーによるムーブは未定義動作となり、再配置操作を行うことができません。
この提案の目的はこのサポートを行うことにあり、次の3つを目標としています
- トリビアルな再配置操作をサポートするために、標準でそれを明確に定義する
- より多くのtrivially relocatableな型を暗黙的にサポートする
- トリビアルな再配置が誤用された場合によりより診断メッセージを出力すること
この提案のP1144との違いは
- トリビアル性は構文ではなく意味から決まる
- P1144は再配置可能という性質をアクセス可能なデストラクタとムーブコンストラクタから構文的に構成され、そのトリビアル性も同様
- この提案の再配置可能性ではトリビアル性が重要であり、言語の他の部分と同様に、ある型のトリビアル性はその全てのメンバと規定クラスのトリビアル性から決定される
- 実装品質に作用されない、予測可能な再配置可能性の指定
- P1144は再配置操作のためにビットコピーを使用すること(トリビアルな再配置操作)を使用する許可を与えるが強制せず、それを使用することは実装品質の問題としている
- また、再配置に適していない型に対する注釈をつける誤用は、UBや診断不要のill-formedとなる
- この提案では、再配置操作に関わるセマンティクスを完全に指定するとともに、機能が誤用された場合に診断可能なill-formedとする
std::swap()
を3つの再配置操作によって実装可能か?
- P1144では、
swap
操作を3つの再配置操作によって実装可能な型のみをサポートするように制限されているが、これによってpmr
コンテナをサポートできなくなっている
- この提案では、そのような制約を要求しない
pmr
型のサポート
- この提案では、
std::pmr
の型に代表されるスコープアロケータモデルをサポートすることを強い動機としている
- P1144では、通常のアロケータモデルしか考慮していない様子
この提案では、relocateやtrivially relocatableと言った性質の意味を定義した上で、非trivially copyableなクラス型に対してそれを明示的に指定するために、trivially_relocatable(expr)
という指定子を導入します。
struct Relocatable trivially_relocatable(true ) {};
struct Alternative trivially_relocatable(false) {};
そして、トリビアルな再配置操作を行うライブラリ関数を導入します
template <class T>
requires is_trivially_relocatable_v<T>
T* trivially_relocate(T* begin, T* end, T* new_location) noexcept;
この関数は、単なるmemcpy
やmemmove
がトリビアルな再配置操作を意図しているかをコンパイラが判断できないため、その意図をコンパイラに伝えることを目的としています。効果は、単にmemmove
を行うだけです。trivially_relocate(&src, &src + 1, &dst)
はsrc
オブジェクトの生存期間が終了しdst
オブジェクトの生存期間が開始されたことを表し、コンパイラ等のツールはそれを認識することができます。
使用されているis_trivially_relocatable_v
は型のtrivially relocatable性を検出するための型特性です。
提案より、診断の例
struct MyType trivially_relocatable : BaseType {
MyType(MyType&&);
};
struct NotRelocatable : BaseType {
NotRelocatable(NotRelocatable&&);
};
struct Error trivially_relocatable : BaseType {
NotRelocatable member;
Error(Error&&);
};
↓
std::generator
のpolymorphic_allocator
を使用するエイリアス、pmr::generator
を追加する提案。
std::generator
は他のアロケータ対応(allocator aware)なコンテナ型等と異なり、生成する要素のためにアロケータを使用するのではなく、コルーチンフレームを保存しておく領域をコルーチンの初期化時に確保するためにアロケータを使用します。そのため、std::generator
は生成要素へのアロケータ伝播を行わず、それはstd::generator
の役割ではありません。
しかし、アロケータのカスタマイズは標準ライブラリの他の型と同じようにテンプレートパラメータで行い、使用可能なアロケータに関しても差異はありません(アロケータオブジェクトの渡し方はコルーチンの事情により少し異なりますが)。したがって、polymorphic_allocator
とmemory_resource
によってアロケータをカスタマイズすることもでき、std::generator
に対してそうしようとするのは自然な発想です。
しかし、std::generator
には3つのテンプレートパラメータがありアロケータは一番最後のパラメータによってカスタマイズしますが、その場合は3つのテンプレートパラメータを全て明示的に指定しなければならず、使いにくくなります。
std::pmr::monotonic_buffer_resource mbr;
std::pmr::polymorphic_allocator<> pa{&mbr};
std::generator<int, void, std::pmr::polymorphic_allocator<>> g = pmr_requiring_coroutine(std::allocator_arg, pa);
template<typename T>
using pmr_genterator = std::generator<T, void, std::pmr::polymorphic_allocator<>>;
pmr_genterator<int> g2 = pmr_requiring_coroutine(std::allocator_arg, pa);
std::pmr
名前空間の意図は、アロケータをカスタマイズ可能な型についてpolymorphic_allocator
をデフォルトとした型名を提供するもので、これまではそういう型しかなかったとはいえコンテナ型などのアロケータ対応型のためだけのものではないはずです。また、std::generator
でpolymorphic_allocator
を使用する場合は上記のようなエイリアスを作成することになるはずです。
この提案は、本来不必要な作業を強いることを回避するために、標準でstd::pmr::generator
エイリアスを用意しておくべきという提案です。
namespace std {
template<class Ref, class V = void, class Allocator = void>
class generator;
namespace pmr {
template<class R, class V = void>
using generator = std::generator<R, V, polymorphic_allocator<>>;
}
}
これによって、std::generator
とほぼ同じ使用感によって、polymorphic_allocator
を使用したgenerator
を使用できるようになります
std::pmr::monotonic_buffer_resource mbr;
std::pmr::polymorphic_allocator<> pa{&mbr};
std::pmr::generator<int> g = pmr_requiring_coroutine(std::allocator_arg, pa);
この提案は、すでに2月のIssaquah会議でC++23向けに採択されています。
名前付きモジュール内で定義されているconst
変数のリンケージを通常の変数と同様に決定するようにする提案。
Cでは定数を宣言する場合にマクロが良く使われますが、C++ではその代わりにconst
変数を使用することができます。
#define MAX_BUNNIES 57
struct bunny bunnies[MAX_BUNNIES];
const int max_bunnies=57;
bunny bunnies[max_bunnies];
ただし、C++においてこのmax_bunnies
のような定数がヘッダで宣言される場合、その定義は複数の翻訳単位に現れる可能性があります。この場合に多重定義の問題を回避するために、C++ではこのような変数に暗黙的に内部リンケージを与えています。ただし、ODR違反を起こす可能性が完全に排除されたわけではありません。
C++17では、inline
変数によってこのような定数をinline constexpr
とすることでODR的に完全に安全な定数を宣言できるようになりました。
また、C++20モジュールでは、inline
はそのリンケージを変更する程度の意味しか持たず、inline
であったとしても定義は翻訳単位全体で1つである必要があるため、同様にODRの問題が解消されます(export
やモジュールリンケージによって参照することで、定義を各翻訳単位に用意する必要が無くなる)。
しかし、const
変数に暗黙に内部リンケージを与える特別扱いはモジュールにおいても残っており、これによってモジュール内部からのそのような変数の参照が不可解な問題を起こします。
module A:B;
const int dimensions = 3;
module A;
import std;
import :B;
using vector = std::array<double, dimensions>;
dimensions
にexport
を付加すると外部リンケージが与えられるため、この問題は同じモジュールの内側に閉じています。
また、このような変数は非内部リンケージの関数から使用することは禁止されています
export module A;
const double delta = 0.01;
template<class F>
export double derivative(F&& f, double x) {
return (f(x+delta)-f(x))/delta;
}
import A;
double d = derivative([](double x) {return x*x;},2);
モジュールにおいては、明示的にinline, extern, export
のいずれかを付加することでリンケージを変更することができますが、これらはそれぞれ少しずつ意味が異なるため、どれを使用するべきかは微妙です
inline
は(標準的には)リンケージの変更のみを行う
extern
は、定義に適用する必要があり、モジュールリンケージを与える
export
は、異なる状況でexport
を取り除いた時だとモジュール内部では影響がない
- 外部リンケージからモジュールリンケージになるだけのはずでは・・・
これらのいずれかの指定を明示的に変更しなければならないということは、非inline
なconst
変数を使用している従来のヘッダーファイルベースのライブラリをモジュールに移行する際の障害となります。
この提案ではこの問題を根本的に解決するために、インポート可能なモジュール本文内では名前空間スコープのconst
変数に暗黙的に内部リンケージを与える仕様を無効化し、通常の変数と同じ方法でリンケージを決定するようにします。これによって、export
が付加されない場合は通常これらの変数はモジュールリンケージが与えられるはずです。
ただし、インポート可能なモジュール本文内とあるように、モジュール実装単位やグローバルモジュール(特にヘッダユニット)ではその扱いは従来と同じとなります。グローバルモジュールは破壊的変更になるため当然として、モジュール実装単位は他の翻訳単位からインポートされることが無くこの変更を適用する意味がないためです。
この提案はC++20へのDRとすることを提案しています。
この提案はNBコメントの解決であることもあり、すでに2月のIssaquah会議でC++20のDRとして採択されています。
2月に行われたIssaquah会議でWDに適用されたライブラリに対するIssue報告の一覧
2月に行われたIssaquah会議でWDに適用されたライブラリに対するIssue報告の一覧。こちらはC++23で新規追加されたライブラリ機能に対するものか、NBコメントを受けてのIssue報告です。
新しいライブラリ機能がテンプレートを使用する場合、その制約をコンセプトによって指定するようにする提案。
C++20でコンセプトが導入されて以降もいくつかの新しいライブラリ機能が標準に導入されていますが、そのテンプレートパラメータの制約は必ずしもコンセプトが使用されているわけではありません。レビューの最中にコンセプトを使うように訂正が入ることもあれば、適格要件(Mandate)で文章と式によって指定されることもあります。
この提案は、今後のライブラリ機能はテンプレートパラメータの制約に必ずコンセプトを使用するように提案するとともに、なぜ現在LWG/LEWGがそうしていないのかを明らかにしようとするものでもあります。
この提案は仮に採択されたとしても、標準のプロセスに適用される問題であり、規格書そのものに何か記述が追加されたりするものではありません。
11月に行われたIssaquah会議でWDに適用されたコア言語に対するIssue報告の一覧。
明示的オブジェクトパラメータを持つメンバ関数とstatic
メンバ関数の曖昧さを解消する提案。
明示的オブジェクトパラメータとは、C++23で導入されたDeducing thisという機能のことで、this
に相当する引数を明示的に記述してメンバ関数を宣言できる構文のことです。
struct S {
int n;
void f(this S& self, int m) {
self.n = m;
}
}
こうして宣言した関数は、普通に使用する分にはメンバ関数のように使用できますが、規格的にはどちらかというと非メンバ関数のような扱いをされています。特に、そのアドレスはメンバ関数ポインタではなく普通の関数ポインタとして取得され、メンバポインタ特有の少し変わった関数呼び出しではなく通常の関数ポインタの用法によって呼び出しができます。
この扱いはstatic
メンバ関数と同様であり、現在の仕様のもとでは関数ポインタ経由で呼び出しを行った際のstatic
メンバ関数との間の振る舞いに仕様の空白地帯が存在しているようです
struct A {
static void f(A);
void f(this A);
void g();
};
void A::g() {
(&A::f)(A());
(&A::f)();
f(A());
f();
}
この例の#1は適切な候補が見つからないためエラーとなりますが、#2がどうなるのかは規定されていないようです。
この例はクラス定義内(非static
メンバ関数内)からの呼び出し例であり、関数ポインタ経由の呼び出しを普通の関数呼び出しに直した場合は、(this
が見えていることから)明示的オブジェクトパラメータを持たないオーバーロード候補のメンバ関数(static
/非static
)はその引数列の先頭に暗黙のオブジェクトパラメータを受け取るかのように(先頭に引数を1つ追加したシグネチャを持つかのように)扱われます。
これはメンバ関数をstatic
に関わらず統一的に扱ってオーバーロード解決するための仕組み(おそらくは処理や規格の記述の共通化のため)ですが、static
メンバ関数の場合はこの暗黙の第一引数には同じく暗黙的にあてがわれているフェイクのthis
引数だけが当てはまり、それを明示的に指定することも参照することもできません。オーバーロード解決においては、static
メンバ関数の暗黙の第一引数はあらゆる型のオブジェクトを無変換で受けられるような型となり、そのマッチングはオーバーロード順位に影響を及ぼしません。
ただしこの扱いは、関数ポインタから呼び出した時には行われず、(&A::f)(A())
はA::f
がオーバーロードされていない場合にのみ適切に呼び出すことができるはずです。ただし、現在の規定ではそれすらも曖昧となっているようです。
C++20までは、上記のA::f()
が(static
問わず)オーバーロードされている場合でも、そもそも関数ポインタを取る時は取得対象の関数が既に確定している必要があり、オーバーロードされている場合はアドレス取得の段階でエラーになるため問題となることはありませんでした。
struct A {
static void f(A) {}
static void g(A) {}
void g() {}
static void h(A) {}
static void h() {}
void i() {}
void call();
};
void A::call() {
f(A{});
(&A::f)(A{});
g(A{});
g();
(&A::g)(A{});
h(A{});
h();
(&A::h)(A{});
i();
(&A::i)();
(this->*(&A::i))();
}
しかし、Deducing thisの導入によって異なる構文を持ちながら実質的に同じようなシグネチャとなりうるメンバ関数宣言構文が追加されたことでこの辺りのことが曖昧になり、その関数ポインタを取得して呼び出そうとしたときにそれらの宣言が衝突するのかどうか(オーバーロードとみなされるのかどうか)が不透明になってしまっているようです。最初の例では、2つのf
がオーバーロードとみなされるのかが不透明であるため、&A::f
からの呼び出しがどうなるのかが不透明となっています((&A::f)()
は、仮に呼び出し可能だったとしても適切な候補がないのでどちらにしてもエラー)。
これらの問題はコア言語のIssueとして報告され、この提案はその解決のためのものです。
提案では、次のようなコードを例に、可能な解決として2つのオプションを提示しています。
struct A {
static void f(A);
void f(this A);
static void e(A const&);
void e() const&;
void g() {
(&A::f)(A());
f(A());
(&A::f)();
(&A::e)(A());
e(A());
(&A::e)();
}
};
void h() {
(&A::f)(A());
f(A());
(&A::f)();
(&A::e)(A());
e(A());
(&A::e)();
}
#1
#2
及び#4
#5
はそれぞれ同じ振る舞いをし(static
メンバ関数を呼び出し)、#3
#6
はill-formed
- 非修飾名による関数呼び出し
(&A::f)
の実際の引数は((A&)*this, A{})
であるため、#A
だけが#1
と#2
の有効な候補となる
- 関数ポインタからの呼び出し時も、
this
が見えてれば非修飾名呼び出しと同じ扱いをする
static
メンバ関数と明治的オブジェクトパラメータを持つメンバ関数の間でオーバーロード成立を回避する
#1
は曖昧、#2
はstatic
メンバ関数を呼び出し、#4
は曖昧、#5
はstatic
メンバ関数を呼び出す
#3
(候補がない) #6
(メンバポインタ呼び出しが必要)はill-formed
(&A::e)
という式でオーバーロード解決するのではなく、まずオーバーロード候補全てのアドレスを取得し、そのポインタで呼び出し式を解決する
#1
#4
は曖昧になり、#2
と#3
は異なる結果となる
static
メンバ関数と明治的オブジェクトパラメータを持つメンバ関数の間でオーバーロードが成立し、衝突しうる
どちらのオプションでも、現在は行なっていない関数ポインタの取得対象の自動解決を行うようになります。
EWGにおける議論の結果としてはオプション2が選択されたようです。これによって(&function-id-expression)(expr-list)
というような関数ポインタによる呼び出し式では、そのポインタ取得先の最適候補を選択する前に、function-id-expression
での探索結果によるオーバーロード候補集合を、その要素のポインタに減衰させてからオーバーロード解決を行い、その結果でもって&function-id-expression
がどの関数のポインタを取得するのかを決定します。
オプション2では現在に引き続いて、this
が見えているスコープでも関数ポインタからの呼び出しで暗黙のオブジェクトパラメータの自動補完のようなことをしないため、その呼び出し結果はthis
が見えているかに関わらず(クラススコープ内外に関わらず)同じになります。よって、#7 #9は曖昧となり、#8 #10はill-formedとなります。
提案より、サンプルコード
struct C {
void c(this const C&);
void c()&;
static void c(int = 0);
void d() {
c();
(C::c)();
(&(C::c))();
(&C::c)(C{});
(&C::c)(*this);
(&C::c)();
}
};
c()
の呼び出し候補は#1~#3全てですが、this
が非const
左辺値であることから#2と#3が最適候補となり(static
メンバ関数に補われた暗黙のオブジェクトパラメータはあらゆる型の値を無変換で受け入れオーバーロード順位に影響しない)、両者の順位がつかないため曖昧となります。(C::c)()
はc()
等価な呼び出しになります。
&(C::c)
はメンバポインタ取得の構文として不正なのでエラーになります。
(&C::c)(C{})
は#1~#3全ての関数ポインタを取得してからオーバーロード解決を行い(それをp
とするとp(C{})
)、#2も考慮対象となりますが、右辺値を受けられるのは#1のみとなります。
(&C::c)(*this)
も上記とほぼ同様の手順を辿り、非const
左辺値にベストマッチするのは#2ですが、この場合に非static
の明示的オブジェクトパラメータを持たない関数が選択されるとill-formedと規定されているためエラーになります。
(&C::c)()
は取得される関数ポインタをp
とするとp()
という呼び出しになり、マッチするのは#3のみです。
この提案は、Issue解決であることもあり、すでに2月のIssaquah会議でC++23へ採択されています。
std::mdspan
のレイアウトマッピングクラスのデフォルトコンストラクタの事前条件を修正する提案。
現在用意されているstd::mdspan
のレイアウトマッピングクラスは3種類(std::layout_left, std::layout_right, std::layout_stride
)あり、それらの::mapping
型はdefault
実装のデフォルトコンストラクタを持っています。そして、このデフォルトコンストラクタは何ら事前条件や制約を持っていません。
例えば次のような極端な例を考えてみると
constexpr size_t N = 4'000'000;
std::layout_left::mapping<std::extents<int, N, N>> map;
レイアウトマッピングクラスは多次元インデックスを1次元配列上のインデックスに変換するようなことを行いますが、その際のインデックスの型はExtents::index_type
が使用され、これはstd::mdspan
のExtents
テンプレートパラメータから与えられます。この型には通常std::extents<I, N, ...>
が使用され、レイアウトマッピングクラスが使用するインデックスの型はここのI
から取得されます。
上記の例ではインデックスの型はint
であり、各次元の静的な要素数N
はint
型に収まっているものの、それを1次元のインデックスにマッピングすると最大でN * N + N
のような計算を行うことになり、これはオーバーフローします。
非デフォルトのコンストラクタでは事前条件によってこの問題に対処しており、この提案はデフォルトコンストラクタも同様に事前条件を追加することによってこの問題に対処するようにしようとするものです。
ただし、各次元のextent(次元ごとの要素数)に1つでも動的なもの(std::dynamic_extent
)を含む場合、それをデフォルト構築するとその次元のextentは0になるため全体の要素数も0、つまり空になるため問題とならず、デフォルトコンストラクタでこの問題があるのはすべてのextentが静的に定まっている場合のみです。
そこで、レイアウトマッピングクラスに指定されたExtents
が全て静的に定まっている(Extents::rank_dynamic() == 0
の)場合、その多次元インデックス値はExtents::index_type
で表現可能であること、が適格要件(Mandates)として指定されるようにします。これは3つのレイアウトマッピングクラスすべてに対して指定されます。
これによって、上記のような例はコンパイルエラーになるようになります。
P2406で報告されている問題について、counted_iterator
の変更による解決は間違っていると指摘する提案。
P2406については以前の記事を参照
P2406で報告されているのは、counted_iterator
を特定の範囲に対して使用するとその終端で意図しない振る舞いをする可能性があり、それを修正するためにlazy_counted_iterator
を提案(あるいはそれをcounted_iterator
に適用)しようとしています。
この提案は、それらの問題の本質はC++のイテレータモデルにそぐわない閉区間の範囲を半開区間の範囲を扱うために設計されたcounted_iterator
で使用しようとしていることにあり、counted_iterator
は半開区間の範囲に対して適切に設計されているため、閉区間の範囲のためにcounted_iterator
を壊す(あるいはlazy_counted_iterator
を追加する)のは間違っている、とするものです。
C++のイテレータは任意の要素からなる半開区間([first, last)
)を表現するものであり、N個の要素からなる範囲にはN+1個のイテレータの値が対応しています。この内N個はN個の要素に対応し、残りの1つは終端(番兵)値に対応します。C++20の範囲(range
)は、このようなイテレータによる範囲の先頭イテレータと終端イテレータのペアとなるもののことであり、これもやはり半開区間の範囲を表現しています。
counted_iterator
は整数カウントをイテレータに結びつけただけのものです。
template <input_or_output_iterator It>
struct counted_iterator {
It it;
int count;
};
counted_iterator
の移動と共にカウントは増減するため、同じ範囲への2つのイテレータ間の距離を簡単に計算できます。
counted_iterator
がカウントダウン(進行するとカウンタを減らす、後退する場合は逆)によってカウントを管理しているのは単にその番兵をステートレスにするためで、終端チェック(番兵値との比較)においてはカウンタが0かどうかをチェックするだけで済むためです。
ここでP2406で提起されている問題に戻ると、これらの問題を引き起こしている入力の範囲は半開区間ではなく閉区間を想定するものであることに気付けます
iota | filter | take
の例
for (auto i : std::views::iota(0)
| std::views::filter([](auto i) { return i < 10; })
| std::views::take(10))
{
std::cout << i << '\n';
}
ここでiota(0) | filter([](auto i) { return i < 10; })
によって生成される範囲はちょうど10個の要素を持ちますが、番兵値に対応する11個目の要素がありません。したがって、その終端を得るために11個目の要素(イテレータ)を計算しようとしていることが問題の根本的な原因です。
istream_view
の例
auto iss = std::istringstream("0 1 2");
for (auto i : std::ranges::istream_view<int>(iss)
| std::views::take(1))
{
std::cout << i << '\n';
}
auto i = 0;
iss >> i;
assert(i == 1);
istream_view
はtake(1)
によってストリームから1つ値を読み取り(0
)それを要素とする範囲を生成しますが、ここでもやはり終端値としてその次の値(1
)を必要とするため、このイテレーションの終了までの間にストリームから2つの値を読み出すことになります。
ここでの問題は、istream_view
がその入力ストリームへの全てのアクセスがそのイテレータを介してしか行われないことを前提としていることから起きており、イテレータを一貫して使用している限り問題は起きません。つまり、元のストリームを直接触りに行く利用者が、istream_view | take(1)
による範囲が半開区間(1要素+1番兵)ではなく閉区間(1要素)だと思ってしまっていることから起きています。前述のようにこの期待は間違っています。
ただ、閉区間による範囲を考慮すると便利な場合もあり、例えばiota_view
はその要素型の最大値を範囲内に含めることができません(番兵値として必要となるため)。ですが、閉区間による範囲は現在のC++イテレータモデルにはそぐわないものであり、counted_iterator
がそのために設計されていないことは当然のことです。
とはいえ、P2406で提起されている問題にもあるように、閉区間の範囲も出現しうるものであり、それをC++のイテレータに適合させて使用しようとするのも自然なことではあります。その場合に必要となるのは、そのようなちょうどN個の要素からなる閉区間をN+1個の要素による半開区間として扱うための方法です。つまり、番兵となる何かの値を添加する必要があります。
その方法としては、例えばvariant<OriginalIterator, PastTheEndSentinel>
のような型の値を使用するなどの方法が考えられ、カウントを使用するのもその方法の一つです。ただし、カウントを使用するのはそのための唯一の方法ではありません。例えば、整数の閉区間の範囲[first, last]
を表すrange-v3ライブラリのclosed_iota
のイテレータは次のようなものになっており
struct iterator {
I current;
I last;
bool past_the_end;
};
このイテレータは進行によってcurrent == last
となる場合にpast_the_end
をtrue
に設定し、current
をインクリメントしないようにしています。
閉区間の範囲を表現し、それを既存のイテレータ/range
として使用できるようにすることには価値がある可能性があります。しかし、counted_iterator
がそのための方法になるべきではありません。特に、そのためにオーバーヘッドを増やし、コンパイラの最適化を阻害し、機能を制限するような変更をC++23作業完了の直前に行うことは閉区間の範囲のサポート方法としては完全に誤っています。
counted_iterator
は半開区間の範囲に対してカウントを結びつけるように設計されており、その想定されるユースケースと設計に問題はなく、閉区間の範囲のサポートのためにはC++26で別の機能として追加することができます。
P1385R7をLEWGのメンバにプレゼンするためのスライド。
P1385R7は行列型をはじめとする線形代数関連のクラス型を標準ライブラリに用意しようとする提案です。詳しくは以前の記事を参照
R0からR7の変遷や、今後の展望などが簡単にまとめられています。
C++26に向けて提案中のstd::simd
クラス型の紹介スライド。
std::simd
については以前の記事を参照
データ並列型としてのstd::simd
のコンセプトから、基本的な使用方法まで非常にわかりやすく紹介されています。
P0876で提案中のスタックフルコルーチンの中核となるfiber_context
の紹介スライド。
P876については以前の記事を参照
fiber_context
の役割や必要性を紹介し、用意されているコンストラクタやメンバ関数について解説が行われています。
値を返せるスコープを導入するdo
式の提案。
C++の構文は文(statement)に基づいて構築されており、if
やfor
などは文であり式(expression)ではありません。文は基本的に値を返すことができず、単一の式は条件分岐やループなどを含むことができません。
変数の初期化時など、単一の式以上のことが必要になる場合はそれを関数にまとめて関数呼び出しに置き換えることで近いことを達成でき、特に即時呼び出しするラムダ式を使用するとかなり通常のブロックに近い形で書くことができます。
int main() {
bool flag = ...;
const int n = [&] {
if (flag) {
...
return -10;
} else {
...
return 10;
}
}();
}
ただし、この方法も問題があり、追加の関数スコープを導入してしまうことで制御フローを複雑化させています。このようなラムダ式がfor
ループの内部に現れている場合、ラムダ内部から外側のループをbreak/continue
することはできず、関数内部で現れている場合はラムダを囲む関数から直接return
することはできないほか、コルーチン内部で現れている場合はラムダ内部から外側コルーチンのco_yield/co_await/co_return
を行うことができません。このような制約を理解するには追加のC++に関する知識が求められるなど、この方法は完璧とは言い難いものです。
この問題はまた、提案中のパターンマッチング(P1371R3)においても問題となる可能性があります。パターンマッチングにおいてはinspect
式のブロック中にpattern => expression;
の形でパターンに対する処理を記述していきますが、ここでも=>
の右辺に指定できるのは単一の式であり、先ほど同様に1つの式以上のことが必要になった時に関数呼び出しに置き換えるなどする必要があります。
P1371ではそのために、=>
の右辺に現れる{ statement }
を特別扱いして、この式をvoid
型の式として評価することで1つの式以上のことを書けるようにしています。ただしこれは言語の他の部分と一貫性がなく、パターンマッチングが{}
初期化式を使用する将来の拡張を妨げることになります。
auto f() -> std::pair<int, int> {
return {1, 2};
return true match -> std::pair<int, int> {
_ => {1, 2}
};
}
このパターンマッチングにおける問題を解決するには、ステートメント式(statement-expression)が必要です。そして、そのようなものはパターンマッチングだけではなく、最初の例のように広く有用なものになります。
このように、現在及び将来の機能でもステートメント式が必要とされていることから、パターンマッチングにおける文法を単純化できるような構文でステートメント式をサポートする直交性の高い言語機能を導入しようとするのがこの提案の目的です。
この提案によるステートメント式は、do { statement }
のような構文で、do
式と呼ばれます。do
式は式なので型と値を持ち、とても単純には次のように使用できます
int x = do { do return 42; };
do
式のブロックが導入するのは単なるブロックスコープであり、そこから値を返すにはdo return
というreturn
文を使用します。ブロックスコープ中には他のブロック同様に任意のC++コードを記述することができ、これは関数スコープではないため新しいスタックフレームを導入せず、その外側の制御フローの一部であり続けます。
do return
は、do
式から値を返すという点を除いてreturn
と同じ振る舞いをし、コピー省略などの戻り値最適化も適用されます。
std::string s = do {
std::string r = "hello";
r += "world";
do return r;
};
do
式の型と値カテゴリは、その内部のすべてのdo return
文から推定されます。これはauto
で戻り値型を宣言した関数/ラムダ式と同じルールによります。また、それらと同様に後置戻り値型を書くことで戻り値型を明示的に指定することもできます。
do -> long { do return 42; }
do
式内でdo return
が使用されていないか、do return;
のようにしか使用されていない場合、式の型はvoid
のpravalueになります。
P2688R0で提案されているパターンマッチング構文(P1371の進化版)にこのdo
式を組み込むことができ、それによって次のような記述が変化します
P2688 |
この提案 |
x match {
0 => { cout << "got zero"; };
1 => { cout << "got one"; };
_ => { cout << "don't care"; };
}
|
x match {
0 => do { cout << "got zero"; };
1 => do { cout << "got one"; };
_ => do { cout << "don't care"; };
}
|
どちらの場合も、このmatch
式の結果はvoid
です。
これによって、=>
の右辺での{}
の特別扱いを回避し、{}
初期化をサポートすることができるようになります
P2688 |
この提案 |
auto f(int i) {
return i match -> std::pair<int, int> {
0 => {1, 2};
_ => std::pair{3, 4};
}
}
|
auto f(int i) {
return i match -> std::pair<int, int> {
0 => {1, 2};
_ => std::pair{3, 4};
}
}
|
do
式の導入するスコープは関数スコープではなく、制御フローはその外側のフローの一部です。従って、do
式からのreturn
やbreak
はその外側の制御フローに対して効果を持ちます。
int outer() {
int g = do {
if (cond) {
do return 1;
}
return 3;
};
}
void func() {
for (;;) {
int j = do {
if (cond) {
break;
}
for (something) {
if (cond) {
do return 1;
}
}
do return 2;
};
}
}
このdo
式の導入するスコープは関数スコープ(関数・ラムダ・コルーチン)と異なる点があり、do
式がvoid
型の式ではなくdo return
が現れる前にその終端にたどり着いた場合はill-formed
とされます。関数スコープの場合はこれは未定義動作とされていました。これによって、ユーザーはdo
式内の制御パスを全てカバーするように注意する必要があり、それができていないとコンパイラに怒られます。
int i = do {
if (cond) {
do return 0;
}
};
do
式の型がvoid
の場合は暗黙的にdo return;
が補われエラーにはなりません。
また、do return
以外にも、do
式終端にたどり着かないことがわかっているものが全ての制御パスに現れていればエラーにはなりません。それは例えば
- 外側の制御フローへ戻るもの
return
breake
continue
co_return
throw
式
[[noreturn]]
関数
- 現在でも
[[noreturn]]
とマークされた関数から制御が戻ると未定義動作となる
- この未定義動作を利用して、新しい未定義動作を導入することなく
[[noreturn]]
関数の呼び出しを制御フローからの脱出と見做せる
enum Color {
Red,
Green,
Blue
};
void func(Color c) {
std::string_view name = do {
switch (c) {
case Red: do return "Red"sv;
case Green: do return "Green"sv;
case Blue: do return "Blue"sv;
}
};
}
int main() {
auto a = do {
if (cond) {
do return 1;
} else {
do return 2;
}
};
int f = do {
if (cond) {
do return 1;
}
throw 2;
};
int h = do {
if (cond) {
do return 1;
}
std::abort();
};
}
また、goto
文の使用はdo
式からの脱出のみ許可され、do
式内のラベルへのジャンプは禁止されます。
この提案はEWGでの最初のレビューにおいて引き続き議論していくことにコンセンサスが取れています。
std::simd
に対するintelの経験に基づくフィードバック提案の解説スライド。
対象の提案については以前の記事を参照
かく提案のうち既に解決済みの問題や、未解決のものの理由や利点欠点などを解説しています。
グローバルモジュールにある内部リンケージを持つエンティティの曝露(exposure)を許容するようにする提案。
static inline
関数のような、内部リンケージを持つエンティティを名前付きモジュールの本文内で使用する場合、それをその翻訳単位の外部に曝露しないように注意しなければなりません。内部リンケージを持つものの曝露とは、export
したinline
関数などから内部リンケージを持つものが翻訳単位外部から参照される可能性がある場合を言います。曝露が起きている場合はコンパイルエラーとなります。
曝露については以前の記事を参照
ただ、static inline
関数のような内部リンケージエンティティは特に、次のような理由からCヘッダで一般的に使用されています
- Cの
inline
セマンティクスはC++のそれとは異なり、どこかに非inline
定義が必要となる
- 内部リンケージを持つことから、コンパイラが認識したものと異なるものを取得することがなく、ABIの分離を実現できる
- これによって、ABIの破損を気にすることなくその動作を変更できる
このようなCヘッダはC/C++間のコード共有のためのものでもあり、そのままモジュールに移行されることはなく、おそらくヘッダユニットのインポートやグローバルモジュールフラグメントでのインクルードなどによって名前付きモジュール内から利用されることになるでしょう。
その場合、ヘッダで定義されている内部リンケージエンティティ(特にstatic inline
関数)はグローバルモジュールに属する内部リンケージエンティティとなりますが、名前付きモジュールのインターフェースからの暴露に関する制約は名前付きモジュールに属する内部リンケージエンティティと同じ扱いとなります。従って、そのようなものを普通に使う感覚でinline
関数や関数テンプレートから参照してしまうとコンパイルエラーを引き起こします。
static inline int f(int n) {
...
}
module;
#include "myheader.h"
export module mymodule1;
export inline int func1(int n) {
return f(n);
}
inline int func2(int n) {
return f(n);
}
static inline int func3(int n) {
return f(n);
}
export module mymodule2;
import std;
import "myheader.h"
template<std::integral I>
export inline int func1(I n) {
return f(n);
}
重要なのは、このようなものはC/C++のコード共有地点で現れるもので、ABI分離等の利点があり、C++の都合のみで変更できるものではなく、また名前付きモジュールに属していない(名前付きモジュールの一部ではない)ということです。さらに、そのようなC/C++共有ヘッダの内部リンケージエンティティはGithubで公開されているいくつかの大規模なプロジェクトだけでも数千件も発見でき、潜在的にはさらに多く利用されていることが予想されます。
P2691R0ではヘッダユニットのstatic inline
関数に限って同様の問題を報告しており、そこではこの問題は以前に予想されたよりも影響が大きく、深刻なモジュール採用の障害になっていることを報告しています。
この問題を要約すると、Cヘッダにある内部リンケージエンティティをグローバルモジュールを介して名前付きモジュールのインターフェースから使用する際、曝露を回避して使用しなければならない(あるいはそれが困難)、ということです。この提案は、そのようなものを含む既存コードーベースの円滑なモジュールへの移行のために、この問題を解決しようとするものです。
この問題の解決のためには、次のことを達成する必要があります
- 問題となっているエンティティを他の翻訳単位から参照できるようにする
- 既存コードを壊さない
- UB(すなわちODR違反)を増加させない
- 実装可能であること
その上でこの提案では、次のような変更によってグローバルモジュールにある内部リンケージエンティティが名前付きモジュールインターフェースから曝露されるのを許可します
- インポート可能なヘッダをインポートするすべての翻訳単位は、そのヘッダの独自のヘッダユニットを取得する
- 各翻訳単位のグローバルモジュールフラグメントとすべてのヘッダユニットに対して、次の変換を適用する
- 内部リンケージを持つすべてのエンティティは、インポート先の翻訳単位に属するモジュールリンケージが与えられる
- 内部リンケージを持つすべてのエンティティは、この変換が適用されない場合のエンティティと区別される
- 内部リンケージを持つすべての関数と変数は
inline
化される
1つ目の変更によって、ヘッダユニットのインポートは翻訳単位ごとに異なるヘッダユニットを生成し使用するようになります。これによって、翻訳単位が異なれば同じヘッダを示すヘッダユニットをインポートしていても、異なるヘッダユニット(翻訳単位)を使用することになります。
2つ目の変更は、主にモジュールのインターフェース単位において行われ、Cヘッダからの内部リンケージエンティティはグローバルモジュールではなくそのモジュールに属するモジュールリンケージを持つエンティティとして扱われ(これによって同じモジュール内の別の翻訳単位から使用できるようになり)、かつマングル名(モジュール内部でのみ有効な)レベルで明確に区別されるとともに、関数と変数はインライン展開されることで直接定義を参照することを回避します。ただし、この変換はどうやら非モジュールにおいても行われるようで、その場合はその翻訳単位とインポートするヘッダユニットを含む匿名モジュールが生成されたかのような扱いをされるようです。
この解決策は、先ほどの4つの要件を全て満たしています。例えば、内部リンケージのエンティティはモジュールのインターフェースでモジュールリンケージを持つようになり曝露の制限対象から外れますが、翻訳単位ごとの実体生成とインライン化によりABI分離も保たれています。実装に関しては、少し問題があるものの主要なコンパイラの開発者から実装可能であるとの確認を取れているようです。
P2546で提案されているis_debugger_present()
をユーザーが置換可能にする提案。
std::is_debugger_present()
は実行時にデバッガがアタッチされている場合にtrue
を返す関数で、これはフリースタンディング環境でもサポートされることを目指しています。しかし、組み込み環境などの一部のフリースタンディング環境ではこの実装が困難となる場合があります。
この機能はフリースタンディング環境でも有用である可能性があり、フリースタンディング環境で削除してしまうとその判定と代替手段のためにプリプロセッサが使用されることになり、C++エコシステムのCPP依存を高めます。
そのため、そのような環境でもこの関数を動作させるための方法が必要であり、この提案はその方法としてこの関数をユーザーが置き換えることを許可することを提案しています。
これによって、ユーザーはフリースタンディング環境以外の環境においても、そのユースケースに従ってstd::is_debugger_present()
を柔軟にカスタマイズすることができるようになります。例えば
- アプリケーションの検証ビルドでは
true
を返すようにしておく
- 外部入力によって結果を制御する
- キー入力やその他の外部信号、シグナルハンドラのシグナルなど
ここでの置き換えとは、std::is_debugger_present()
という関数シグネチャを衝突させる形でユーザーコードで定義し、実装はそれを検出したらデフォルトの実装をユーザー定義のものに置き換える、のようなことです。
P1673で提案中の線形代数ライブラリの解説を行う文書。
主に、LEWGのレビューにおいてその設計がどのように変化したかを記述しています。
P2188R1の解説スライド。
P2188R1で主張されているポインタの保証や意味論(必ずしも現在のC++が保証していないもの)についての詳しい解説がなされています。
P2188R1については以前の記事を参照
C++を安全なプログラミング言語へと進化させることについて、その必要性及び方法について解説したスライド。
主に、P2687で提案されていることのベースとなっている考えについて詳細に説明されています。
おわり
この記事のMarkdownソース