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)

4cd53d17fd7e26f611822b508963f613?s=128

Miutsuru kariya

August 23, 2018
Tweet

Transcript

  1. C++17 の新機能 (非順序)連想コンテナ編 2018/8/23 鳥頭かりやマン 1

  2. 連想コンテナおいしい!!! (特に非順序) 2 注:個人の感想です

  3. ユニークキーマップの挿入 インタフェース向上 3

  4. ユニークキーマップの挿入イン タフェース向上 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
  5. ユニークキーマップの挿入イン タフェース向上 5 ユニークキーマップ?

  6. ユニークキーマップの挿入イン タフェース向上 6 ユニークキーマップ? ⇓ map unordered_map

  7. ユニークキーマップの挿入イン タフェース向上 7 multimap unordered_multimap は対象外 もちろん set 系も…

  8. ユニークキーマップの挿入イン タフェース向上 8 導入の背景

  9. ユニークキーマップの挿入イン タフェース向上 導入の背景 9 既存の emplace の仕様が 曖昧でツラい…

  10. ユニークキーマップの挿入イン タフェース向上 導入の背景 10 ???

  11. ユニークキーマップの挿入イン タフェース向上 導入の背景 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); 提案ペーパーから(改)
  12. ユニークキーマップの挿入イン タフェース向上 導入の背景 12 答え 未規定

  13. ユニークキーマップの挿入イン タフェース向上 導入の背景 13 ちな、 GCC:失敗する Clang(~3.8.1):失敗する Clang(3.9.1~):成功する Wandbox調べ

  14. ユニークキーマップの挿入イン タフェース向上 導入の背景 14 何で? 既にキーがあるんだから Clang 3.9.1~の方が正し いんじゃないの?

  15. ユニークキーマップの挿入イン タフェース向上 導入の背景 15 お前は map さんの 気持ちを 全くわかってない

  16. ユニークキーマップの挿入イン タフェース向上 導入の背景 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); 提案ペーパーから(改)
  17. ユニークキーマップの挿入イン タフェース向上 導入の背景 17 GCC:失敗する Clang:失敗する Wandbox調べ

  18. ユニークキーマップの挿入イン タフェース向上 導入の背景 18 ???

  19. ユニークキーマップの挿入イン タフェース向上 導入の背景 19 map さんの気持ち を考える

  20. ユニークキーマップの挿入イン タフェース向上 導入の背景 20 • 挿入するかどうかを判断するためには既 存のキーと挿入するデータのキーを比較 する必要がある。 • emplaceの引数はvalue_type(=std::pair)の

    コンストラクタ引数だから、引数にキーと直 接比較可能な値が存在するとは限らない。
  21. ユニークキーマップの挿入イン タフェース向上 導入の背景 21 ふつうの map さん(GCCとか) 挿入するかどうかわからないけど、比較 するためにとりあえずメモリアロケートし て

    value_type を構築しちゃおう。 ⇓ たとえ挿入しなくても、右辺値参照は ムーブされちゃったりする。
  22. ユニークキーマップの挿入イン タフェース向上 導入の背景 22 がんばる map さん(Clang 3.9.1~とか) value_type を構築しなくても引数と直接

    比較できる時は構築しないで頑張ろう。 直接比較できない時はしょうがないので 構築しよう。 ⇓ ムーブされる条件は結構複雑。
  23. ユニークキーマップの挿入イン タフェース向上 導入の背景 23 いずれにしろ、 最終的に引数がどう なっているのかが良く 分からない いやまぁ実装調べればわかるけど、バージョンによって変わるかもしれないし…

  24. ユニークキーマップの挿入イン タフェース向上 24 回避策

  25. ユニークキーマップの挿入イン タフェース向上 回避策 25 1. find を使う 2. lower_bound を使う

    (でも map だけ)
  26. ユニークキーマップの挿入イン タフェース向上 回避策 26 1. findを使う auto it = m.find("foo");

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

  29. ユニークキーマップの挿入イン タフェース向上 対応 29 新しい メンバ関数 追加しよ 575

  30. ユニークキーマップの挿入イン タフェース向上 対応 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);
  31. ユニークキーマップの挿入イン タフェース向上 対応 31 ポイント 既存の emplace とは異なり、キーの引数 k が

    key_type で独立しているので、value_type を構 築しなくても必ずキーの比較が可能 ⇓ 挿入しない場合は、引数は絶対不変!
  32. ユニークキーマップの挿入イン タフェース向上 対応 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); 提案ペーパーから(改)
  33. ユニークキーマップの挿入イン タフェース向上 対応 33 答え 常に通る(ヤッター!)

  34. ユニークキーマップの挿入イン タフェース向上 対応 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); 提案ペーパーから(改)
  35. ユニークキーマップの挿入イン タフェース向上 対応 35 答え 常に通る(ヤッター!)

  36. ユニークキーマップの挿入イン タフェース向上 対応 36 素朴な疑問点 何で emplace の規約を変更し ないでメンバ関数追加にした の?

  37. ユニークキーマップの挿入イン タフェース向上 対応 37 提案ペーパーでの回答 しれっと変えちゃうと、対応してる処 理系と対応してない処理系をユー ザが判別するのが難しくなって、バ グの温床になりかねないから。

  38. ユニークキーマップの挿入イン タフェース向上 対応 38 今までもいろいろしれっと変え てきてると思うんだけどなぁ…

  39. ユニークキーマップの挿入イン タフェース向上 39 詳細

  40. ユニークキーマップの挿入イン タフェース向上 詳細 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される…
  41. ユニークキーマップの挿入イン タフェース向上 詳細 41 template <class... Args> pair<iterator, bool> try_emplace(key_type&&

    k, Args&&... args); k:キー値。単体でコンテナ内の他の要素のキーと比較可能。このキーと同 値のキーを持つ要素がコンテナ内に存在しない場合、新たな要素のキーが この引数からムーブ構築※される。存在した場合は k は不変。 args:1個目のオーバーロードと同じ。 戻り値(iterator):1個目のオーバーロードと同じ。 戻り値(bool):1個目のオーバーロードと同じ。 ※ 実際には(ry
  42. ユニークキーマップの挿入イン タフェース向上 詳細 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 返さないんですかね…
  43. ユニークキーマップの挿入イン タフェース向上 詳細 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
  44. ユニークキーマップの挿入イン タフェース向上 詳細 44 あ、計算量は emplace、 emplace_hint と同じです

  45. ユニークキーマップの挿入イン タフェース向上 詳細 45 注意 キーは、直接構築じゃなくて、メンバ 関数呼び出し前に構築、その後必 要に応じてコピー・ムーブになる まぁ引数が key_type

    型なんで当たり前なんですが…
  46. ユニークキーマップの挿入イン タフェース向上 46 不満点

  47. ユニークキーマップの挿入イン タフェース向上 不満点 47 異種混合比較コンテナの場合でも、 キー用の引数は key_type になっ ちゃう… (つまりキーが直接構築されない)

  48. ユニークキーマップの挿入イン タフェース向上 不満点 48 異種混合比較コンテナ???

  49. ユニークキーマップの挿入イン タフェース向上 不満点 49 C++14で追加されたこんなヤツ std::map<std::string, std::unique_ptr<int>, std::less<>> m; auto

    res = m.find("foo"); // "foo" から std::string が作られない (残念ながら我らが愛する非順序連想コンテナには無い…)
  50. ユニークキーマップの挿入イン タフェース向上 不満点 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); // 何個?(挿入されないけど
  51. ユニークキーマップの挿入イン タフェース向上 不満点 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個(悲しい…)
  52. ユニークキーマップの挿入イン タフェース向上 不満点 52 注意 キーは、たとえ異種混合比較コンテ ナでも、直接構築じゃなくて、メンバ 関数呼び出し前に構築、その後必 要に応じてコピー・ムーブになる しつこい…

  53. ユニークキーマップの挿入イン タフェース向上 53 実装

  54. ユニークキーマップの挿入イン タフェース向上 実装 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 だと呼ばれない…)
  55. ユニークキーマップの挿入イン タフェース向上 実装 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 と完全に一致…
  56. ユニークキーマップの挿入イン タフェース向上 実装 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}; } えぇ… 回避方法のまんまやし、もうちょっと頑張って欲しい…
  57. ユニークキーマップの挿入イン タフェース向上 実装 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 は悔い改めて欲しい…
  58. ユニークキーマップの挿入イン タフェース向上 58 完

  59. ユニークキーマップの挿入イン タフェース向上 59 と思うじゃないですか…

  60. ユニークキーマップの挿入イン タフェース向上 60 導入の背景

  61. ユニークキーマップの挿入イン タフェース向上 導入の背景 61 指定したキーが無かったら挿 入、あったら更新、がしたい

  62. ユニークキーマップの挿入イン タフェース向上 62 回避策

  63. ユニークキーマップの挿入イン タフェース向上 回避策 63 1. find を使う 2. lower_bound を使う

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

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

  68. ユニークキーマップの挿入イン タフェース向上 対応 68 新しい メンバ関数 追加しよ 575(nページぶり2回目)

  69. ユニークキーマップの挿入イン タフェース向上 対応 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);
  70. ユニークキーマップの挿入イン タフェース向上 対応 70 ポイント 普通の insert とは異なり、キーが存在した場合 には代入が走る 普通の

    insert とは異なり、キーと値が引数レベ ルで分離されてる
  71. ユニークキーマップの挿入イン タフェース向上 対応 71 素朴な疑問点 何で emplace じゃなくて insert って名前なの?

    何で variadic じゃないの?
  72. ユニークキーマップの挿入イン タフェース向上 対応 72 提案ペーパーの回答 キーが無い場合の構築は emplace でもいいけど、キーがあった場合の 代入演算子は引数1つしかないや ろ?

    なるほどな?
  73. ユニークキーマップの挿入イン タフェース向上 73 詳細

  74. ユニークキーマップの挿入イン タフェース向上 詳細 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
  75. ユニークキーマップの挿入イン タフェース向上 詳細 75 template <class M> pair<iterator, bool> insert_or_assign(key_type&&

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

    key_type&& k, M&& obj); hint:3個目のオーバーロードと同じ。 k:2個目のオーバーロードと同じ。 obj:1個目のオーバーロードと同じ。 戻り値:1個目のオーバーロードの iterator と同じ。
  78. ユニークキーマップの挿入イン タフェース向上 詳細 78 あ、計算量は emplace、 emplace_hint と同じです さっきも聞いたような…

  79. ユニークキーマップの挿入イン タフェース向上 実装 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); } えぇ… 回避方法のまんまやし、もうちょっと頑張って欲しい…
  80. ユニークキーマップの挿入イン タフェース向上 実装 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 に代入追加しただけ…
  81. ユニークキーマップの挿入イン タフェース向上 実装 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いらんやろ
  82. ユニークキーマップの挿入イン タフェース向上 実装 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 はマジ悔い改めて欲しい…
  83. ユニークキーマップの挿入イン タフェース向上 83 おまけ

  84. ユニークキーマップの挿入イン タフェース向上 おまけ 84 機能テストマクロ <map> __cpp_lib_map_try_emplace 201411L <unordered_map> __cpp_lib_unordered_map_try_emplace

    201411L map と unordered_map で別々に定義されているのね… (C++17 以降使ってれば使う機会無いけど…)
  85. ユニークキーマップの挿入イン タフェース向上 85 完

  86. (非順序)連想コンテナの 接合 86

  87. (非順序)連想コンテナの接合 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
  88. (非順序)連想コンテナの接合 88 接合?

  89. (非順序)連想コンテナの接合 89 接合? ⇓ splicing 良い日本語があったら教えてください…

  90. (非順序)連想コンテナの接合 90 導入の背景

  91. (非順序)連想コンテナの接合 導入の背景 91 1. 連想コンテナの要素をコ ンテナ間で移動したい! 2. キーを変更したい!

  92. (非順序)連想コンテナの接合 導入の背景 92 1. (非順序)連想コンテナの要素をコ ンテナ間で移動したい! std::map<int, std::string> s, d;

    s.emplace(42, "foo"); s.emplace(114514, "bar"); // ここで s の42の要素を d に移動したい…
  93. (非順序)連想コンテナの接合 導入の背景 93 1. (非順序)連想コンテナの要素をコ ンテナ間で移動したい! std::map<int, std::string> s, d;

    s.emplace(42, "foo"); s.emplace(114514, "bar"); auto it = s.find(42); d.insert(std::move(*it)); s.erase(it);
  94. (非順序)連想コンテナの接合 導入の背景 94 auto it = s.find(42); // まぁしょうがない d.insert(std::move(*it));

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

    m.emplace(114514, "bar"); // ここで m の 42 のキーを 893 に変更したい…
  96. (非順序)連想コンテナの接合 導入の背景 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);
  97. (非順序)連想コンテナの接合 導入の背景 97 auto it = m.find(42); // まぁしょうがない m.emplace(893,

    std::move(it->second)); // メモリ割当&ムーブ m.erase(it); // メモリ開放&デストラクタ メモリ割当&解放(ry
  98. (非順序)連想コンテナの接合 98 対応

  99. (非順序)連想コンテナの接合 対応 99 新しい メンバ関数 追加しよ 575(nページぶり3回目)

  100. (非順序)連想コンテナの接合 対応 100 コンテナから指定されたノードを 切り離すメンバ関数(extract)と、 切り離したノードをコンテナに追 加するメンバ関数(insert)を追加。

  101. (非順序)連想コンテナの接合 対応 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. (非順序)連想コンテナの接合 対応 102 ポイント 切り離されたノードは「ノードハンドル」に格納さ れて、元のコンテナとは独立して管理される。 切り離されたノードは、「ノードハンドル」を通し て map のキー値や

    set の要素値を変更可能。
  103. (非順序)連想コンテナの接合 対応 103 ノードハンドル?

  104. (非順序)連想コンテナの接合 対応 104 ノードハンドル? ⇓ (非順序)連想コンテナのノードを、 コンテナから分離した時に、当該 ノードを管理するクラステンプレート

  105. (非順序)連想コンテナの接合 対応 105 ノードハンドルの特徴 • クラステンプレートの名前は規格では規定されていない。 • コンテナ C のノードハンドルの型は

    C::node_type で参照可能(ノード 自体の型じゃないのにこの名前なのは混乱しやすくないですか…)。 • 元のコンテナが破棄されてもノードを管理できる。 • 元のコンテナのアロケータのコピーを独自に持っている。 • ムーブオンリーである(コピーは出来ない)。 • ノードを持たない空のノードハンドルも作れる。 • ノードを持ったまま破棄されると、アロケータを使ってノードを適切に 破棄してくれる。 • map のキーや set の要素など、普段は変更不可なものも変更可能。 • 異なる型のコンテナ間でも互換性あり(次ページ参照)
  106. (非順序)連想コンテナの接合 対応 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>
  107. (非順序)連想コンテナの接合 対応 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. (非順序)連想コンテナの接合 対応 108 ノードハンドルの詳細 コンストラクタ constexpr node_handle() noexcept; // デフォルトコンストラクタ

    node_handle(node_handle&&) noexcept; // ムーブコンストラクタ デフォルトコンストラクタ:空のノードハンドルを作成。 ムーブコンストラクタ:引数のノードハンドルからノードとアロケータを 奪って作成。引数のノードハンドルは空になる。
  109. (非順序)連想コンテナの接合 対応 109 ノードハンドルの詳細 デストラクタ ~node_handle(); ノードハンドルが空じゃなかったら、手持ちのアロケータで、destroy メン バ関数を使って要素を破棄してから、deallocate メンバ関数を使ってメモ

    リを解放。(実際は allocator_traits を介して起動)
  110. (非順序)連想コンテナの接合 対応 110 ノードハンドルの詳細 代入演算子 node_handle& operator=(node_handle&&); // ムーブ代入演算子 引数のノードハンドルからノードとアロケータを奪う。引数のノードハンド

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

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

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

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

    node_type extract(const_iterator position); // 対象をイテレータで指定 上側の、キーで指定するバージョンは、該当するキーが無かったら空のノー ドハンドルが返る。 非ユニークコンテナだと、最初に見つかったノードが返る。 下側のイテレータで指定するバージョンは、イテレータが有効な要素を指し ていないといけない。つまり、position は cend() ではいけない。(cend() なら 空のノードハンドル返せばいいと思うんだけど…)
  115. ユニークキーマップの挿入イン タフェース向上 詳細 115 あ、計算量は対応する erase と同じです

  116. (非順序)連想コンテナの接合 対応 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. ユニークキーマップの挿入イン タフェース向上 詳細 117 あ、計算量は対応する insert と同じです

  118. (非順序)連想コンテナの接合 対応 118 insert_return_type?

  119. (非順序)連想コンテナの接合 対応 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(...)) ってやりたかったからっぽいけど…)
  120. (非順序)連想コンテナの接合 対応 120 完

  121. (非順序)連想コンテナの接合 対応 121 と思うじゃないですか… またですか…

  122. (非順序)連想コンテナの接合 対応 122 ノードを根こそぎ転送するメンバ関数 template <...> void merge(C<Key, T, ...,

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

  124. (非順序)連想コンテナの接合 おまけ 124 機能テストマクロ <map> <set> <unordered_map> <unordered_set> __cpp_lib_node_extract 201606L

    (C++17 以降使ってれば使う機会無いけど…)
  125. (非順序)連想コンテナの接合 対応 125 完