I am trying to make an std::unordered_set whose key is of type std::tuple.
The below code compiled by MSVC gives me:
Error C3848 expression having type 'const _Hasher' would lose some const-volatile qualifiers
in order to call 'size_t TupleHash::operator ()<int,int,int>(const std::tuple<int,int,int> &)'
struct TupleHash
{
// Uncomment the const keyword below resolves the error
template<typename T1, typename T2, typename T3>
std::size_t operator()(const std::tuple<T1, T2,T3> &t) // const
{
return std::hash<T1>{}(std::get<0>(t)) ^ (std::hash<T2>{}(std::get<1>(t)) << 1) ^ ( ( std::hash<T2>{}(std::get<2>(t)) << 2 ) );
}
};
int main() {
std::unordered_set<std::tuple<int, int, int>, TupleHash> test;
test.insert(std::make_tuple(1, 2, 3));
test.insert(std::make_tuple(2, 3, 4));
test.insert(std::make_tuple(2, 3, 1));
test.insert(std::make_tuple(1, 2, 3));
for (auto& [field1, field2, field3] : test) {
printf("%d, %d, %d\n", field1, field2, field3);
}
}
Apparently, it is complaining that TupleHash::operator() is not declared as const. To fix it, I simply added a const keyword at the function signature.
Why does the regulation exist? I can understand that the argument of the hash function, i.e., std::tuple<T1, T2, T3> &t, needs to be constant, as hash container asks its element to be immutable, but why does the member function of the functor itself needs to be const too?
I just tested and confirmed GCC 12.3 behaves the same.
The answer is 'because the C++ standard says so'.
[hash.requirements]:
his a value of type (possibly const)H, ...
h(k)
(h(k) needs to be valid even if h is of a const-qualified type).
The reason that it needs to be callable as const is because sometimes it will only be accessible as a const object.
Consider this:
using Set = std::unordered_set<std::tuple<int, int, int>, TupleHash>;
Set get_set();
int main() {
const Set s = get_set();
return s.contains(std::tuple{0, 0, 0})
}
The set is const, so its subobject TupleHash will also generally be const, so it hashes std::tuple{0, 0, 0} with a const TupleHash.
If you really need mutable state for the hasher, use mutable fields. This is only really useful for caches because of how the result must be consistent.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With