Rule A18-1-6 (required, implementation, automated)
All std::hash specializations for user-defined types shall have a noexcept function call operator.
Rationale
Some of standard library containers use std::hash indirectly. Function call operator should be defined as noexcept to prevent container simple access from throwing an exception. Note: Consider own hash specializations to use standard library specializations combined with XOR (ˆ) operation as implemented by boost::hash_combine: seed ^= std::hash< decltype (v)>{}(v) + 0x9e3779b9 + (seed << 6) + (seed >> 2);
Example
// $Id: A18-1-6.cpp 311792 2018-03-15 04:15:08Z christof.meerwald $
#include <cstdint>
#include <functional>
#include <string>
#include <unordered_map>
class A
{
public:
A(uint32_t x, uint32_t y) noexcept : x(x), y(y) {}
uint32_t GetX() const noexcept { return x; }
uint32_t GetY() const noexcept { return y; }
friend bool operator == (const A &lhs, const A &rhs) noexcept
{ return lhs.x == rhs.x && lhs.y == rhs.y; }
private:
uint32_t x;
uint32_t y;
};
class B
{
public:
B(uint32_t x, uint32_t y) noexcept : x(x), y(y) {}
uint32_t GetX() const noexcept { return x; }
uint32_t GetY() const noexcept { return y; }
friend bool operator == (const B &lhs, const B &rhs) noexcept
{ return lhs.x == rhs.x && lhs.y == rhs.y; }
private:
uint32_t x;
uint32_t y;
};
namespace std
{
// Compliant
template <>
struct hash<A>
{
std::size_t operator()(const A& a) const noexcept
{
auto h1 = std::hash<decltype(a.GetX())>{}(a.GetX());
std::size_t seed { h1 + 0x9e3779b9 };
auto h2 = std::hash<decltype(a.GetY())>{}(a.GetY());
seed ^= h2 + 0x9e3779b9 + (seed << 6) + (seed >> 2);
return seed;
}
};
// Non-compliant: string concatenation can potentially throw
template <>
struct hash<B>
{
std::size_t operator()(const B& b) const
{
std::string s{std::to_string(b.GetX()) + ’,’ + std::to_string(b.GetY())};
return std::hash<std::string>{}(s);
}
};
}
int main()
{
std::unordered_map<A, bool> m1 { { A{5, 7}, true } };
if (m1.count(A{4, 3}) != 0)
{
// ....
}
std::unordered_map<B, bool> m2 { { B{5, 7}, true } };
// Lookup can potentially throw if hash function throws
if (m2.count(B{4, 3}) != 0)
{
// ....
}
}
See also
C++ Core Guidelines [11]: C.89: Make a hash noexcept.