How absl::Hash Works with Custom Types: Requirements and Implementation Guide
To make a custom type hashable with absl::Hash, provide a free function template named AbslHashValue in the same namespace as your type that combines member fields using H::combine and returns the updated hash state.
The absl::Hash framework in the abseil/abseil-cpp repository provides a generic hashing mechanism that supports custom types through a specific extension interface. Unlike standard library hash specializations, Abseil's approach uses Argument-Dependent Lookup (ADL) and free functions to keep hashing logic separate from your type's public interface. Understanding how absl::Hash works with custom types ensures your classes integrate seamlessly with Abseil containers and hashing algorithms.
The Three Conditions for Hashability
absl::Hash<T> can hash any type that satisfies one of three conditions, evaluated in order as defined in absl/hash/hash.h (lines 99-106):
- Arithmetic or pointer types: Built-in integers, enums, floating-point numbers, and raw pointers are supported natively.
AbslHashValueoverload: A free function template that combines the object's fields into a hash state.std::hash<T>specialization: Legacy fallback support for types already compatible with the standard library.
The preferred approach is providing an AbslHashValue overload, as it leverages the full Abseil hashing framework and maintains better encapsulation.
Requirements for Custom Types
Define AbslHashValue as a Free Function
The AbslHashValue function must be a non-member function template defined in the same namespace as your custom type. According to the implementation in absl/hash/hash.h, the function signature follows this pattern:
template <typename H>
H AbslHashValue(H state, const MyType& v) {
return H::combine(std::move(state), v.field1, v.field2);
}
Key requirements:
- The function must return the updated hash state of type
H. - It must be discoverable via Argument-Dependent Lookup (ADL), requiring placement in the same namespace as the type.
- The function should combine all fields used in
operator==to ensure that equal objects produce equal hash values.
Ensure All Members Are Hashable
Every field passed to H::combine must itself be hashable by absl::Hash. The framework validates this at compile time using the hash_internal::is_hashable<T> trait (defined in absl/hash/internal/hash.h around lines 9-12). If any member lacks hash support, compilation will fail with a diagnostic indicating the unhashable type.
Avoid Specializing absl::Hash
The framework explicitly forbids specializing absl::Hash itself. As noted in absl/hash/hash.h (lines 52-53), all extensions must occur through AbslHashValue overloads rather than template specializations, ensuring consistent behavior across the hashing ecosystem.
Implementation Examples
Simple Struct with Public Members
For a basic struct, define the overload in the same namespace:
#include "absl/hash/hash.h"
struct Point {
int x;
int y;
};
template <typename H>
H AbslHashValue(H state, const Point& p) {
return H::combine(std::move(state), p.x, p.y);
}
// Usage
absl::Hash<Point> hasher;
size_t h = hasher(Point{3, 7});
Class with Private Members
When fields are private, declare AbslHashValue as a friend function:
#include "absl/hash/hash.h"
class Circle {
std::pair<int, int> center_;
int radius_;
public:
template <typename H>
friend H AbslHashValue(H state, const Circle& c) {
return H::combine(std::move(state), c.center_, c.radius_);
}
};
Type Erasure for PImpl Idioms
For types using the Pointer to Implementation (PImpl) pattern or virtual functions, use absl::HashState to erase the concrete hash state type:
#include "absl/hash/hash.h"
#include <typeindex>
class WidgetImpl;
class Widget {
public:
void HashValue(absl::HashState state) const;
template <typename H>
friend H AbslHashValue(H state, const Widget& w) {
state = H::combine(std::move(state), std::type_index(typeid(w)));
w.HashValue(absl::HashState::Create(&state));
return state;
}
};
This approach, documented in absl/hash/hash.h (lines 78-86), adds overhead and should only be used when templates cannot be employed.
Hashing Containers of Custom Types
Once AbslHashValue is defined, standard containers work automatically:
#include <vector>
#include "absl/hash/hash.h"
struct Item {
int id;
std::string name;
};
template <typename H>
H AbslHashValue(H state, const Item& i) {
return H::combine(std::move(state), i.id, i.name);
}
// Vector hashing uses the library's built-in container support
absl::Hash<std::vector<Item>> vec_hasher;
std::vector<Item> items = {{1, "a"}, {2, "b"}};
size_t hv = vec_hasher(items);
Core Hash State Operations
The hash state H passed to AbslHashValue provides three primary combination methods implemented in hash_internal::HashStateBase (from absl/hash/internal/hash.h):
H::combine(state, v1, v2, ...): Combines arbitrary variadic values into the state.H::combine_contiguous(state, data, size): Mixes a contiguous byte range efficiently.H::combine_unordered(state, begin, end): Provides order-independent hashing for unordered containers.
These primitives ensure high-quality hash distribution while maintaining consistency across the Abseil ecosystem.
Summary
- Provide
AbslHashValue: Define a free function template in your type's namespace that takesH stateandconst T&, returningH::combine(std::move(state), members...). - Ensure ADL visibility: Place the overload in the same namespace as your custom type so Argument-Dependent Lookup can find it.
- Hash all equality fields: Combine every member used in
operator==to maintain the contract that equal objects have equal hashes. - Members must be hashable: All fields passed to
H::combinemust themselves satisfyabsl::Hashrequirements. - No specialization: Never specialize
absl::Hash<T>directly; useAbslHashValueinstead. - Type erasure option: Use
absl::HashState::Create()when templates are unavailable, such as in virtual functions or PImpl implementations.
Frequently Asked Questions
What happens if I forget to put AbslHashValue in the same namespace as my type?
If AbslHashValue is not defined in the same namespace as your custom type, Argument-Dependent Lookup (ADL) will fail to find the function. According to the logic in absl/hash/hash.h (lines 99-106), the framework will then attempt to fall back to std::hash<T>. If no std::hash specialization exists, you will receive a compilation error indicating that the type is not hashable.
Can I specialize std::hash for my type instead of using AbslHashValue?
Yes, absl::Hash will detect and use a std::hash<T> specialization as a fallback mechanism. However, this is considered legacy support and is not recommended. Using AbslHashValue provides better integration with Abseil's hashing framework, supports more efficient state combination operations, and keeps hashing logic separate from your type's interface.
How do I hash a custom container that stores elements in an arbitrary order?
For containers where element order does not affect equality (such as std::unordered_set), use H::combine_unordered instead of H::combine. This method, available in the hash state object, ensures that the same elements produce the same hash value regardless of their storage order. You can find the implementation details in absl/hash/internal/hash.h.
Why does AbslHashValue need to be a free function rather than a member function?
absl::Hash uses Argument-Dependent Lookup (ADL) to find hashing implementations, which requires free functions defined in the same namespace as the type. Member functions cannot be found through ADL, and requiring them would force intrusive modifications to class interfaces. The free function approach allows you to add hashing support for types without modifying the class definition itself, such as when using the friend keyword for private members.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →