Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

std::map Requirements for Keys (Design Decision)

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==?

like image 842
OmarOthman Avatar asked Feb 23 '12 13:02

OmarOthman


People also ask

Can a map have 2 Keys C++?

Implement a MultiKeyMap in C++A MultiKeyMap is a map that offers support for multiple keys. It is exactly the same as a normal map, except that it needs a container to store multiple keys. A simple solution to implement a MultiKeyMap in C++ is using std::pair for the key.

Is map faster than unordered_map?

For the map implementation, it takes 200 ms to finish the job. For the unordered_map + map , it takes 70 ms for unordered_map insertion and 80 ms for map insertion. So the hybrid implementation is 50 ms faster. We should think twice before we use the map .

What is the behavior of std::map if we try to set a key that does not exist?

"it inserts one at that position." - it increases the size of map by 1 if it doesn't exist.

Why is map key needed?

A map key or legend is an essential part of the map. It explains what the symbols on the map mean and allows you to make sense of the map. Maps are very valuable tools that can be used to easily show things that would otherwise be difficult to understand.


1 Answers

operator== is inevitable for std::map::find()

This is where you go badly wrong. map does not use operator== at all, it is not "inevitable". Two keys x and y are considered equivalent for the purposes of the map if !(x < y) && !(y < x).

map doesn't know or care whether you've implemented operator==. Even if you have, it need not be the case that all equivalent keys in the order are equal according to operator==.

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 like if (!(x < y) && !(y < x)) that does both tests close together.

Additionally, none of this is necessarily based on operator<. The default comparator for map is std::less<KeyType>, and that by default uses operator<. But if you've specialized std::less for KeyType then you needn't define operator<, and if you specify a different comparator for the map then it may or may not have anything to do with operator< or std::less<KeyType>. So where I've said x < y above, really it's cmp(x,y), where cmp is the strict weak order.

This flexibility is another reason why not to drag operator== into it. Suppose KeyType is std::string, and you specify your own comparator that implements some kind of locale-specific, case-insensitive collation rules. If map used operator== 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 < key then key < current, leaving no other option but equivalence. The situation is exactly:

if (search_key < current_element)
    go_left();
else if (current_element < search_key)
    go_right();
else
    declare_equivalent();

and what you're suggesting is:

if (search_key < current_element)
    go_left();
else if (current_element < search_key)
    go_right();
else if (current_element == search_key)
    declare_equivalent();

which is obviously not needed. In fact, it's your suggestion that's less efficient!

like image 51
Steve Jessop Avatar answered Oct 06 '22 17:10

Steve Jessop