c++stlunordered-set

Does unordered_set not support `unordered_set<vector<int>>` or `unordered_set<pair<int,int>>`?


unordered_set<pair<int,int>> vis;
unordered_set<vector<int>> vis;

Both of them are wrong, but if I change them to

set<vector<int>> vis;
set<pair<int,int>> vis;

then they are correct. Why?

int test()
{
        unordered_set<pair<int,int>> vis;
        return 0;
}

Compile Error:

error: call to implicitly-deleted default constructor of 'unordered_set<pair<int, int>>'
        unordered_set<pair<int,int>> vis;
                                     ^
/usr/bin/../lib/gcc/x86_64-linux-gnu/9/../../../../include/c++/9/bits/unordered_set.h:135:7: note: explicitly defaulted function was implicitly deleted here
      unordered_set() = default;
      ^
/usr/bin/../lib/gcc/x86_64-linux-gnu/9/../../../../include/c++/9/bits/unordered_set.h:100:18: note: default constructor of 'unordered_set<std::pair<int, int>, std::hash<std::pair<int, int>>, std::equal_to<std::pair<int, int>>, std::allocator<std::pair<int, int>>>' is implicitly deleted because field '_M_h' has a deleted default constructor
      _Hashtable _M_h;
                 ^
/usr/bin/../lib/gcc/x86_64-linux-gnu/9/../../../../include/c++/9/bits/hashtable.h:414:7: note: explicitly defaulted function was implicitly deleted here
      _Hashtable() = default;
      ^
/usr/bin/../lib/gcc/x86_64-linux-gnu/9/../../../../include/c++/9/bits/hashtable.h:174:7: note: default constructor of '_Hashtable<std::pair<int, int>, std::pair<int, int>, std::allocator<std::pair<int, int>>, std::__detail::_Identity, std::equal_to<std::pair<int, int>>, std::hash<std::pair<int, int>>, std::__detail::_Mod_range_hashing, std::__detail::_Default_ranged_hash, std::__detail::_Prime_rehash_policy, std::__detail::_Hashtable_traits<true, true, true>>' is implicitly deleted because base class '__detail::_Hashtable_base<pair<int, int>, pair<int, int>, _Identity, equal_to<pair<int, int>>, hash<pair<int, int>>, _Mod_range_hashing, _Default_ranged_hash, _Hashtable_traits<true, true, true>>' has a deleted default constructor
    : public __detail::_Hashtable_base<_Key, _Value, _ExtractKey, _Equal,
      ^
/usr/bin/../lib/gcc/x86_64-linux-gnu/9/../../../../include/c++/9/bits/hashtable_policy.h:1822:5: note: explicitly defaulted function was implicitly deleted here
    _Hashtable_base() = default;
    ^
/usr/bin/../lib/gcc/x86_64-linux-gnu/9/../../../../include/c++/9/bits/hashtable_policy.h:1771:5: note: default constructor of '_Hashtable_base<std::pair<int, int>, std::pair<int, int>, std::__detail::_Identity, std::equal_to<std::pair<int, int>>, std::hash<std::pair<int, int>>, std::__detail::_Mod_range_hashing, std::__detail::_Default_ranged_hash, std::__detail::_Hashtable_traits<true, true, true>>' is implicitly deleted because base class '_Hash_code_base<std::pair<int, int>, std::pair<int, int>, std::__detail::_Identity, std::hash<std::pair<int, int>>, std::__detail::_Mod_range_hashing, std::__detail::_Default_ranged_hash, _Hashtable_traits<true, true, true>::__hash_cached::value>' has a deleted default constructor
  : public _Hash_code_base<_Key, _Value, _ExtractKey, _H1, _H2, _Hash,
    ^
/usr/bin/../lib/gcc/x86_64-linux-gnu/9/../../../../include/c++/9/bits/hashtable_policy.h:1373:7: note: explicitly defaulted function was implicitly deleted here
      _Hash_code_base() = default;
      ^
/usr/bin/../lib/gcc/x86_64-linux-gnu/9/../../../../include/c++/9/bits/hashtable_policy.h:1349:7: note: default constructor of '_Hash_code_base<std::pair<int, int>, std::pair<int, int>, std::__detail::_Identity, std::hash<std::pair<int, int>>, std::__detail::_Mod_range_hashing, std::__detail::_Default_ranged_hash, true>' is implicitly deleted because base class '_Hashtable_ebo_helper<1, std::hash<std::pair<int, int>>>' has a deleted default constructor
      private _Hashtable_ebo_helper<1, _H1>,
      ^
/usr/bin/../lib/gcc/x86_64-linux-gnu/9/../../../../include/c++/9/bits/hashtable_policy.h:1096:7: note: explicitly defaulted function was implicitly deleted here
      _Hashtable_ebo_helper() = default;
      ^
/usr/bin/../lib/gcc/x86_64-linux-gnu/9/../../../../include/c++/9/bits/hashtable_policy.h:1094:7: note: default constructor of '_Hashtable_ebo_helper<1, std::hash<std::pair<int, int>>, true>' is implicitly deleted because base class 'std::hash<std::pair<int, int>>' has a deleted default constructor
    : private _Tp
      ^
/usr/bin/../lib/gcc/x86_64-linux-gnu/9/../../../../include/c++/9/bits/functional_hash.h:101:19: note: default constructor of 'hash<std::pair<int, int>>' is implicitly deleted because base class '__hash_enum<std::pair<int, int>>' has no default constructor
    struct hash : __hash_enum<_Tp>
                  ^
1 error generated.

Solution

  • By default, no. For unordered_set, it needs to be able to hash the objects, and std::pair and std::vector don't have a default hash implementation. There are two typical ways to do this; provide you own Hash type, or implement the std::hash functor for those types. Let's look at both.

    First off all, this is how std::unordered_set is defined:

    template<
        class Key,
        class Hash = std::hash<Key>,
        class KeyEqual = std::equal_to<Key>,
        class Allocator = std::allocator<Key>
    > class unordered_set;
    

    The thing we're interested in is the second template parameter, Hash = std::hash<Key>. As it currently stands, your program is failing to compile because the specialization of std::hash<std::pair<int, int>> doesn't exist. So we could provide it:

    template <>
    struct std::hash<std::pair<int, int>>
    {
        std::size_t operator()(std::pair<int, int> p) {
            return std::hash<int>{}(p.first) ^ std::hash<int>{}(p.second);
        }
    };
    

    Here I am delegating to std::hash<int> to implement the hash, however you can implement this however you want.

    Alternatively you can provide you own struct instead of implementing std::hash, you just then have to declare it when you define your unordered_set object:

    struct MyHash
    {
        std::size_t operator()(std::pair<int, int> p) {
            return std::hash<int>{}(p.first) ^ std::hash<int>{}(p.second);
        }
    };
    
    int main() {
        std::unordered_set<std::pair<int, int>, MyHash> mySet; // Using my own hashing class.
    }
    

    For a bit of fun, in C++20, you're allowed to use lambdas in unevaluated contexts such as decltype, so the following is also valid:

    int main() {
        const auto hash = [](std::pair<int, int> p) {
            return std::hash<int>{}(p.first) ^ std::hash<int>{}(p.second);
        }
    
        std::unordered_set<std::pair<int, int>, decltype(hash)> mySet; 
    }