2

I'm trying to compile this piece of code but with Gcc15 and Clang21 it doesn't compile. It does compile with gcc 14 and Clang20.

I want to create a std::unordered_map and use a class with explicit constructor as key.

#include <unordered_map>
#include <iostream>

class Key{
public:
    explicit Key(){}
    explicit Key(unsigned int x):_k{x}
    {}
    size_t operator()(const Key& p) const {
        return p._k;
    }
private:
    unsigned int _k;

};

int main(int argc, char** argv) {

  std::unordered_map<Key, int, Key> m;
  return 0;
}

The problem is related to the keywork explicit. Removing the explicit keyword make the code compile.

This is the error reported by gcc15

In file included from /cefs/ac/ac576b8994a054d93ddc62cc_gcc-trunk-20251014/include/c++/16.0.0/bits/hashtable.h:37,
                 from /cefs/ac/ac576b8994a054d93ddc62cc_gcc-trunk-20251014/include/c++/16.0.0/bits/unordered_map.h:33,
                 from /cefs/ac/ac576b8994a054d93ddc62cc_gcc-trunk-20251014/include/c++/16.0.0/unordered_map:43,
                 from <source>:1:
/cefs/ac/ac576b8994a054d93ddc62cc_gcc-trunk-20251014/include/c++/16.0.0/bits/hashtable_policy.h: In instantiation of 'constexpr std::__detail::_Hash_code_base<_Key, _Value, _ExtractKey, _Hash, _RangeHash, _Unused, <anonymous> >::_Hash_code_base() [with _Key = Key; _Value = std::pair<const Key, int>; _ExtractKey = std::__detail::_Select1st; _Hash = Key; _RangeHash = std::__detail::_Mod_range_hashing; _Unused = std::__detail::_Default_ranged_hash; bool <anonymous> = true]':
recursively required from 'std::unordered_map<_Key, _Tp, _Hash, _Pred, _Alloc>::unordered_map() [with _Key = Key; _Tp = int; _Hash = Key; _Pred = std::equal_to<Key>; _Alloc = std::allocator<std::pair<const Key, int> >]'
<source>:19:37:   
   19 |   std::unordered_map<Key, int, Key> m;
      |                                     ^
required from here
<source>:19:37:   
/cefs/ac/ac576b8994a054d93ddc62cc_gcc-trunk-20251014/include/c++/16.0.0/bits/hashtable_policy.h:1056:62: error: converting to 'Key' from initializer list would use explicit constructor 'Key::Key()'
 1056 |       [[__no_unique_address__]] _Hashtable_ebo_helper<_Hash> _M_hash{};
      |                                                              ^~~~~~~
<source>:6:14: note: 'Key::Key()' declared here
    6 |     explicit Key(){}
      |   

How can i get it to compile with the newer compilers?

3
  • you are using Key as a hash functor. But Key is not a functor, since it is not default constructible Commented Oct 16 at 10:03
  • 6
    Using the same type as key and as its hasher is weird. Make a separate hasher struct. Commented Oct 16 at 10:08
  • godbolt.org/z/b18dP7fv1 Commented Oct 16 at 11:50

1 Answer 1

9

You marked Key() as explicit which prevents std::unordered_map to default construct the hash functor.

You need to construct the object yourself:

std::unordered_map<Key, int, Key> m(10,Key{});

I recommend writing a specialization for std::hash<Key> instead.

Sign up to request clarification or add additional context in comments.

10 Comments

Why does explicit prevent default construction (see counterexample: godbolt.org/z/a3z45Yfe7)? Why did it work under gcc14, but not in gcc 15?
when it is default constructible an implementation can do something like this godbolt.org/z/rvMa3qEc1 (though, it doesnt have to, hence seeing it pass isnt a big surprise)
@463035818_is_not_an_ai Hmm. I wonder if standard mandates that this should compile, because otherwise Key passes standard default constructible type traits. But that would be another follow-up question I guess.
I had to look it up, and it comes as a surprise to me too, but std::is_default_constructible merely checks if T obj(); is well formed. Not sure if I am missing something
related: stackoverflow.com/q/42786565/4117728. Seems like one has to check std::is_convertible in addition
just writing std::unordered_map<Key, int, Key> m(10); or std::unordered_map<Key, int, Key> m(0); make it compile
godbolt.org/z/ff7Yn5jnM MSVC do not like it.
you need to add those 2 method to make it compile under MSVC bool operator==(Key const & r) const {return (_k == r._k);} bool operator!=(Key const & r) const {return !(*this == r);}
@463035818_is_not_an_ai The thing is, standard mandates hasher to be Cpp17DefaultConstructible (assuming std::unordered_map is created with default constructor) and that doesn���t seem to require anything that explicit prevents. But this discussion is off-topic, I'll draft a new question for language-lawyers :)
As stated in eel.is/c++draft/unord.map.cnstr , unordered_map<Key, int, Key> m; calls a delegating constructor that calls unordered_map(size_type(some implementation defined value), hasher(), key_equal(), allocater_type()), the exact same constructor called by unordered_map<Key, int, Key> m(10,Key{});, (which has some default arguments) so if the former was problematic / not meeting some requirement, (10, Key{}) would be too

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.