新たに定義した構造体をstd::setに入れる方法
普段、整数型(int
型やlong
型、long long
型など)やstd::string
クラスくらいしか入れないのですっかり失念していたのですが、std::set
クラスに自分で定義した構造体を入れるようにしたらコンパイルエラーになりました。たぶんstd::map
でも同じなんじゃないかな?
気分転換にディレクトリ内のファイル差分を出すコマンドラインで動くやつつくってる。
— mutax (@RMT_xxx) 2020年12月26日
std::filesystem::directory_entryのパスをstd::stringに入れる方法がわからーん!
これちがった。ディレクトリ要素を自作構造体(パス文字列、ディレクトリフラグ)にまとめてstd::setに突っ込んでたけど、構造体が < の演算子の比較に対応していないからだった。そりゃそうだな。
— mutax (@RMT_xxx) 2020年12月26日
あとから思うと「それはそう」と思うのですが、忘れないようにメモしておきます。
std::setはinsert()時に比較を行う。
std::set
はキーを内部で自動的にソートして保持しています。これによってデータの追加、削除、検索を高速に行えます。
内部的には二分木で持っており、左の子供の値 < 自分の値 < 右の子供の値の関係が成立するようになっているため、値をinsert()
するときに比較する必要があります。
この値の比較を行うときに、構造体やクラスだと、値をかんたんに比較することができず、以下のようなエラーが出てしまいます。
std::setに新たに定義した構造体を入れる方法
ではどうしたらいいのか?という話ですが、答えは簡単でoperator<
を定義すればよいです。
以下に自分が今回の問題を解決したあとのソースを載せます。
typedef struct _PATH_INFO{ std::string path; DIR_FILE info; bool operator<(const _PATH_INFO &rhs) const { if (path == rhs.path) return info > rhs.info; else return path > rhs.path; } } PATH_INFO; void makeDirList(std::set<PATH_INFO> &list, std::string path){ for (const fs::directory_entry& it : fs::recursive_directory_iterator(path.c_str())) { PATH_INFO buf; buf.path = it.path().relative_path().string(); buf.path = buf.path.substr(path.length() + 1, buf.path.length() - path.length()); buf.info = (it.is_directory() ? ID_DIRECTORY : ID_FILE); list.insert(buf); } }
ちなみに何やっているか説明すると、makeDirList()
ではstd::string path
で受け取ったパスのフォルダ以下のファイル/フォルダをすべて探索して、一つのファイル/フォルダの情報をPATH_INFO
構造体にまとめてstd::set<PATH_INFO> list
に格納しています。
このときoperator<
に定義した比較関数で比較することで、各PATH_INFO
構造体に格納されたデータが順序付けられて、std::set
の内部でソートされています。
補足:他の方法
今回はoperator<
を構造体に(クラスでも同様)定義する方法にしましたが、それ以外にも関数オブジェクトを作ってstd::set<T , CompFunc>
にしたり、std::set<T> s_map([](){ return a.a > b.a;})
のようにラムダ式をコンストラクタにわたす、といった手もあるみたいなので調べてみてください。