When I make a std::map<my_data_type, mapped_value>, what C++ expects from me is that my_data_type has its own operator<.
struct my_data_type
{
my_data_type(int i) : my_i(i) { }
bool operator<(const my_data_type& other) const { return my_i < other.my_i; }
int my_i;
};
The reason is that you can derive operator> and operator== from operator<. b < a implies a > b, so there’s operator>. !(a < b) && !(b < a) means that a is neither less than b nor greater than it, so they must be equal.
The question is: Why hasn’t the C++ designer require operator== to be explicitly defined? Obviously, operator== is inevitable for std::map::find() and for removing duplicates from the std::map. Why implement 5 operations and call a method twice in order not to compel me to explicitly implement operator==?
This is where you go badly wrong.
mapdoes not useoperator==at all, it is not “inevitable”. Two keysxandyare considered equivalent for the purposes of the map if!(x < y) && !(y < x).mapdoesn’t know or care whether you’ve implementedoperator==. Even if you have, it need not be the case that all equivalent keys in the order are equal according tooperator==.The reason for all this is that wherever C++ relies on orders (sorting, maps, sets, binary searches), it bases everything it does on the well-understood mathematical concept of a “strict weak order”, which is also defined in the standard. There’s no particular need for
operator==, and if you look at the code for these standard functions you won’t very often see anything likeif (!(x < y) && !(y < x))that does both tests close together.Additionally, none of this is necessarily based on
operator<. The default comparator formapisstd::less<KeyType>, and that by default usesoperator<. But if you’ve specializedstd::lessforKeyTypethen you needn’t defineoperator<, and if you specify a different comparator for the map then it may or may not have anything to do withoperator<orstd::less<KeyType>. So where I’ve saidx < yabove, really it’scmp(x,y), wherecmpis the strict weak order.This flexibility is another reason why not to drag
operator==into it. SupposeKeyTypeisstd::string, and you specify your own comparator that implements some kind of locale-specific, case-insensitive collation rules. Ifmapusedoperator==some of the time, then that would completely ignore the fact that strings differing only by case should count as the same key (or in some languages: with other differences that are considered not to matter for collation purposes). So the equality comparison would also have to be configurable, but there would only be one “correct” answer that the programmer could provide. This isn’t a good situation, you never want your API to offer something that looks like a point of customization but really isn’t.Besides, the concept is that once you’ve ruled out the section of the tree that’s less than the key you’re searching for, and the section of the tree for which the key is less than it, what’s left either is empty (no match found) or else has a key in it (match found). So, you’ve already used
current < keythenkey < current, leaving no other option but equivalence. The situation is exactly:and what you’re suggesting is:
which is obviously not needed. In fact, it’s your suggestion that’s less efficient!