The following code results with unexplained calls to the hash function:
namespace foo {
using Position = tuple <int, int, int>;
std::ostream& operator<<(std::ostream& out, const Position& pos) noexcept{
return out << get<0>(pos) << ", " << get<1>(pos) << ", " << get<2>(pos);
}
struct hashFunc{
std::size_t operator()(const Position& pos) const noexcept{
int res = get<0>(pos) * 17 ^ get<1>(pos) * 11 ^ get<2>(pos);
cout << "@@@ hash function called for key: " << pos
<< ", hash: " << res << endl;
return res;
}
};
template<typename T>
void print_buckets(T&& map) {
auto num_buckets = map.bucket_count();
cout << "------------------------------" << endl;
cout << "NUM BUCKETS: " << num_buckets << endl;
for(size_t i=0; i<num_buckets; ++i) {
auto bucket_size = map.bucket_size(i);
if(bucket_size) {
cout << "BUCKET " << i << " size: " << bucket_size << endl;
}
}
cout << "------------------------------" << endl;
}
}
Main:
using namespace foo;
int main() {
// note: bucket_count specified
unordered_map <Position, std::string, hashFunc> test(10);
auto x = tuple{1,0,0};
auto z = tuple{0,1,0};
auto w = tuple{0,0,1};
cout << "==================================" << endl;
cout << "about to insert: " << x << endl;
test[x] = "hello";
print_buckets(test);
cout << "after insert of: " << x << endl;
cout << "==================================" << endl;
cout << "about to insert: " << z << endl;
test[z] = "hey";
print_buckets(test);
cout << "after insert of: " << z << endl;
cout << "==================================" << endl;
cout << "about to insert: " << w << endl;
test.insert({w, "hello"});
print_buckets(test);
cout << "after insert of: " << w << endl;
cout << "==================================" << endl;
}
Output:
==================================
about to insert: 1, 0, 0
@@@ hash function called for key: 1, 0, 0, hash: 17
------------------------------
NUM BUCKETS: 11
BUCKET 6 size: 1
------------------------------
after insert of: 1, 0, 0
==================================
about to insert: 0, 1, 0
@@@ hash function called for key: 0, 1, 0, hash: 11
@@@ hash function called for key: 1, 0, 0, hash: 17 <= why?
------------------------------
NUM BUCKETS: 11
@@@ hash function called for key: 1, 0, 0, hash: 17 <= why?
BUCKET 0 size: 1
BUCKET 6 size: 1
------------------------------
after insert of: 0, 1, 0
==================================
about to insert: 0, 0, 1
@@@ hash function called for key: 0, 0, 1, hash: 1
@@@ hash function called for key: 0, 1, 0, hash: 11 <= why?
------------------------------
NUM BUCKETS: 11
@@@ hash function called for key: 1, 0, 0, hash: 17 <= why?
BUCKET 0 size: 1
@@@ hash function called for key: 0, 1, 0, hash: 11 <= why?
BUCKET 1 size: 1
BUCKET 6 size: 1
------------------------------
after insert of: 0, 0, 1
==================================
Code (same behavior for gcc and clang)
Notes:
1. Trying the same without the bucket_count parameter for the constructor, calls to hash function become even more excessive, due to rehash. But in the scenario above it seems that there is no rehash and there are no collisions.
2. Related, but specifically on MSVC: Inserting to std::unordered_map calls hash function twice in MSVC++'s STL, bad design or special reason?