Upgrade to Pro — share decks privately, control downloads, hide ads and more …

C++17の新機能 連想コンテナ編 / new features of C++17 - associative containers

C++17の新機能 連想コンテナ編 / new features of C++17 - associative containers

C++17 で(非順序)連想コンテナに追加された以下の新機能の紹介です。

・ユニークキーマップの挿入インタフェース向上
 (Improved insertion interface for unique-key maps)

・(非順序)連想コンテナの接合
 (Splicing Maps and Sets)

Miutsuru kariya

August 23, 2018
Tweet

More Decks by Miutsuru kariya

Other Decks in Programming

Transcript

  1. ユニークキーマップの挿入イン タフェース向上 4 N3873 Improved insertion interface for unique-key maps

    http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2014/n3873.html N4006 An improved emplace() for unique-key maps http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2014/n4006.html N4240 Improved insertion interface for unique-key maps (Revision 2) http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2014/n4240.html N4279 Improved insertion interface for unique-key maps (Revision 2.3) http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2014/n4279.html
  2. ユニークキーマップの挿入イン タフェース向上 導入の背景 11 問題①:次のコードのアサーションは通るか? std::map<int, std::unique_ptr<int>> m; m[42] =

    nullptr; auto ptr = std::make_unique<int>(); m.emplace(42, std::move(ptr)); // キー重複だから // 挿入されないけど… assert(ptr); 提案ペーパーから(改)
  3. ユニークキーマップの挿入イン タフェース向上 導入の背景 16 問題②:次のコードのアサーションは通るか? std::map<std::string, std::unique_ptr<int>> m; m["foo"] =

    nullptr; auto ptr = std::make_unique<int>(); m.emplace("foo", std::move(ptr)); // キー重複だから(ry assert(ptr); 提案ペーパーから(改)
  4. ユニークキーマップの挿入イン タフェース向上 導入の背景 22 がんばる map さん(Clang 3.9.1~とか) value_type を構築しなくても引数と直接

    比較できる時は構築しないで頑張ろう。 直接比較できない時はしょうがないので 構築しよう。 ⇓ ムーブされる条件は結構複雑。
  5. ユニークキーマップの挿入イン タフェース向上 回避策 26 1. findを使う auto it = m.find("foo");

    if (it == m.end()) { it = m.emplace("foo", std::move(p)).first; } 欠点:キーが存在しない場合、おんなじ検索が2回さ れる、MOTTAINAI
  6. ユニークキーマップの挿入イン タフェース向上 回避策 27 2. 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 には使えない。 毎回こんなん書くの面倒だし覚えてられなくてバグり そう。
  7. ユニークキーマップの挿入イン タフェース向上 対応 30 template <class... Args> pair<iterator, bool> try_emplace(const

    key_type& k, Args&&... args); template <class... Args> pair<iterator, bool> try_emplace(key_type&& k, Args&&... args); template <class... Args> iterator try_emplace(const_iterator hint, const key_type& k, Args&&... args); template <class... Args> iterator try_emplace(const_iterator hint, key_type&& k, Args&&... args);
  8. ユニークキーマップの挿入イン タフェース向上 対応 31 ポイント 既存の emplace とは異なり、キーの引数 k が

    key_type で独立しているので、value_type を構 築しなくても必ずキーの比較が可能 ⇓ 挿入しない場合は、引数は絶対不変!
  9. ユニークキーマップの挿入イン タフェース向上 対応 32 問題➂:次のコードのアサーションは通るか? std::map<int, std::unique_ptr<int>> m; m[42] =

    nullptr; auto ptr = std::make_unique<int>(); m.try_emplace(42, std::move(ptr)); assert(ptr); 提案ペーパーから(改)
  10. ユニークキーマップの挿入イン タフェース向上 対応 34 問題④:次のコードのアサーションは通るか? std::map<std::string, std::unique_ptr<int>> m; m["foo"] =

    nullptr; auto ptr = std::make_unique<int>(); m.try_emplace("foo", std::move(ptr)); assert(ptr); 提案ペーパーから(改)
  11. ユニークキーマップの挿入イン タフェース向上 詳細 40 template <class... Args> pair<iterator, bool> try_emplace(const

    key_type& k, Args&&... args); k:キー値。単体でコンテナ内の他の要素のキーと比較可能。このキーと同 値のキーを持つ要素がコンテナ内に存在しない場合、新たな要素のキーが この引数からコピー構築※される。 args:対応する値のコンストラクタの引数。k と同値のキーを持つ要素がコン テナ内に存在しない場合、新たな要素の値がこの引数から直接構築※され る。存在した場合は args は不変。 戻り値(iterator):新たに追加した要素(追加した場合)、あるいは、k と同値 のキーを持つ要素(追加しなかった場合)。 戻り値(bool):要素を追加した場合 true、追加しなかった場合 false。 ※ 実際にはちょっと違って全引数でpiecewise_constructされる…
  12. ユニークキーマップの挿入イン タフェース向上 詳細 41 template <class... Args> pair<iterator, bool> try_emplace(key_type&&

    k, Args&&... args); k:キー値。単体でコンテナ内の他の要素のキーと比較可能。このキーと同 値のキーを持つ要素がコンテナ内に存在しない場合、新たな要素のキーが この引数からムーブ構築※される。存在した場合は k は不変。 args:1個目のオーバーロードと同じ。 戻り値(iterator):1個目のオーバーロードと同じ。 戻り値(bool):1個目のオーバーロードと同じ。 ※ 実際には(ry
  13. ユニークキーマップの挿入イン タフェース向上 詳細 42 template <class... Args> iterator try_emplace(const_iterator hint,

    const key_type& k, Args&&... args); hint:k で指定したキー値をコンテナから検索する際のヒント。適切なヒントを 指定すれば検索が早く終わる。ただし、我らが非順序連想コンテナの場合 は完全に意味なし(何で非順序にも追加したし…)。 k:1個目のオーバーロードと同じ。 args:1個目のオーバーロードと同じ。 戻り値:1個目のオーバーロードの iterator と同じ。 何で bool 返さないんですかね…
  14. ユニークキーマップの挿入イン タフェース向上 詳細 43 template <class... Args> iterator try_emplace(const_iterator hint,

    key_type&& k, Args&&... args); hint:3個目のオーバーロードと同じ。 k:2個目のオーバーロードと同じ。 args:1個目のオーバーロードと同じ。 戻り値:1個目のオーバーロードの iterator と同じ。 何で bo(ry
  15. ユニークキーマップの挿入イン タフェース向上 不満点 49 C++14で追加されたこんなヤツ std::map<std::string, std::unique_ptr<int>, std::less<>> m; auto

    res = m.find("foo"); // "foo" から std::string が作られない (残念ながら我らが愛する非順序連想コンテナには無い…)
  16. ユニークキーマップの挿入イン タフェース向上 不満点 50 問題⑤:次のコードを順に実行すると、std::string オ ブジェクトはそれぞれ何個作られるか? std::map<std::string, int, std::less<>>

    m; m. emplace("foo", 42); // 何個?(挿入される m. emplace("foo", 42); // 何個?(挿入されないけど m.try_emplace("bar", 42); // 何個?(挿入される m.try_emplace("bar", 42); // 何個?(挿入されないけど
  17. ユニークキーマップの挿入イン タフェース向上 不満点 51 答え std::map<std::string, int, std::less<>> m; m.

    emplace("foo", 42); // 1個 m. emplace("foo", 42); // 1個(悲しい…) m.try_emplace("bar", 42); // 2個(悲しい…) m.try_emplace("bar", 42); // 1個(悲しい…)
  18. ユニークキーマップの挿入イン タフェース向上 実装 54 Clang HEAD map template <class... Args>

    pair<iterator, bool> 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>(args)...)); } 何か一生懸命やってそう… 実際 __emplace_unique_key_args では k による検索は1回しか走らないし、もち ろん既にキーが存在すれば args も無傷。 ちな、通常の insert や emplace でも引数からキーに直接アクセスできる場合に はコイツが呼ばれてる。(でも piecewise_construct だと呼ばれない…)
  19. ユニークキーマップの挿入イン タフェース向上 実装 55 Clang HEAD unordered_map template <class... Args>

    pair<iterator, bool> 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>(args)...)); } 何か一生懸命やってそう… てか map と完全に一致…
  20. ユニークキーマップの挿入イン タフェース向上 実装 56 GCC HEAD map template <typename... Args>

    pair<iterator, bool> 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>(args)...)); return {i, true}; } return {i, false}; } えぇ… 回避方法のまんまやし、もうちょっと頑張って欲しい…
  21. ユニークキーマップの挿入イン タフェース向上 実装 57 GCC HEAD unordered_map template <typename... Args>

    pair<iterator, bool> 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>(args)...)).first; return {i, true}; } return {i, false}; } えぇ… 提案の意図ほぼ汲んでなくて検索2回走るし GCC は悔い改めて欲しい…
  22. ユニークキーマップの挿入イン タフェース向上 回避策 63 1. find を使う 2. lower_bound を使う

    (でも map だけ) 3. operator[] を使う さっき似たようなの見たような…
  23. ユニークキーマップの挿入イン タフェース向上 回避策 64 1. 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
  24. ユニークキーマップの挿入イン タフェース向上 回避策 65 2. 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
  25. ユニークキーマップの挿入イン タフェース向上 回避策 66 3. operator[] を使う m["foo"] = std::move(p);

    欠点:mapped_typeがデフォルト構築可能じゃなきゃ ならない。 デフォルト構築されてから代入されるから、デフォ ルト構築が高価だとムダ感マジ半端ないって。 あと、今までキーが存在してたかどうかが分からな い。
  26. ユニークキーマップの挿入イン タフェース向上 対応 69 template <class M> pair<iterator, bool> insert_or_assign(const

    key_type& k, M&& obj); template <class M> pair<iterator, bool> insert_or_assign(key_type&& k, M&& obj); template <class M> iterator insert_or_assign(const_iterator hint, const key_type& k, M&& obj); template <class M> iterator insert_or_assign(const_iterator hint, key_type&& k, M&& obj);
  27. ユニークキーマップの挿入イン タフェース向上 詳細 74 template <class M> pair<iterator, bool> insert_or_assign(const

    key_type& k, M&& obj); k:キー値。単体でコンテナ内の他の要素のキーと比較可能。このキーと同 値のキーを持つ要素がコンテナ内に存在しない場合、新たな要素のキーが この引数からコピー構築※される。 obj:対応する値のコンストラクタの引数。k と同値のキーを持つ要素がコン テナ内に存在しない場合、新たな要素の値がこの引数から直接構築※され る。存在した場合は要素の値はこの引数からムーブ代入される。 戻り値(iterator):新たに追加した要素(追加した場合)、あるいは、k と同値 のキーを持つ要素(追加しなかった場合)。 戻り値(bool):要素を追加した場合 true、追加しなかった場合 false。 ※ 実際には(ry
  28. ユニークキーマップの挿入イン タフェース向上 詳細 75 template <class M> pair<iterator, bool> insert_or_assign(key_type&&

    k, M&& obj); k:キー値。単体でコンテナ内の他の要素のキーと比較可能。このキーと同 値のキーを持つ要素がコンテナ内に存在しない場合、新たな要素のキーが この引数からムーブ構築※される。存在する場合は不明…(普通に考えた ら不変だと思うんだけど…) obj:1個目のオーバーロードと同じ。 戻り値(iterator): 1個目のオーバーロードと同じ。 戻り値(bool): 1個目のオーバーロードと同じ。 ※ 実際には(ry
  29. ユニークキーマップの挿入イン タフェース向上 詳細 76 template <class M> iterator insert_or_assign(const_iterator hint,

    const key_type& k, M&& obj); hint:k で指定したキー値をコンテナから検索する際のヒント。適切なヒントを 指定すれば検索が早く終わる。ただし、我らが非順序連想コンテナの場合 は完全に意味なし(何で非順序にも追加したし…) 。 k:1個目のオーバーロードと同じ。 obj:1個目のオーバーロードと同じ。 戻り値:1個目のオーバーロードの iterator と同じ。
  30. ユニークキーマップの挿入イン タフェース向上 詳細 77 template <class M> iterator insert_or_assign(const_iterator hint,

    key_type&& k, M&& obj); hint:3個目のオーバーロードと同じ。 k:2個目のオーバーロードと同じ。 obj:1個目のオーバーロードと同じ。 戻り値:1個目のオーバーロードの iterator と同じ。
  31. ユニークキーマップの挿入イン タフェース向上 実装 79 Clang HEAD map template <class Vp>

    pair<iterator, bool> 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<Vp>(v); return make_pair(p, false); } return make_pair(emplace_hint(p, k, forward<Vp>(v)), true); } えぇ… 回避方法のまんまやし、もうちょっと頑張って欲しい…
  32. ユニークキーマップの挿入イン タフェース向上 実装 80 Clang HEAD unordered_map template <class Vp>

    pair<iterator, bool> insert_or_assign(const key_type& k, Vp&& v) { pair<iterator, bool> res = __table_.__emplace_unique_key_args(k, k, forward<Vp>(v)); if (!res.second) { res.first->second = forward<Vp>(v); } return res; } 何か一生懸命やってそう… てか try_emplace に代入追加しただけ…
  33. ユニークキーマップの挿入イン タフェース向上 実装 81 GCC HEAD map template <typename Obj>

    pair<iterator, bool> 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>(obj))); return {i, true}; } (*i).second = std::forward<Obj>(obj); return {i, false}; } えぇ… もうちょっと頑張って欲しい…(てか引数2個固定なんだからpiecewiseいらんやろ
  34. ユニークキーマップの挿入イン タフェース向上 実装 82 GCC HEAD unordered_map template <typename Obj>

    pair<iterator, bool> 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>(obj))).first; return {i, true}; } (*i).second = std::forward<Obj>(obj); return {i, false}; } えぇ… 提案の意図ほぼ汲んでなくて検索2回走るし GCC はマジ悔い改めて欲しい…
  35. ユニークキーマップの挿入イン タフェース向上 おまけ 84 機能テストマクロ <map> __cpp_lib_map_try_emplace 201411L <unordered_map> __cpp_lib_unordered_map_try_emplace

    201411L map と unordered_map で別々に定義されているのね… (C++17 以降使ってれば使う機会無いけど…)
  36. (非順序)連想コンテナの接合 87 N3586 Splicing Maps and Sets http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2013/n3586.pdf N3645 Splicing

    Maps and Sets (Revision 1) http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2013/n3645.pdf P0083R0 Splicing Maps and Sets (Revision 2) http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2015/p0083r0.pdf P0083R1 Splicing Maps and Sets (Revision 3) http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2015/p0083r1.pdf P0083R2 Splicing Maps and Sets (Revision 4) http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2016/p0083r2.pdf P0083R3 Splicing Maps and Sets (Revision 5) http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2016/p0083r3.pdf
  37. (非順序)連想コンテナの接合 導入の背景 94 auto it = s.find(42); // まぁしょうがない d.insert(std::move(*it));

    // d側でメモリ割当&ムーブ s.erase(it); // s側でメモリ開放&デストラクタ メモリ割当&解放とか、ムーブコン ストラクタ&デストラクタとか、控え めに言ってムダじゃね? しかもこれじゃキーはムーブできてない… map じゃなくて set だとそもそもムーブできない…
  38. (非順序)連想コンテナの接合 導入の背景 96 2. キーを変更したい! std::map<int, std::string> m; m.emplace(42, "foo");

    m.emplace(114514, "bar"); auto it = m.find(42); m.emplace(893, std::move(it->second)); m.erase(it);
  39. (非順序)連想コンテナの接合 導入の背景 97 auto it = m.find(42); // まぁしょうがない m.emplace(893,

    std::move(it->second)); // メモリ割当&ムーブ m.erase(it); // メモリ開放&デストラクタ メモリ割当&解放(ry
  40. (非順序)連想コンテナの接合 対応 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); // 挿入位置のヒント付き ※ ユニークコンテナだとキー値重複で失敗する可能性があるので戻り値がちょっと違う
  41. (非順序)連想コンテナの接合 対応 105 ノードハンドルの特徴 • クラステンプレートの名前は規格では規定されていない。 • コンテナ C のノードハンドルの型は

    C::node_type で参照可能(ノード 自体の型じゃないのにこの名前なのは混乱しやすくないですか…)。 • 元のコンテナが破棄されてもノードを管理できる。 • 元のコンテナのアロケータのコピーを独自に持っている。 • ムーブオンリーである(コピーは出来ない)。 • ノードを持たない空のノードハンドルも作れる。 • ノードを持ったまま破棄されると、アロケータを使ってノードを適切に 破棄してくれる。 • map のキーや set の要素など、普段は変更不可なものも変更可能。 • 異なる型のコンテナ間でも互換性あり(次ページ参照)
  42. (非順序)連想コンテナの接合 対応 106 ノードハンドルの互換性 下記の同じセル内はノードハンドルのやりとりが可能 ただし、アロケータは型が同じ必要があるだけでなく、オブ ジェクトも等しくなければならない。 map系 比較関数が違っていてもOK map<K,

    T, Cx, A> multimap<K, T, Cx, A> set系 比較関数が違っていてもOK set<K, Cx, A> multiset<K, Cx, A> unordered_map系 ハッシュ・比較関数が違っていてもOK unordered_map<K, T, Hx, Ex, A> unordered_multimap<K, T, Hx, Ex, A> unordered_set系 ハッシュ・比較関数が違っていてもOK unordered_set<K, Hx, Ex, A> unordered_multiset<K, Hx, Ex, A>
  43. (非順序)連想コンテナの接合 対応 107 ノードハンドルの詳細 メンバ型 using value_type = コンテナの value_type;

    // set 系にしか無い using key_type = コンテナの key_type; // map 系にしか無い using mapped_type = コンテナの mapped_type; // map 系にしか無い using allocator_type = コンテナの allocator_type;
  44. (非順序)連想コンテナの接合 対応 108 ノードハンドルの詳細 コンストラクタ constexpr node_handle() noexcept; // デフォルトコンストラクタ

    node_handle(node_handle&&) noexcept; // ムーブコンストラクタ デフォルトコンストラクタ:空のノードハンドルを作成。 ムーブコンストラクタ:引数のノードハンドルからノードとアロケータを 奪って作成。引数のノードハンドルは空になる。
  45. (非順序)連想コンテナの接合 対応 110 ノードハンドルの詳細 代入演算子 node_handle& operator=(node_handle&&); // ムーブ代入演算子 引数のノードハンドルからノードとアロケータを奪う。引数のノードハンド

    ルは空になる(元々空なら自分も空になる) 。 元々ノードを持っていた場合には、デストラクタと同様当該ノードを破棄 するが、その場合、自分と引数のアロケータが等しいか、アロケータの propagate_on_container_move_assignment が true か、のどちらかでなけ ればならない。(この条件いりますかね…)
  46. (非順序)連想コンテナの接合 対応 111 ノードハンドルの詳細 要素アクセス value_type& value() const; // set

    系のみ key_type& key() const; // map 系のみ mapped_type& mapped() const; // map 系のみ 名前の通り。set や map のキーも const じゃない参照を返すので変更可 能。 しかも見ての通りノードハンドル自体が const でも変更可能。(なぜそう したんだろう…) 当然、空のノードハンドルに対して呼び出したら UB。
  47. (非順序)連想コンテナの接合 対応 112 ノードハンドルの詳細 その他 allocator_type get_allocator() const; // アロケータを取得

    explicit operator bool() const noexcept; // 空じゃなかったら true bool empty() const noexcept; // 空だったら true 見たまんま。 アロケータは、空のノードハンドルに対して呼び出したら UB。
  48. (非順序)連想コンテナの接合 対応 113 ノードハンドルの詳細 swap void swap(node_handle&) noexcept(※); どちらかが空のノードハンドルか、アロケータが等しいか、アロケータの propagate_on_container_swap

    が true か、 のいずれかでないと UB。 ※アロケータのpropagate_on_container_swap が true かアロケータの is_always_equal が true であれば noexcept。 非メンバ関数版もあるよ。(メンバ関数を呼び出すだけ)
  49. (非順序)連想コンテナの接合 対応 114 再掲:ノードを切り離すメンバ関数 node_type extract(const key_type& x); // 対象をキーで指定

    node_type extract(const_iterator position); // 対象をイテレータで指定 上側の、キーで指定するバージョンは、該当するキーが無かったら空のノー ドハンドルが返る。 非ユニークコンテナだと、最初に見つかったノードが返る。 下側のイテレータで指定するバージョンは、イテレータが有効な要素を指し ていないといけない。つまり、position は cend() ではいけない。(cend() なら 空のノードハンドル返せばいいと思うんだけど…)
  50. (非順序)連想コンテナの接合 対応 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 は無視されるかも…
  51. (非順序)連想コンテナの接合 対応 119 insert_return_type? ⇓ こんなやつ(ただし、名前は規定されてない) template<class Iterator, class NodeType>

    struct INSERT_RETURN_TYPE { Iterator position; // insert に成功したら挿入された要素、 // 失敗したら挿入しようとした要素と同一のキーの要素 bool inserted; // insert に成功したら true、失敗したら false NodeType node; // insert に成功したら空、失敗したら引数 nh からムーブ }; 3番目のノードハンドル、必要ですかね… (一応 d.insert(s.extract(...)) ってやりたかったからっぽいけど…)
  52. (非順序)連想コンテナの接合 対応 122 ノードを根こそぎ転送するメンバ関数 template <...> void merge(C<Key, T, ...,

    Allocator>& x); // 左辺値参照(const じゃないよ) template <...> void merge(C<Key, T, ..., Allocator>&& x); //右辺値参照 引数のコンテナが持つノードを全部奪い取る。ただし、奪い取る側がユニーク コンテナの場合には、キー重複となるノードは引数のコンテナに残ったままと なる。 引数のコンテナ C に許される型は、ノードハンドルの互換性の表と同じ。 あと、アロケータは等しくないとダメ。 (引数の型のテンプレートにある Key は set 系の場合は無いよ。めんどいから一緒にしちゃった…)