新たに定義した構造体をstd::setに入れる方法

普段、整数型(int型やlong型、long long型など)やstd::stringクラスくらいしか入れないのですっかり失念していたのですが、std::setクラスに自分で定義した構造体を入れるようにしたらコンパイルエラーになりました。たぶんstd::mapでも同じなんじゃないかな?

あとから思うと「それはそう」と思うのですが、忘れないようにメモしておきます。

std::setはinsert()時に比較を行う。

std::setはキーを内部で自動的にソートして保持しています。これによってデータの追加、削除、検索を高速に行えます。 内部的には二分木で持っており、左の子供の値 < 自分の値 < 右の子供の値の関係が成立するようになっているため、値をinsert()するときに比較する必要があります。

この値の比較を行うときに、構造体やクラスだと、値をかんたんに比較することができず、以下のようなエラーが出てしまいます。

C2678エラー
C2678エラー

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;})のようにラムダ式をコンストラクタにわたす、といった手もあるみたいなので調べてみてください。