$30 off During Our Annual Pro Sale. View Details »

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. C++17 の新機能
    (非順序)連想コンテナ編
    2018/8/23 鳥頭かりやマン
    1

    View Slide

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

    View Slide

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

    View Slide

  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

    View Slide

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

    View Slide

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

    map
    unordered_map

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    たとえ挿入しなくても、右辺値参照は
    ムーブされちゃったりする。

    View Slide

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

    ムーブされる条件は結構複雑。

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  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 には使えない。
    毎回こんなん書くの面倒だし覚えてられなくてバグり
    そう。

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    挿入しない場合は、引数は絶対不変!

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  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

    View Slide

  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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  84. ユニークキーマップの挿入イン
    タフェース向上 おまけ
    84
    機能テストマクロ

    __cpp_lib_map_try_emplace 201411L

    __cpp_lib_unordered_map_try_emplace 201411L
    map と unordered_map で別々に定義されているのね…
    (C++17 以降使ってれば使う機会無いけど…)

    View Slide

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

    View Slide

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

    View Slide

  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

    View Slide

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

    View Slide

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

    splicing
    良い日本語があったら教えてください…

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  93. (非順序)連想コンテナの接合
    導入の背景
    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);

    View Slide

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

    View Slide

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

    View Slide

  96. (非順序)連想コンテナの接合
    導入の背景
    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);

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

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

    View Slide

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

    View Slide

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

    View Slide

  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;

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  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 は無視されるかも…

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  124. (非順序)連想コンテナの接合
    おまけ
    124
    機能テストマクロ

    __cpp_lib_node_extract 201606L
    (C++17 以降使ってれば使う機会無いけど…)

    View Slide

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

    View Slide