c++visual-studiostlequalityunordered-multiset

Why does the == operator of std::unordered_multiset<T> returns wrong result when T is a pointer type?


Is this a bug, or am I doing something wrong? I already tried providing hashing and equality functors for the pointer type, but it doesn't seem to work. I even tried creating my own miniature template container just to test the functors.

Hashing functor:

class CharPtHash
{
private:
    using pChar = char*;
public:
    size_t operator()(const pChar& c) const
    {
        std::hash<char> hasher;
        if (c == nullptr)
        {
            return 0;
        }
        return hasher(*c);
    }
};

Equality:

class CharPtEqual
{
private:
    using pChar = char*;
public:
    bool operator()(const pChar& lhs, const pChar& rhs)const
    {

        if (lhs == rhs)//not sure of nullptr is equal to itself.
        {
            return true;
        }
        else if (lhs==nullptr || rhs==nullptr)
        {
            return false;
        }
        return *lhs == *rhs;
    }
};

Main:

int main()
{
    cout << "Testing unordered_multiset with keys being simple types:\n";
    unordered_multiset<char> sA1({ 'a','b','c' });
    unordered_multiset<char> sA2({ 'a','c','b' });

    cout << "Values: " << endl << sA1 << endl << sA2 << endl;

    cout << (sA1 == sA2 ? "Equal" : "Not Equal");
    cout << endl;

    cout << "Testing unordered_multiset with keys being pointers to simple types:\n";
    char** c1 = new char* [3]{ new char('a'), new char('b'), new char('c') };
    char** c2 = new char* [3]{ new char('a'), new char('c'), new char('b') };

    unordered_multiset<char*,CharPtHash,CharPtEqual> sB1;
    unordered_multiset<char*,CharPtHash,CharPtEqual> sB2;

    sB1.insert(c1[0]);
    sB1.insert(c1[1]);
    sB1.insert(c1[2]);
    sB2.insert(c2[0]);
    sB2.insert(c2[1]);
    sB2.insert(c2[2]);

    cout << "Values: " << endl << sB1 << endl << sB2 << endl;

    cout << (sB1 == sB2 ? "Equal" : "Not Equal");
    cout << endl;

    cin.get();
}

I tried compiling it to c++20 and c++14 using Visual Studio 2022.

This is the output:

Testing unordered_multiset with keys being simple types:
Values:
{ a, b, c }
{ a, c, b }
Equal
Testing unordered_multiset with keys being pointers to simple types:
Values:
{ a, b, c }
{ a, c, b }
Not Equal

Solution

  • Supplying your own KeyEqual only change the behavior internally, i.e. inserting new items. However it has no effects on operator==.

    According to operator==(std::unordered_multiset), the behavior of it is as if each equivalent equal_ranges were compared with std::is_permutation.

    You can potentially specialize the behavior of std::is_permutation for your set pre-C++20(this is undefined behavior since C++20):

    template<>
    bool std::is_permutation(
        std::unordered_multiset<char*, CharPtHash, CharPtEqual>::const_iterator l_begin, 
        std::unordered_multiset<char*, CharPtHash, CharPtEqual>::const_iterator l_end, 
        std::unordered_multiset<char*, CharPtHash, CharPtEqual>::const_iterator r_begin)
    {
        return std::is_permutation(l_begin, l_end, r_begin, CharPtEqual{});
    }
    

    Or just create your own char* wrapper with a custom operator==.