Search code examples
c++templatesfunctorunordered-set

Hashable type with overwritten operators or external functors


To use a custom type in a std::unordered_set I have to options.

1) Implement the == operator for my type and specialize std::hash

struct MyType {
    int x;

    bool operator==(const MyType& o) {
        return this.x == o.x;
    }
};

namespace std
{
template<>
struct hash<MyType> {
    size_t operator()(const MyType& o) const {
        return hash<int>()(o.x);
    }
};
}

std::unordered_set<MyType> mySet;

Or 2), provide functor classes:

struct MyTypeHash {
    size_t operator()(const MyType& o) const {
        return std::hash<int>()(o.x);
    }
};

struct MyTypeCompare {
  bool operator()(const MyType& o1, const MyType& o2) const {
    return o1.x == o2.x;
  }
};

std::unordered_set<MyType, MyTypeHash, MyTypeCompare> mySet;

The second approach lets me choose new behaviour for every new instantion of std::unordered_set, while with the first approach the behaviour as being part of the type itself will always be the same.

Now, if I know that I only ever want a single behaviour (I'll never define two different comparators for MyType), which approach is to be preferred? What other differences exist between those two?


Solution

  • Attaching the behavior to the type allows for code like

    template<template<class> Set,class T>
    auto organizeWithSet(…);
    
    /* elsewhere */ {
      organizeWithSet<std::unordered_set,MyType>(…);
      organizeWithSet<std::set,MyType>(…);
    }
    

    which obviously cannot pass custom function objects.

    That said, it is possible to define

    template<class T>
    using MyUnorderedSet=std::unordered_set<T, MyTypeHash,MyTypeCompare>;
    

    and use that as a template template argument, although that introduces yet another name and might be considered less readable.

    Otherwise, you have to consider that your operator== is simultaneously the default for std::unordered_set and std::find, among others; if the equivalence you want for these purposes varies, you probably want named comparators. On the other hand, if one suffices, C++20 might even let you define it merely with =default.