std::mapなどで自作のクラスをキーにする場合、そのクラスはoperator<を定義していなければならない。クラスにメンバーが1つしかない場合は、それを比較するだけで簡単なのだが、2つ以上あるときは少し難しくなる。例えばメンバーa,bがある場合、以下のように書けない。
1 2 3 |
bool operator<(const C& that) const { return a_ < that.a_ && b_ < that.b_; } |
以下のようなoperator<検証プログラムを書くとassertに引っかかる。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 |
#include <iostream> #include <thread> #include <random> #include <cassert> #include <tuple> using namespace std; class C { int a_; int b_; public: C(int a, int b) : a_(a), b_(b) { cout << "C()" << "a_=" << a_ << "," << "b_=" << b_ << endl; } C(const C& c) { cout << "C(const C&)" << endl; } C(C&& c) { cout << "C(const C&&)" << endl; } ~C() { cout << "~C()" << endl; } bool operator==(const C& that) const { return a_==that.a_ && b_==that.b_; } bool operator!=(const C& that) const { return !(*this==that); } bool operator<(const C& that) const { return a_ < that.a_ && b_ < that.b_; } }; int main() { // https://stackoverflow.com/a/7560564 std::random_device rd; // obtain a random number from hardware std::mt19937 eng(rd()); // seed the generator std::uniform_int_distribution<> distr(0, 9); // define the range for(int i=0 ; i < 100; ++i) { C c1(distr(eng),distr(eng)); C c2(distr(eng),distr(eng)); // check operator< is valid if (c1 < c2) { assert(c1 != c2); assert(!(c2 < c1)); } else if(c2 < c1) { assert(c1 != c2); assert(!(c1 < c2)); } else { assert(c1==c2); } } } |
例えば
C(1,6) < C(7,0)
は
1 < 7 && 6 < 0 で
false
逆にすると
C(7,0) < C(1,6)
7 < 1 && 0 < 6 で
両方falseになってしまう。
ちゃんと書くと以下のようになる。
1 2 3 4 5 6 7 |
if(a_ < that.a_) return true; if(a_ == that.a_) return b_ < that.b_; return false; |
この書き方だと3つになった場合分けがわからなくなるので、std::tieを使ってスマートに書ける。
1 |
return tie(a_,b_) < tie(that.a_, that.b_); |
アルゴリズムとしては==の代わりに !< を使って1つずつ比較していく。
このような比較はlexicographical_compareといって、イテレータを渡して実現することもできる。このコードでイテレータを渡すのは強引だが以下のようになる。
1 |
lexicographical_compare(&a_, (&b_)+1, &that.a_, (&that.b_)+1); |