C++17 で(非順序)連想コンテナに追加された以下の新機能の紹介です。
・ユニークキーマップの挿入インタフェース向上 (Improved insertion interface for unique-key maps)
・(非順序)連想コンテナの接合 (Splicing Maps and Sets)
C++17 の新機能(非順序)連想コンテナ編2018/8/23 鳥頭かりやマン1
View Slide
連想コンテナおいしい!!!(特に非順序)2注:個人の感想です
ユニークキーマップの挿入インタフェース向上3
ユニークキーマップの挿入インタフェース向上4N3873 Improved insertion interface for unique-key mapshttp://www.open-std.org/jtc1/sc22/wg21/docs/papers/2014/n3873.htmlN4006 An improved emplace() for unique-key mapshttp://www.open-std.org/jtc1/sc22/wg21/docs/papers/2014/n4006.htmlN4240 Improved insertion interface for unique-key maps (Revision 2)http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2014/n4240.htmlN4279 Improved insertion interface for unique-key maps (Revision 2.3)http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2014/n4279.html
ユニークキーマップの挿入インタフェース向上5ユニークキーマップ?
ユニークキーマップの挿入インタフェース向上6ユニークキーマップ?⇓mapunordered_map
ユニークキーマップの挿入インタフェース向上7multimapunordered_multimapは対象外もちろん set 系も…
ユニークキーマップの挿入インタフェース向上8導入の背景
ユニークキーマップの挿入インタフェース向上 導入の背景9既存の emplace の仕様が曖昧でツラい…
ユニークキーマップの挿入インタフェース向上 導入の背景10???
ユニークキーマップの挿入インタフェース向上 導入の背景11問題①:次のコードのアサーションは通るか?std::map> m;m[42] = nullptr;auto ptr = std::make_unique();m.emplace(42, std::move(ptr)); // キー重複だから// 挿入されないけど…assert(ptr);提案ペーパーから(改)
ユニークキーマップの挿入インタフェース向上 導入の背景12答え未規定
ユニークキーマップの挿入インタフェース向上 導入の背景13ちな、GCC:失敗するClang(~3.8.1):失敗するClang(3.9.1~):成功するWandbox調べ
ユニークキーマップの挿入インタフェース向上 導入の背景14何で?既にキーがあるんだからClang 3.9.1~の方が正しいんじゃないの?
ユニークキーマップの挿入インタフェース向上 導入の背景15お前は map さんの気持ちを全くわかってない
ユニークキーマップの挿入インタフェース向上 導入の背景16問題②:次のコードのアサーションは通るか?std::map> m;m["foo"] = nullptr;auto ptr = std::make_unique();m.emplace("foo", std::move(ptr)); // キー重複だから(ryassert(ptr);提案ペーパーから(改)
ユニークキーマップの挿入インタフェース向上 導入の背景17GCC:失敗するClang:失敗するWandbox調べ
ユニークキーマップの挿入インタフェース向上 導入の背景18???
ユニークキーマップの挿入インタフェース向上 導入の背景19map さんの気持ちを考える
ユニークキーマップの挿入インタフェース向上 導入の背景20• 挿入するかどうかを判断するためには既存のキーと挿入するデータのキーを比較する必要がある。• emplaceの引数はvalue_type(=std::pair)のコンストラクタ引数だから、引数にキーと直接比較可能な値が存在するとは限らない。
ユニークキーマップの挿入インタフェース向上 導入の背景21ふつうの map さん(GCCとか)挿入するかどうかわからないけど、比較するためにとりあえずメモリアロケートして value_type を構築しちゃおう。⇓たとえ挿入しなくても、右辺値参照はムーブされちゃったりする。
ユニークキーマップの挿入インタフェース向上 導入の背景22がんばる map さん(Clang 3.9.1~とか)value_type を構築しなくても引数と直接比較できる時は構築しないで頑張ろう。直接比較できない時はしょうがないので構築しよう。⇓ムーブされる条件は結構複雑。
ユニークキーマップの挿入インタフェース向上 導入の背景23いずれにしろ、最終的に引数がどうなっているのかが良く分からないいやまぁ実装調べればわかるけど、バージョンによって変わるかもしれないし…
ユニークキーマップの挿入インタフェース向上24回避策
ユニークキーマップの挿入インタフェース向上 回避策251. find を使う2. lower_bound を使う(でも map だけ)
ユニークキーマップの挿入インタフェース向上 回避策261. findを使うauto it = m.find("foo");if (it == m.end()) {it = m.emplace("foo", std::move(p)).first;}欠点:キーが存在しない場合、おんなじ検索が2回される、MOTTAINAI
ユニークキーマップの挿入インタフェース向上 回避策272. lower_boundを使うauto it = m.lower_bound("foo");if (it == m.end() || m.key_comp()("foo", it->first)) {it = m.emplace_hint(it, "foo", std::move(p)).first;}欠点:我らが愛する unordered_map には使えない。毎回こんなん書くの面倒だし覚えてられなくてバグりそう。
ユニークキーマップの挿入インタフェース向上28対応
ユニークキーマップの挿入インタフェース向上 対応29新しいメンバ関数追加しよ575
ユニークキーマップの挿入インタフェース向上 対応30template pair try_emplace(const key_type& k, Args&&... args);template pair try_emplace(key_type&& k, Args&&... args);template iterator try_emplace(const_iterator hint, const key_type& k, Args&&... args);template iterator try_emplace(const_iterator hint, key_type&& k, Args&&... args);
ユニークキーマップの挿入インタフェース向上 対応31ポイント既存の emplace とは異なり、キーの引数 k がkey_type で独立しているので、value_type を構築しなくても必ずキーの比較が可能⇓挿入しない場合は、引数は絶対不変!
ユニークキーマップの挿入インタフェース向上 対応32問題➂:次のコードのアサーションは通るか?std::map> m;m[42] = nullptr;auto ptr = std::make_unique();m.try_emplace(42, std::move(ptr));assert(ptr);提案ペーパーから(改)
ユニークキーマップの挿入インタフェース向上 対応33答え常に通る(ヤッター!)
ユニークキーマップの挿入インタフェース向上 対応34問題④:次のコードのアサーションは通るか?std::map> m;m["foo"] = nullptr;auto ptr = std::make_unique();m.try_emplace("foo", std::move(ptr));assert(ptr);提案ペーパーから(改)
ユニークキーマップの挿入インタフェース向上 対応35答え常に通る(ヤッター!)
ユニークキーマップの挿入インタフェース向上 対応36素朴な疑問点何で emplace の規約を変更しないでメンバ関数追加にしたの?
ユニークキーマップの挿入インタフェース向上 対応37提案ペーパーでの回答しれっと変えちゃうと、対応してる処理系と対応してない処理系をユーザが判別するのが難しくなって、バグの温床になりかねないから。
ユニークキーマップの挿入インタフェース向上 対応38今までもいろいろしれっと変えてきてると思うんだけどなぁ…
ユニークキーマップの挿入インタフェース向上39詳細
ユニークキーマップの挿入インタフェース向上 詳細40template pair try_emplace(const key_type& k, Args&&... args);k:キー値。単体でコンテナ内の他の要素のキーと比較可能。このキーと同値のキーを持つ要素がコンテナ内に存在しない場合、新たな要素のキーがこの引数からコピー構築※される。args:対応する値のコンストラクタの引数。k と同値のキーを持つ要素がコンテナ内に存在しない場合、新たな要素の値がこの引数から直接構築※される。存在した場合は args は不変。戻り値(iterator):新たに追加した要素(追加した場合)、あるいは、k と同値のキーを持つ要素(追加しなかった場合)。戻り値(bool):要素を追加した場合 true、追加しなかった場合 false。※ 実際にはちょっと違って全引数でpiecewise_constructされる…
ユニークキーマップの挿入インタフェース向上 詳細41template pair try_emplace(key_type&& k, Args&&... args);k:キー値。単体でコンテナ内の他の要素のキーと比較可能。このキーと同値のキーを持つ要素がコンテナ内に存在しない場合、新たな要素のキーがこの引数からムーブ構築※される。存在した場合は k は不変。args:1個目のオーバーロードと同じ。戻り値(iterator):1個目のオーバーロードと同じ。戻り値(bool):1個目のオーバーロードと同じ。※ 実際には(ry
ユニークキーマップの挿入インタフェース向上 詳細42template iterator try_emplace(const_iterator hint, const key_type& k, Args&&...args);hint:k で指定したキー値をコンテナから検索する際のヒント。適切なヒントを指定すれば検索が早く終わる。ただし、我らが非順序連想コンテナの場合は完全に意味なし(何で非順序にも追加したし…)。k:1個目のオーバーロードと同じ。args:1個目のオーバーロードと同じ。戻り値:1個目のオーバーロードの iterator と同じ。何で bool 返さないんですかね…
ユニークキーマップの挿入インタフェース向上 詳細43template iterator try_emplace(const_iterator hint, key_type&& k, Args&&... args);hint:3個目のオーバーロードと同じ。k:2個目のオーバーロードと同じ。args:1個目のオーバーロードと同じ。戻り値:1個目のオーバーロードの iterator と同じ。何で bo(ry
ユニークキーマップの挿入インタフェース向上 詳細44あ、計算量は emplace、emplace_hint と同じです
ユニークキーマップの挿入インタフェース向上 詳細45注意キーは、直接構築じゃなくて、メンバ関数呼び出し前に構築、その後必要に応じてコピー・ムーブになるまぁ引数が key_type 型なんで当たり前なんですが…
ユニークキーマップの挿入インタフェース向上46不満点
ユニークキーマップの挿入インタフェース向上 不満点47異種混合比較コンテナの場合でも、キー用の引数は key_type になっちゃう…(つまりキーが直接構築されない)
ユニークキーマップの挿入インタフェース向上 不満点48異種混合比較コンテナ???
ユニークキーマップの挿入インタフェース向上 不満点49C++14で追加されたこんなヤツstd::map, std::less<>> m;auto res = m.find("foo"); // "foo" から std::string が作られない(残念ながら我らが愛する非順序連想コンテナには無い…)
ユニークキーマップの挿入インタフェース向上 不満点50問題⑤:次のコードを順に実行すると、std::string オブジェクトはそれぞれ何個作られるか?std::map> m;m. emplace("foo", 42); // 何個?(挿入されるm. emplace("foo", 42); // 何個?(挿入されないけどm.try_emplace("bar", 42); // 何個?(挿入されるm.try_emplace("bar", 42); // 何個?(挿入されないけど
ユニークキーマップの挿入インタフェース向上 不満点51答えstd::map> m;m. emplace("foo", 42); // 1個m. emplace("foo", 42); // 1個(悲しい…)m.try_emplace("bar", 42); // 2個(悲しい…)m.try_emplace("bar", 42); // 1個(悲しい…)
ユニークキーマップの挿入インタフェース向上 不満点52注意キーは、たとえ異種混合比較コンテナでも、直接構築じゃなくて、メンバ関数呼び出し前に構築、その後必要に応じてコピー・ムーブになるしつこい…
ユニークキーマップの挿入インタフェース向上53実装
ユニークキーマップの挿入インタフェース向上 実装54Clang HEAD maptemplate pair try_emplace(const key_type& k, Args&&... args) {return __tree_.__emplace_unique_key_args(k,piecewise_construct,forward_as_tuple(k),forward_as_tuple(forward(args)...));}何か一生懸命やってそう…実際 __emplace_unique_key_args では k による検索は1回しか走らないし、もちろん既にキーが存在すれば args も無傷。ちな、通常の insert や emplace でも引数からキーに直接アクセスできる場合にはコイツが呼ばれてる。(でも piecewise_construct だと呼ばれない…)
ユニークキーマップの挿入インタフェース向上 実装55Clang HEAD unordered_maptemplate pair try_emplace(const key_type& k, Args&&... args) {return __table_.__emplace_unique_key_args(k,piecewise_construct,forward_as_tuple(k),forward_as_tuple(forward(args)...));}何か一生懸命やってそう…てか map と完全に一致…
ユニークキーマップの挿入インタフェース向上 実装56GCC HEAD maptemplate pair try_emplace(const key_type& k, Args&&... args) {iterator i = lower_bound(k);if (i == end() || key_comp()(k, (*i).first)) {i = emplace_hint(i, piecewise_construct, forward_as_tuple(k),forward_as_tuple(forward(args)...));return {i, true};}return {i, false};}えぇ…回避方法のまんまやし、もうちょっと頑張って欲しい…
ユニークキーマップの挿入インタフェース向上 実装57GCC HEAD unordered_maptemplate pair try_emplace(const key_type& k, Args&&... args) {iterator i = find(k);if (i == end()) {i = emplace(std::piecewise_construct, std::forward_as_tuple(k),std::forward_as_tuple(std::forward(args)...)).first;return {i, true};}return {i, false};}えぇ…提案の意図ほぼ汲んでなくて検索2回走るし GCC は悔い改めて欲しい…
ユニークキーマップの挿入インタフェース向上58完
ユニークキーマップの挿入インタフェース向上59と思うじゃないですか…
ユニークキーマップの挿入インタフェース向上60導入の背景
ユニークキーマップの挿入インタフェース向上 導入の背景61指定したキーが無かったら挿入、あったら更新、がしたい
ユニークキーマップの挿入インタフェース向上62回避策
ユニークキーマップの挿入インタフェース向上 回避策631. find を使う2. lower_bound を使う(でも map だけ)3. operator[] を使うさっき似たようなの見たような…
ユニークキーマップの挿入インタフェース向上 回避策641. findを使うauto it = m.find("foo");if (it == m.end()) {it = m.emplace("foo", std::move(p)).first;} else {it->second = std::move(p); // 違うのはここだけ}欠点:キーが存在しない場合、おんなじ検(ry
ユニークキーマップの挿入インタフェース向上 回避策652. lower_boundを使うauto it = m.lower_bound("foo");if (it == m.end() || m.key_comp()("foo", it->first)) {it = m.emplace_hint(it, "foo", std::move(p)).first;} else {it->second = std::move(p); // 違うのはここだけ}欠点:我らが愛する unordered_map には使えない。毎回こんなん書くの面倒だし覚え(ry
ユニークキーマップの挿入インタフェース向上 回避策663. operator[] を使うm["foo"] = std::move(p);欠点:mapped_typeがデフォルト構築可能じゃなきゃならない。デフォルト構築されてから代入されるから、デフォルト構築が高価だとムダ感マジ半端ないって。あと、今までキーが存在してたかどうかが分からない。
ユニークキーマップの挿入インタフェース向上67対応
ユニークキーマップの挿入インタフェース向上 対応68新しいメンバ関数追加しよ575(nページぶり2回目)
ユニークキーマップの挿入インタフェース向上 対応69template pair insert_or_assign(const key_type& k, M&& obj);template pair insert_or_assign(key_type&& k, M&& obj);template iterator insert_or_assign(const_iterator hint, const key_type& k, M&& obj);template iterator insert_or_assign(const_iterator hint, key_type&& k, M&& obj);
ユニークキーマップの挿入インタフェース向上 対応70ポイント普通の insert とは異なり、キーが存在した場合には代入が走る普通の insert とは異なり、キーと値が引数レベルで分離されてる
ユニークキーマップの挿入インタフェース向上 対応71素朴な疑問点何で emplace じゃなくてinsert って名前なの?何で variadic じゃないの?
ユニークキーマップの挿入インタフェース向上 対応72提案ペーパーの回答キーが無い場合の構築は emplaceでもいいけど、キーがあった場合の代入演算子は引数1つしかないやろ?なるほどな?
ユニークキーマップの挿入インタフェース向上73詳細
ユニークキーマップの挿入インタフェース向上 詳細74template pair insert_or_assign(const key_type& k, M&& obj);k:キー値。単体でコンテナ内の他の要素のキーと比較可能。このキーと同値のキーを持つ要素がコンテナ内に存在しない場合、新たな要素のキーがこの引数からコピー構築※される。obj:対応する値のコンストラクタの引数。k と同値のキーを持つ要素がコンテナ内に存在しない場合、新たな要素の値がこの引数から直接構築※される。存在した場合は要素の値はこの引数からムーブ代入される。戻り値(iterator):新たに追加した要素(追加した場合)、あるいは、k と同値のキーを持つ要素(追加しなかった場合)。戻り値(bool):要素を追加した場合 true、追加しなかった場合 false。※ 実際には(ry
ユニークキーマップの挿入インタフェース向上 詳細75template pair insert_or_assign(key_type&& k, M&& obj);k:キー値。単体でコンテナ内の他の要素のキーと比較可能。このキーと同値のキーを持つ要素がコンテナ内に存在しない場合、新たな要素のキーがこの引数からムーブ構築※される。存在する場合は不明…(普通に考えたら不変だと思うんだけど…)obj:1個目のオーバーロードと同じ。戻り値(iterator): 1個目のオーバーロードと同じ。戻り値(bool): 1個目のオーバーロードと同じ。※ 実際には(ry
ユニークキーマップの挿入インタフェース向上 詳細76template iterator insert_or_assign(const_iterator hint, const key_type& k, M&& obj);hint:k で指定したキー値をコンテナから検索する際のヒント。適切なヒントを指定すれば検索が早く終わる。ただし、我らが非順序連想コンテナの場合は完全に意味なし(何で非順序にも追加したし…) 。k:1個目のオーバーロードと同じ。obj:1個目のオーバーロードと同じ。戻り値:1個目のオーバーロードの iterator と同じ。
ユニークキーマップの挿入インタフェース向上 詳細77template iterator insert_or_assign(const_iterator hint, key_type&& k, M&& obj);hint:3個目のオーバーロードと同じ。k:2個目のオーバーロードと同じ。obj:1個目のオーバーロードと同じ。戻り値:1個目のオーバーロードの iterator と同じ。
ユニークキーマップの挿入インタフェース向上 詳細78あ、計算量は emplace、emplace_hint と同じですさっきも聞いたような…
ユニークキーマップの挿入インタフェース向上 実装79Clang HEAD maptemplate pair insert_or_assign(const key_type& k, Vp&& v) {iterator p = lower_bound(k);if (p != end() && !key_comp()(k, p->first)) {p->second = forward(v);return make_pair(p, false);}return make_pair(emplace_hint(p, k, forward(v)), true);}えぇ…回避方法のまんまやし、もうちょっと頑張って欲しい…
ユニークキーマップの挿入インタフェース向上 実装80Clang HEAD unordered_maptemplate pair insert_or_assign(const key_type& k, Vp&& v) {pair res =__table_.__emplace_unique_key_args(k, k, forward(v));if (!res.second) {res.first->second = forward(v);}return res;}何か一生懸命やってそう…てか try_emplace に代入追加しただけ…
ユニークキーマップの挿入インタフェース向上 実装81GCC HEAD maptemplate pair insert_or_assign(const key_type& k, Obj&& obj) {iterator i = lower_bound(k);if (i == end() || key_comp()(k, (*i).first)) {i = emplace_hint(i, std::piecewise_construct, std::forward_as_tuple(k),std::forward_as_tuple(std::forward(obj)));return {i, true};}(*i).second = std::forward(obj);return {i, false};}えぇ…もうちょっと頑張って欲しい…(てか引数2個固定なんだからpiecewiseいらんやろ
ユニークキーマップの挿入インタフェース向上 実装82GCC HEAD unordered_maptemplate pair insert_or_assign(const key_type& k, Obj&& obj) {iterator i = find(k);if (i == end()) {i = emplace(std::piecewise_construct, std::forward_as_tuple(k),std::forward_as_tuple(std::forward(obj))).first;return {i, true};}(*i).second = std::forward(obj);return {i, false};}えぇ…提案の意図ほぼ汲んでなくて検索2回走るし GCC はマジ悔い改めて欲しい…
ユニークキーマップの挿入インタフェース向上83おまけ
ユニークキーマップの挿入インタフェース向上 おまけ84機能テストマクロ__cpp_lib_map_try_emplace 201411L__cpp_lib_unordered_map_try_emplace 201411Lmap と unordered_map で別々に定義されているのね…(C++17 以降使ってれば使う機会無いけど…)
ユニークキーマップの挿入インタフェース向上85完
(非順序)連想コンテナの接合86
(非順序)連想コンテナの接合87N3586 Splicing Maps and Setshttp://www.open-std.org/jtc1/sc22/wg21/docs/papers/2013/n3586.pdfN3645 Splicing Maps and Sets (Revision 1)http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2013/n3645.pdfP0083R0 Splicing Maps and Sets (Revision 2)http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2015/p0083r0.pdfP0083R1 Splicing Maps and Sets (Revision 3)http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2015/p0083r1.pdfP0083R2 Splicing Maps and Sets (Revision 4)http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2016/p0083r2.pdfP0083R3 Splicing Maps and Sets (Revision 5)http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2016/p0083r3.pdf
(非順序)連想コンテナの接合88接合?
(非順序)連想コンテナの接合89接合?⇓splicing良い日本語があったら教えてください…
(非順序)連想コンテナの接合90導入の背景
(非順序)連想コンテナの接合導入の背景911. 連想コンテナの要素をコンテナ間で移動したい!2. キーを変更したい!
(非順序)連想コンテナの接合導入の背景921. (非順序)連想コンテナの要素をコンテナ間で移動したい!std::map s, d;s.emplace(42, "foo");s.emplace(114514, "bar");// ここで s の42の要素を d に移動したい…
(非順序)連想コンテナの接合導入の背景931. (非順序)連想コンテナの要素をコンテナ間で移動したい!std::map s, d;s.emplace(42, "foo");s.emplace(114514, "bar");auto it = s.find(42);d.insert(std::move(*it));s.erase(it);
(非順序)連想コンテナの接合導入の背景94auto it = s.find(42); // まぁしょうがないd.insert(std::move(*it)); // d側でメモリ割当&ムーブs.erase(it); // s側でメモリ開放&デストラクタメモリ割当&解放とか、ムーブコンストラクタ&デストラクタとか、控えめに言ってムダじゃね?しかもこれじゃキーはムーブできてない…map じゃなくて set だとそもそもムーブできない…
(非順序)連想コンテナの接合導入の背景952. キーを変更したい!std::map m;m.emplace(42, "foo");m.emplace(114514, "bar");// ここで m の 42 のキーを 893 に変更したい…
(非順序)連想コンテナの接合導入の背景962. キーを変更したい!std::map m;m.emplace(42, "foo");m.emplace(114514, "bar");auto it = m.find(42);m.emplace(893, std::move(it->second));m.erase(it);
(非順序)連想コンテナの接合導入の背景97auto it = m.find(42); // まぁしょうがないm.emplace(893, std::move(it->second));// メモリ割当&ムーブm.erase(it); // メモリ開放&デストラクタメモリ割当&解放(ry
(非順序)連想コンテナの接合98対応
(非順序)連想コンテナの接合対応99新しいメンバ関数追加しよ575(nページぶり3回目)
(非順序)連想コンテナの接合対応100コンテナから指定されたノードを切り離すメンバ関数(extract)と、切り離したノードをコンテナに追加するメンバ関数(insert)を追加。
(非順序)連想コンテナの接合対応101ノードを切り離すメンバ関数node_type extract(const key_type& x); // 対象をキーで指定node_type extract(const_iterator position); // 対象をイテレータで指定切り離したノードを追加するメンバ関数insert_return_type insert(node_type&& nh); // ユニークコンテナの場合※iterator insert(node_type&& nh); // 非ユニークコンテナの場合※iterator insert(const_iterator hint, node_type&& nh);// 挿入位置のヒント付き※ ユニークコンテナだとキー値重複で失敗する可能性があるので戻り値がちょっと違う
(非順序)連想コンテナの接合対応102ポイント切り離されたノードは「ノードハンドル」に格納されて、元のコンテナとは独立して管理される。切り離されたノードは、「ノードハンドル」を通して map のキー値や set の要素値を変更可能。
(非順序)連想コンテナの接合対応103ノードハンドル?
(非順序)連想コンテナの接合対応104ノードハンドル?⇓(非順序)連想コンテナのノードを、コンテナから分離した時に、当該ノードを管理するクラステンプレート
(非順序)連想コンテナの接合対応105ノードハンドルの特徴• クラステンプレートの名前は規格では規定されていない。• コンテナ C のノードハンドルの型は C::node_type で参照可能(ノード自体の型じゃないのにこの名前なのは混乱しやすくないですか…)。• 元のコンテナが破棄されてもノードを管理できる。• 元のコンテナのアロケータのコピーを独自に持っている。• ムーブオンリーである(コピーは出来ない)。• ノードを持たない空のノードハンドルも作れる。• ノードを持ったまま破棄されると、アロケータを使ってノードを適切に破棄してくれる。• map のキーや set の要素など、普段は変更不可なものも変更可能。• 異なる型のコンテナ間でも互換性あり(次ページ参照)
(非順序)連想コンテナの接合対応106ノードハンドルの互換性下記の同じセル内はノードハンドルのやりとりが可能ただし、アロケータは型が同じ必要があるだけでなく、オブジェクトも等しくなければならない。map系比較関数が違っていてもOKmapmultimapset系比較関数が違っていてもOKsetmultisetunordered_map系ハッシュ・比較関数が違っていてもOKunordered_mapunordered_multimapunordered_set系ハッシュ・比較関数が違っていてもOKunordered_setunordered_multiset
(非順序)連想コンテナの接合対応107ノードハンドルの詳細メンバ型using value_type = コンテナの value_type; // set 系にしか無いusing key_type = コンテナの key_type; // map 系にしか無いusing mapped_type = コンテナの mapped_type; // map 系にしか無いusing allocator_type = コンテナの allocator_type;
(非順序)連想コンテナの接合対応108ノードハンドルの詳細コンストラクタconstexpr node_handle() noexcept; // デフォルトコンストラクタnode_handle(node_handle&&) noexcept; // ムーブコンストラクタデフォルトコンストラクタ:空のノードハンドルを作成。ムーブコンストラクタ:引数のノードハンドルからノードとアロケータを奪って作成。引数のノードハンドルは空になる。
(非順序)連想コンテナの接合対応109ノードハンドルの詳細デストラクタ~node_handle();ノードハンドルが空じゃなかったら、手持ちのアロケータで、destroy メンバ関数を使って要素を破棄してから、deallocate メンバ関数を使ってメモリを解放。(実際は allocator_traits を介して起動)
(非順序)連想コンテナの接合対応110ノードハンドルの詳細代入演算子node_handle& operator=(node_handle&&); // ムーブ代入演算子引数のノードハンドルからノードとアロケータを奪う。引数のノードハンドルは空になる(元々空なら自分も空になる) 。元々ノードを持っていた場合には、デストラクタと同様当該ノードを破棄するが、その場合、自分と引数のアロケータが等しいか、アロケータのpropagate_on_container_move_assignment が true か、のどちらかでなければならない。(この条件いりますかね…)
(非順序)連想コンテナの接合対応111ノードハンドルの詳細要素アクセスvalue_type& value() const; // set 系のみkey_type& key() const; // map 系のみmapped_type& mapped() const; // map 系のみ名前の通り。set や map のキーも const じゃない参照を返すので変更可能。しかも見ての通りノードハンドル自体が const でも変更可能。(なぜそうしたんだろう…)当然、空のノードハンドルに対して呼び出したら UB。
(非順序)連想コンテナの接合対応112ノードハンドルの詳細その他allocator_type get_allocator() const; // アロケータを取得explicit operator bool() const noexcept; // 空じゃなかったら truebool empty() const noexcept; // 空だったら true見たまんま。アロケータは、空のノードハンドルに対して呼び出したら UB。
(非順序)連想コンテナの接合対応113ノードハンドルの詳細swapvoid swap(node_handle&) noexcept(※);どちらかが空のノードハンドルか、アロケータが等しいか、アロケータのpropagate_on_container_swap が true か、 のいずれかでないと UB。※アロケータのpropagate_on_container_swap が true かアロケータのis_always_equal が true であれば noexcept。非メンバ関数版もあるよ。(メンバ関数を呼び出すだけ)
(非順序)連想コンテナの接合対応114再掲:ノードを切り離すメンバ関数node_type extract(const key_type& x); // 対象をキーで指定node_type extract(const_iterator position); // 対象をイテレータで指定上側の、キーで指定するバージョンは、該当するキーが無かったら空のノードハンドルが返る。非ユニークコンテナだと、最初に見つかったノードが返る。下側のイテレータで指定するバージョンは、イテレータが有効な要素を指していないといけない。つまり、position は cend() ではいけない。(cend() なら空のノードハンドル返せばいいと思うんだけど…)
ユニークキーマップの挿入インタフェース向上 詳細115あ、計算量は対応するerase と同じです
(非順序)連想コンテナの接合対応116再掲:切り離したノードを追加するメンバ関数insert_return_type insert(node_type&& nh); // ユニークコンテナの場合※iterator insert(node_type&& nh); // 非ユニークコンテナの場合※iterator insert(const_iterator hint, node_type&& nh);// 挿入位置のヒント付きどのバージョンでも nh が空の場合には何も起きない。1番目と2番目のバージョンでは、nh は必ず空になる。3番目のバージョンでは、キー重複で挿入できなかった場合には nh は不変。1番目の戻り値の insert_return_type はコンテナのメンバ型の名前。(後述2番目と3番目の戻り値のイテレータは、挿入した要素か、挿入できなかった場合は挿入しようとした要素と同じキーをもつ要素を指す。hint は無視されるかも…
ユニークキーマップの挿入インタフェース向上 詳細117あ、計算量は対応するinsert と同じです
(非順序)連想コンテナの接合対応118insert_return_type?
(非順序)連想コンテナの接合対応119insert_return_type?⇓こんなやつ(ただし、名前は規定されてない)templatestruct INSERT_RETURN_TYPE {Iterator position; // insert に成功したら挿入された要素、// 失敗したら挿入しようとした要素と同一のキーの要素bool inserted; // insert に成功したら true、失敗したら falseNodeType node; // insert に成功したら空、失敗したら引数 nh からムーブ};3番目のノードハンドル、必要ですかね…(一応 d.insert(s.extract(...)) ってやりたかったからっぽいけど…)
(非順序)連想コンテナの接合対応120完
(非順序)連想コンテナの接合対応121と思うじゃないですか…またですか…
(非順序)連想コンテナの接合対応122ノードを根こそぎ転送するメンバ関数template <...>void merge(C& x); // 左辺値参照(const じゃないよ)template <...>void merge(C&& x); //右辺値参照引数のコンテナが持つノードを全部奪い取る。ただし、奪い取る側がユニークコンテナの場合には、キー重複となるノードは引数のコンテナに残ったままとなる。引数のコンテナ C に許される型は、ノードハンドルの互換性の表と同じ。あと、アロケータは等しくないとダメ。(引数の型のテンプレートにある Key は set 系の場合は無いよ。めんどいから一緒にしちゃった…)
(非順序)連想コンテナの接合123おまけ
(非順序)連想コンテナの接合おまけ124機能テストマクロ __cpp_lib_node_extract 201606L(C++17 以降使ってれば使う機会無いけど…)
(非順序)連想コンテナの接合対応125完