1

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
Ken White
  • 123,280
  • 14
  • 225
  • 444
  • Comparing unrelated pointers? [Is it unspecified behavior to compare pointers to different arrays for equality?](https://stackoverflow.com/questions/4909766/is-it-unspecified-behavior-to-compare-pointers-to-different-arrays-for-equality) – Jason Nov 14 '22 at 02:03
  • @JasonLiam Well, the answer says you can compare them just fine, so I don't see why mention it here. – Yksisarvinen Nov 14 '22 at 02:07
  • 1
    Why do you think the result is wrong? Do you think `new char('a')` would return the same pointer as the pointer returned by the previous `new char('a')`? They are not the same pointer. – Drew Dormann Nov 14 '22 at 02:10

2 Answers2

1

Well, my previous answer was completely wrong: as far as I can tell, your Hash and Pred are correct. However, the issue is somewhere else. operator == for std::unordered_multiset uses std::is_permutation() to perform comparison internally and doesn't provide any comparison function that algorithm, so default operator == for that type (in this case char*) is used. There is UB due to that I think, but I don't really understand the phrasing there.

To be fair, this looks like an oversight in standard. operator == for std::unordered_multimap doesn't allow comparing different types of maps, so it should be possible to pass Pred instance to std::is_permutation. Or maybe there is a reason to have it that way, but I don't see it.

Yksisarvinen
  • 18,008
  • 2
  • 24
  • 52
0

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

Ranoiaetep
  • 5,872
  • 1
  • 14
  • 39