std::map, custom comparator's design constrain

2020-04-18 03:09发布

问题:

I have been trying defining a custom comparator for std::map container.

The question is : can I relay on == and != operators or would that break strict weak order ? Am I forced to use operator < ?

(One and Two classes do have operator!= and operator== defined correctly)

typedef std::pair<One, Two> MapKey_t;
class cmp
{
    bool operator()(const MapKey_t& left, const MapKey_t& right) const
    { return left.first != right.first && right.first == right.second; }
}

typedef std::map<MapKey_t, Three*, cmp> MyMap_t;

Since switching left with right would not change the comparator return value, will this be working?

I don't really care about how the items are sorted into the container, but I do not want duplicates to be part of it.

Update :

can I use the memory address to obtain strict weak ordering ?

class cmp
{
    bool operator()(const MapKey_t& left, const MapKey_t& right) const
    { 
        if(left.first == right.first)
            if(left.second != right.second)
                return false; // This represents for me, functionally, a duplicate
            else
                // Can't use operator < there since left.second equals right.second but
                // functionally for me, this is not a duplicate and should be stored
                // what about memory address strict weak ordering ?
                return &left < &right;
        else
            return left.first < right.first; // There I can use operator <
}

回答1:

You cannot rely on self consistent equals or not equals because these do not establish a strict weak ordering. The comparator return value should change if you switch the left and right arguments. It is essential for the map to be able to establish an ordering of elements, not just be able to distinguish whether two elements are equal.

It is very important that, is A < B is true, then B < A is false. Furthermore, if A < B and B < C are both true, then A < C is also true.

If you do not want to or cannot establish a strict weak ordering for your types, then, then you could use a map that doesn't require it: std::unordered_map1, which is a hash map. However, this will require that you provide a hashing function and an equality comparison. It also requires that your compiler have C++11 support.

1 As @JohnDibling points out in comments, std::unordered_map is unfortunately named. It should have been std::hash_map but apparently that name could have clashed with hash_maps from other libraries. In any case, the intent is not to have a map that is not ordered, but to have one with constant time look-up.



回答2:

You may not care how things are ordered in the map, but the map does. The code you've provided above does not appear to implement strict weak ordering correctly. As you have already noted that switching left with right will not change the result of operator() in all cases, your function does not implement strict weak ordering.

You do not necesarrily have to implement the comparator in terms of operator< directly, but you must ensure that if operator()(A,B) returns true, then operator()(B,A) does not also return true.



回答3:

This is unacceptable, string weak ordering means that A < B and B < A shall not be true at the same time. std::map relies on this to establish both ordering and equality of the keys