Let's say we have a snippet of code as follows
#include <list>
#include <string>
#include <unordered_map>
int main() {
std::list<int> myList = {4, 1, 3, 2};
std::unordered_map<std::list<int>::iterator, std::string> myMap;
myMap[myList.begin()] = "first element";
}
This code in itself wouldn't work since we have to define a template specialization for std::hash, like the one below
template<>
class std::hash<std::list<int>::iterator> {
public:
size_t operator()(std::list<int>::iterator const& it) const noexcept {
return hash<int*>()(&*it);
}
};
This works well.
But when I tried to generalize it and define the following instead,
template<typename T>
class std::hash<std::list<T>::iterator> {
public:
size_t operator()(std::list<T>::iterator const& it) const noexcept {
return hash<T*>()(&*it);
}
};
I see a static assertion failure again from the compiler. Why doesn't this work, and what is the right way to define std::hash for any list-iterator?
Compiler details
- g++ (GCC) 13.2.1 20230801
-std=c++23flag (C++23 standard)
There's just no way to make this work:
This is a non-deduced context, so deduction will always fail†. So you need to come up with some way to make your deduction viable.
The simplest approach is just to wrap:
This is straightforwardly viable and works just fine, it's just that you need to have a
unordered_map<ListIterator<T>, V>instead of anunordered_map<std::list<T>::iterator, V>. On the insertion side, shouldn't be a difference once you add an implicit constructor - but on the extraction side, yeah you might have to do an extra accessor here and there, but this'll get the job done.†Although I feel like the language should be extended to cover the case of matching types whose name is actually
C<T>::D- there are going to be contexts that cannot be deducible at all (likeadd_const<T>::type), but otherwise for the cases where you want stuff like this to work (and they do exist) you need to try to come up with wacky workarounds.