Slide 1

Slide 1 text

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

Slide 2

Slide 2 text

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

Slide 3

Slide 3 text

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

Slide 4

Slide 4 text

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

Slide 5

Slide 5 text

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

Slide 6

Slide 6 text

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

Slide 7

Slide 7 text

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

Slide 8

Slide 8 text

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

Slide 9

Slide 9 text

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

Slide 10

Slide 10 text

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

Slide 11

Slide 11 text

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

Slide 12

Slide 12 text

ユニークキーマップの挿入イン タフェース向上 導入の背景 12 答え 未規定

Slide 13

Slide 13 text

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

Slide 14

Slide 14 text

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

Slide 15

Slide 15 text

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

Slide 16

Slide 16 text

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

Slide 17

Slide 17 text

ユニークキーマップの挿入イン タフェース向上 導入の背景 17 GCC:失敗する Clang:失敗する Wandbox調べ

Slide 18

Slide 18 text

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

Slide 19

Slide 19 text

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

Slide 20

Slide 20 text

ユニークキーマップの挿入イン タフェース向上 導入の背景 20 • 挿入するかどうかを判断するためには既 存のキーと挿入するデータのキーを比較 する必要がある。 • emplaceの引数はvalue_type(=std::pair)の コンストラクタ引数だから、引数にキーと直 接比較可能な値が存在するとは限らない。

Slide 21

Slide 21 text

ユニークキーマップの挿入イン タフェース向上 導入の背景 21 ふつうの map さん(GCCとか) 挿入するかどうかわからないけど、比較 するためにとりあえずメモリアロケートし て value_type を構築しちゃおう。 ⇓ たとえ挿入しなくても、右辺値参照は ムーブされちゃったりする。

Slide 22

Slide 22 text

ユニークキーマップの挿入イン タフェース向上 導入の背景 22 がんばる map さん(Clang 3.9.1~とか) value_type を構築しなくても引数と直接 比較できる時は構築しないで頑張ろう。 直接比較できない時はしょうがないので 構築しよう。 ⇓ ムーブされる条件は結構複雑。

Slide 23

Slide 23 text

ユニークキーマップの挿入イン タフェース向上 導入の背景 23 いずれにしろ、 最終的に引数がどう なっているのかが良く 分からない いやまぁ実装調べればわかるけど、バージョンによって変わるかもしれないし…

Slide 24

Slide 24 text

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

Slide 25

Slide 25 text

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

Slide 26

Slide 26 text

ユニークキーマップの挿入イン タフェース向上 回避策 26 1. findを使う auto it = m.find("foo"); if (it == m.end()) { it = m.emplace("foo", std::move(p)).first; } 欠点:キーが存在しない場合、おんなじ検索が2回さ れる、MOTTAINAI

Slide 27

Slide 27 text

ユニークキーマップの挿入イン タフェース向上 回避策 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 には使えない。 毎回こんなん書くの面倒だし覚えてられなくてバグり そう。

Slide 28

Slide 28 text

ユニークキーマップの挿入イン タフェース向上 28 対応

Slide 29

Slide 29 text

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

Slide 30

Slide 30 text

ユニークキーマップの挿入イン タフェース向上 対応 30 template 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);

Slide 31

Slide 31 text

ユニークキーマップの挿入イン タフェース向上 対応 31 ポイント 既存の emplace とは異なり、キーの引数 k が key_type で独立しているので、value_type を構 築しなくても必ずキーの比較が可能 ⇓ 挿入しない場合は、引数は絶対不変!

Slide 32

Slide 32 text

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

Slide 33

Slide 33 text

ユニークキーマップの挿入イン タフェース向上 対応 33 答え 常に通る(ヤッター!)

Slide 34

Slide 34 text

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

Slide 35

Slide 35 text

ユニークキーマップの挿入イン タフェース向上 対応 35 答え 常に通る(ヤッター!)

Slide 36

Slide 36 text

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

Slide 37

Slide 37 text

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

Slide 38

Slide 38 text

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

Slide 39

Slide 39 text

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

Slide 40

Slide 40 text

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

Slide 41

Slide 41 text

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

Slide 42

Slide 42 text

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

Slide 43

Slide 43 text

ユニークキーマップの挿入イン タフェース向上 詳細 43 template iterator try_emplace(const_iterator hint, key_type&& k, Args&&... args); hint:3個目のオーバーロードと同じ。 k:2個目のオーバーロードと同じ。 args:1個目のオーバーロードと同じ。 戻り値:1個目のオーバーロードの iterator と同じ。 何で bo(ry

Slide 44

Slide 44 text

ユニークキーマップの挿入イン タフェース向上 詳細 44 あ、計算量は emplace、 emplace_hint と同じです

Slide 45

Slide 45 text

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

Slide 46

Slide 46 text

ユニークキーマップの挿入イン タフェース向上 46 不満点

Slide 47

Slide 47 text

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

Slide 48

Slide 48 text

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

Slide 49

Slide 49 text

ユニークキーマップの挿入イン タフェース向上 不満点 49 C++14で追加されたこんなヤツ std::map, std::less<>> m; auto res = m.find("foo"); // "foo" から std::string が作られない (残念ながら我らが愛する非順序連想コンテナには無い…)

Slide 50

Slide 50 text

ユニークキーマップの挿入イン タフェース向上 不満点 50 問題⑤:次のコードを順に実行すると、std::string オ ブジェクトはそれぞれ何個作られるか? std::map> m; m. emplace("foo", 42); // 何個?(挿入される m. emplace("foo", 42); // 何個?(挿入されないけど m.try_emplace("bar", 42); // 何個?(挿入される m.try_emplace("bar", 42); // 何個?(挿入されないけど

Slide 51

Slide 51 text

ユニークキーマップの挿入イン タフェース向上 不満点 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個(悲しい…)

Slide 52

Slide 52 text

ユニークキーマップの挿入イン タフェース向上 不満点 52 注意 キーは、たとえ異種混合比較コンテ ナでも、直接構築じゃなくて、メンバ 関数呼び出し前に構築、その後必 要に応じてコピー・ムーブになる しつこい…

Slide 53

Slide 53 text

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

Slide 54

Slide 54 text

ユニークキーマップの挿入イン タフェース向上 実装 54 Clang HEAD map template 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 だと呼ばれない…)

Slide 55

Slide 55 text

ユニークキーマップの挿入イン タフェース向上 実装 55 Clang HEAD unordered_map template 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 と完全に一致…

Slide 56

Slide 56 text

ユニークキーマップの挿入イン タフェース向上 実装 56 GCC HEAD map template 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}; } えぇ… 回避方法のまんまやし、もうちょっと頑張って欲しい…

Slide 57

Slide 57 text

ユニークキーマップの挿入イン タフェース向上 実装 57 GCC HEAD unordered_map template 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 は悔い改めて欲しい…

Slide 58

Slide 58 text

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

Slide 59

Slide 59 text

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

Slide 60

Slide 60 text

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

Slide 61

Slide 61 text

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

Slide 62

Slide 62 text

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

Slide 63

Slide 63 text

ユニークキーマップの挿入イン タフェース向上 回避策 63 1. find を使う 2. lower_bound を使う (でも map だけ) 3. operator[] を使う さっき似たようなの見たような…

Slide 64

Slide 64 text

ユニークキーマップの挿入イン タフェース向上 回避策 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

Slide 65

Slide 65 text

ユニークキーマップの挿入イン タフェース向上 回避策 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

Slide 66

Slide 66 text

ユニークキーマップの挿入イン タフェース向上 回避策 66 3. operator[] を使う m["foo"] = std::move(p); 欠点:mapped_typeがデフォルト構築可能じゃなきゃ ならない。 デフォルト構築されてから代入されるから、デフォ ルト構築が高価だとムダ感マジ半端ないって。 あと、今までキーが存在してたかどうかが分からな い。

Slide 67

Slide 67 text

ユニークキーマップの挿入イン タフェース向上 67 対応

Slide 68

Slide 68 text

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

Slide 69

Slide 69 text

ユニークキーマップの挿入イン タフェース向上 対応 69 template 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);

Slide 70

Slide 70 text

ユニークキーマップの挿入イン タフェース向上 対応 70 ポイント 普通の insert とは異なり、キーが存在した場合 には代入が走る 普通の insert とは異なり、キーと値が引数レベ ルで分離されてる

Slide 71

Slide 71 text

ユニークキーマップの挿入イン タフェース向上 対応 71 素朴な疑問点 何で emplace じゃなくて insert って名前なの? 何で variadic じゃないの?

Slide 72

Slide 72 text

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

Slide 73

Slide 73 text

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

Slide 74

Slide 74 text

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

Slide 75

Slide 75 text

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

Slide 76

Slide 76 text

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

Slide 77

Slide 77 text

ユニークキーマップの挿入イン タフェース向上 詳細 77 template iterator insert_or_assign(const_iterator hint, key_type&& k, M&& obj); hint:3個目のオーバーロードと同じ。 k:2個目のオーバーロードと同じ。 obj:1個目のオーバーロードと同じ。 戻り値:1個目のオーバーロードの iterator と同じ。

Slide 78

Slide 78 text

ユニークキーマップの挿入イン タフェース向上 詳細 78 あ、計算量は emplace、 emplace_hint と同じです さっきも聞いたような…

Slide 79

Slide 79 text

ユニークキーマップの挿入イン タフェース向上 実装 79 Clang HEAD map template 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); } えぇ… 回避方法のまんまやし、もうちょっと頑張って欲しい…

Slide 80

Slide 80 text

ユニークキーマップの挿入イン タフェース向上 実装 80 Clang HEAD unordered_map template 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 に代入追加しただけ…

Slide 81

Slide 81 text

ユニークキーマップの挿入イン タフェース向上 実装 81 GCC HEAD map template 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いらんやろ

Slide 82

Slide 82 text

ユニークキーマップの挿入イン タフェース向上 実装 82 GCC HEAD unordered_map template 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 はマジ悔い改めて欲しい…

Slide 83

Slide 83 text

ユニークキーマップの挿入イン タフェース向上 83 おまけ

Slide 84

Slide 84 text

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

Slide 85

Slide 85 text

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

Slide 86

Slide 86 text

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

Slide 87

Slide 87 text

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

Slide 88

Slide 88 text

(非順序)連想コンテナの接合 88 接合?

Slide 89

Slide 89 text

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

Slide 90

Slide 90 text

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

Slide 91

Slide 91 text

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

Slide 92

Slide 92 text

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

Slide 93

Slide 93 text

(非順序)連想コンテナの接合 導入の背景 93 1. (非順序)連想コンテナの要素をコ ンテナ間で移動したい! 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);

Slide 94

Slide 94 text

(非順序)連想コンテナの接合 導入の背景 94 auto it = s.find(42); // まぁしょうがない d.insert(std::move(*it)); // d側でメモリ割当&ムーブ s.erase(it); // s側でメモリ開放&デストラクタ メモリ割当&解放とか、ムーブコン ストラクタ&デストラクタとか、控え めに言ってムダじゃね? しかもこれじゃキーはムーブできてない… map じゃなくて set だとそもそもムーブできない…

Slide 95

Slide 95 text

(非順序)連想コンテナの接合 導入の背景 95 2. キーを変更したい! std::map m; m.emplace(42, "foo"); m.emplace(114514, "bar"); // ここで m の 42 のキーを 893 に変更したい…

Slide 96

Slide 96 text

(非順序)連想コンテナの接合 導入の背景 96 2. キーを変更したい! 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);

Slide 97

Slide 97 text

(非順序)連想コンテナの接合 導入の背景 97 auto it = m.find(42); // まぁしょうがない m.emplace(893, std::move(it->second)); // メモリ割当&ムーブ m.erase(it); // メモリ開放&デストラクタ メモリ割当&解放(ry

Slide 98

Slide 98 text

(非順序)連想コンテナの接合 98 対応

Slide 99

Slide 99 text

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

Slide 100

Slide 100 text

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

Slide 101

Slide 101 text

(非順序)連想コンテナの接合 対応 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); // 挿入位置のヒント付き ※ ユニークコンテナだとキー値重複で失敗する可能性があるので戻り値がちょっと違う

Slide 102

Slide 102 text

(非順序)連想コンテナの接合 対応 102 ポイント 切り離されたノードは「ノードハンドル」に格納さ れて、元のコンテナとは独立して管理される。 切り離されたノードは、「ノードハンドル」を通し て map のキー値や set の要素値を変更可能。

Slide 103

Slide 103 text

(非順序)連想コンテナの接合 対応 103 ノードハンドル?

Slide 104

Slide 104 text

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

Slide 105

Slide 105 text

(非順序)連想コンテナの接合 対応 105 ノードハンドルの特徴 • クラステンプレートの名前は規格では規定されていない。 • コンテナ C のノードハンドルの型は C::node_type で参照可能(ノード 自体の型じゃないのにこの名前なのは混乱しやすくないですか…)。 • 元のコンテナが破棄されてもノードを管理できる。 • 元のコンテナのアロケータのコピーを独自に持っている。 • ムーブオンリーである(コピーは出来ない)。 • ノードを持たない空のノードハンドルも作れる。 • ノードを持ったまま破棄されると、アロケータを使ってノードを適切に 破棄してくれる。 • map のキーや set の要素など、普段は変更不可なものも変更可能。 • 異なる型のコンテナ間でも互換性あり(次ページ参照)

Slide 106

Slide 106 text

(非順序)連想コンテナの接合 対応 106 ノードハンドルの互換性 下記の同じセル内はノードハンドルのやりとりが可能 ただし、アロケータは型が同じ必要があるだけでなく、オブ ジェクトも等しくなければならない。 map系 比較関数が違っていてもOK map multimap set系 比較関数が違っていてもOK set multiset unordered_map系 ハッシュ・比較関数が違っていてもOK unordered_map unordered_multimap unordered_set系 ハッシュ・比較関数が違っていてもOK unordered_set unordered_multiset

Slide 107

Slide 107 text

(非順序)連想コンテナの接合 対応 107 ノードハンドルの詳細 メンバ型 using value_type = コンテナの value_type; // set 系にしか無い using key_type = コンテナの key_type; // map 系にしか無い using mapped_type = コンテナの mapped_type; // map 系にしか無い using allocator_type = コンテナの allocator_type;

Slide 108

Slide 108 text

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

Slide 109

Slide 109 text

(非順序)連想コンテナの接合 対応 109 ノードハンドルの詳細 デストラクタ ~node_handle(); ノードハンドルが空じゃなかったら、手持ちのアロケータで、destroy メン バ関数を使って要素を破棄してから、deallocate メンバ関数を使ってメモ リを解放。(実際は allocator_traits を介して起動)

Slide 110

Slide 110 text

(非順序)連想コンテナの接合 対応 110 ノードハンドルの詳細 代入演算子 node_handle& operator=(node_handle&&); // ムーブ代入演算子 引数のノードハンドルからノードとアロケータを奪う。引数のノードハンド ルは空になる(元々空なら自分も空になる) 。 元々ノードを持っていた場合には、デストラクタと同様当該ノードを破棄 するが、その場合、自分と引数のアロケータが等しいか、アロケータの propagate_on_container_move_assignment が true か、のどちらかでなけ ればならない。(この条件いりますかね…)

Slide 111

Slide 111 text

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

Slide 112

Slide 112 text

(非順序)連想コンテナの接合 対応 112 ノードハンドルの詳細 その他 allocator_type get_allocator() const; // アロケータを取得 explicit operator bool() const noexcept; // 空じゃなかったら true bool empty() const noexcept; // 空だったら true 見たまんま。 アロケータは、空のノードハンドルに対して呼び出したら UB。

Slide 113

Slide 113 text

(非順序)連想コンテナの接合 対応 113 ノードハンドルの詳細 swap void swap(node_handle&) noexcept(※); どちらかが空のノードハンドルか、アロケータが等しいか、アロケータの propagate_on_container_swap が true か、 のいずれかでないと UB。 ※アロケータのpropagate_on_container_swap が true かアロケータの is_always_equal が true であれば noexcept。 非メンバ関数版もあるよ。(メンバ関数を呼び出すだけ)

Slide 114

Slide 114 text

(非順序)連想コンテナの接合 対応 114 再掲:ノードを切り離すメンバ関数 node_type extract(const key_type& x); // 対象をキーで指定 node_type extract(const_iterator position); // 対象をイテレータで指定 上側の、キーで指定するバージョンは、該当するキーが無かったら空のノー ドハンドルが返る。 非ユニークコンテナだと、最初に見つかったノードが返る。 下側のイテレータで指定するバージョンは、イテレータが有効な要素を指し ていないといけない。つまり、position は cend() ではいけない。(cend() なら 空のノードハンドル返せばいいと思うんだけど…)

Slide 115

Slide 115 text

ユニークキーマップの挿入イン タフェース向上 詳細 115 あ、計算量は対応する erase と同じです

Slide 116

Slide 116 text

(非順序)連想コンテナの接合 対応 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 は無視されるかも…

Slide 117

Slide 117 text

ユニークキーマップの挿入イン タフェース向上 詳細 117 あ、計算量は対応する insert と同じです

Slide 118

Slide 118 text

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

Slide 119

Slide 119 text

(非順序)連想コンテナの接合 対応 119 insert_return_type? ⇓ こんなやつ(ただし、名前は規定されてない) template struct INSERT_RETURN_TYPE { Iterator position; // insert に成功したら挿入された要素、 // 失敗したら挿入しようとした要素と同一のキーの要素 bool inserted; // insert に成功したら true、失敗したら false NodeType node; // insert に成功したら空、失敗したら引数 nh からムーブ }; 3番目のノードハンドル、必要ですかね… (一応 d.insert(s.extract(...)) ってやりたかったからっぽいけど…)

Slide 120

Slide 120 text

(非順序)連想コンテナの接合 対応 120 完

Slide 121

Slide 121 text

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

Slide 122

Slide 122 text

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

Slide 123

Slide 123 text

(非順序)連想コンテナの接合 123 おまけ

Slide 124

Slide 124 text

(非順序)連想コンテナの接合 おまけ 124 機能テストマクロ __cpp_lib_node_extract 201606L (C++17 以降使ってれば使う機会無いけど…)

Slide 125

Slide 125 text

(非順序)連想コンテナの接合 対応 125 完