♻️

Boost Multi-index Containers をいい感じにする

2020/09/26に公開

Boost Multi-index Containers Library を使うと複数のキーを持つコンテナが作れる

例えば以下の様なメンバを持ったクラスがあるとする

struct Item {
    int item_id;
    int item_category_id;
    std::string name;
    std::string description;
    // etc...
};

すると multi_index_container を使って以下の様に書ける

// タグの定義
struct tag_item_id { };
struct tag_item_category_id { };
struct tag_item_name { };

// コンテナ型の定義
using item_container_t = boost::multi_index_container<
    std::shared_ptr<Item>, // 格納する値の型
    boost::multi_index::indexed_by<
        boost::multi_index::hashed_unique< // 1つ目のキーは item_id
            boost::multi_index::tag<tag_item_id>,
            boost::multi_index::member<Item, int, &Item::item_id>
        >,
        boost::multi_index::hashed_non_unique< // 2つ目のキーは item_category_id
            boost::multi_index::tag<tag_item_category_id>,
            boost::multi_index::member<Item, int, &Item::item_category_id>
        >,
        boost::multi_index::hashed_non_unique< // 3つ目のキーは name
            boost::multi_index::tag<tag_item_name>,
            boost::multi_index::member<Item, std::string, &Item::name>
        >
    >
>;

// コンテナ
item_container_t items;
// 色々入れる
// items.insert(std::make_shared<Item>(...));
// ... 

// item_id が 100 のやつを取得
auto it_id_100 = items.get<tag_item_id>().find(100);

// item_category_id が 10 のやつを取得
auto it_category_10 = items.get<tag_item_category_id>().equal_range(10);

// name が "hoge" のやつを取得
auto it_hoge = items.get<tag_item_name>().find("hoge");

これでいいのだがコンテナ型の定義が長すぎて非常にめんどくさい
特にメンバ変数の型とかいちいち書くのだるいしメンバポインタ渡してるんだからそれから取得してほしい
が、下記ドキュメントではそれはできないからあきらめろと書いてある :anger:

https://www.boost.org/doc/libs/release/libs/multi_index/doc/tutorial/key_extraction.html#member

It might seem that the first and second parameters are superfluous, since the type of the base object and of the associated data field are already implicit in the pointer to member argument: unfortunately, it is not possible to extract this information with current C++ mechanisms, which makes the syntax of member a little too verbose.

しかし、これは嘘である
今のC++にできないことなどないはずである

なので頑張っていい感じにするヘルパーを作ってみた

https://gist.github.com/hikarin522/31bcdb5cd3437554f6f3f6380c35ac75

これを導入すると以下の様に簡単に書ける


// タグの定義
struct tag_item_name { };

// コンテナ型の定義
using item_container_t = boost::multi_index_container<
    std::shared_ptr<Item>, // 格納する値の型
    boost::multi_index::indexed_by<
        IDX(&Item::item_id)::hashed_unique<>,                // 1つ目のキーは item_id
        IDX(&Item::item_category_id)::hashed_non_unique<>,   // 2つ目のキーは item_category_id
        IDX(&Item::name, tag_item_name)::hashed_non_unique<> // 3つ目のキーは name, tag型も指定できる
    >
>;

// コンテナ
item_container_t items;
// 色々入れる
// items.insert(std::make_shared<Item>(...));
// ... 

// item_id が 100 のやつを取得
auto it_id_100 = items.get<IDX(&Item::item_id)>().find(100);

// item_category_id が 10 のやつを取得
auto it_category_10 = items.get<IDX(&Item::item_category_id)>().equal_range(10);

// name が "hoge" のやつを取得
auto it_hoge = items.get<IDX(&Item::name)>().find("hoge");
auto it_hoge = items.get<tag_item_name>().find("hoge"); // タグ型でもいける

IDX マクロで ##__VA_ARGS__ を使ってるので gcc じゃないとだめ
ちなみに C++17 だと IDX マクロは不要になる
https://cpprefjp.github.io/lang/cpp17/declaring_non-type_template_arguments_with_auto.html

参考文献

https://www.boost.org/doc/libs/release/libs/multi_index/
https://boostjp.github.io/tips/multi_index.html
https://faithandbrave.hateblo.jp/entry/20091126/1259226643

Discussion