How to Use Abseil Hash Framework with Custom Types in C++
To make a custom type hashable with Abseil, implement a template AbslHashValue friend function that combines your data members using H::combine, enabling use with absl::Hash<T> and Abseil containers like absl::flat_hash_map.
The Abseil library provides a robust hashing framework that extends beyond standard std::hash to deliver high-quality, consistent hashing across its container implementations. Located in the abseil/abseil-cpp repository, the core API resides in absl/hash/hash.h, while the underlying algorithms and hardware-accelerated optimizations are implemented in absl/hash/internal/hash.h. This framework allows you to integrate user-defined types seamlessly using the AbslHashValue extension point.
Understanding the Hashable Contract
A type becomes compatible with Abseil's hashing infrastructure when it satisfies one of two conditions. According to the implementation in absl/hash/hash.h, the framework checks for AbslHashValue first, then falls back to std::hash<T>.
AbslHashValue as the Primary Extension Point
The preferred method for making custom types hashable is implementing a template friend function named AbslHashValue. As defined in the source, this function must adhere to the following signature:
template <typename H>
friend H AbslHashValue(H h, const MyType& v);
The function receives a hash state h (conforming to the internal hash_internal::Hasher concept) and returns the updated state after mixing the members of v. This design allows the framework to support piece-wise hashing and algorithmic optimizations without exposing internal details.
Fallback to std::hash
If AbslHashValue is not defined, the framework automatically falls back to std::hash<T>. However, this bypasses Abseil's optimized algorithms and consistency guarantees.
Implementing AbslHashValue for Custom Types
To implement hashing for a custom struct, you define AbslHashValue as a friend function that delegates member hashing to H::combine. This method, utilized throughout the Abseil codebase including in absl/hash/hash_testing.h, ensures that each member contributes to the final hash value.
Consider a Person struct containing a string, integer, and vector:
#include "absl/hash/hash.h"
#include <string>
#include <vector>
struct Person {
std::string name;
int age;
std::vector<double> scores;
// Define the hash extension point
template <typename H>
friend H AbslHashValue(H h, const Person& p) {
// Combine each member into the hash state
return H::combine(std::move(h), p.name, p.age, p.scores);
}
};
Once defined, you can use Person directly with absl::Hash or in Abseil containers:
#include "absl/container/flat_hash_map.h"
int main() {
absl::flat_hash_map<Person, int> map;
Person alice{"Alice", 30, {95.0, 88.5}};
map[alice] = 1; // Works because Person satisfies the hashable contract
size_t h = absl::Hash<Person>{}(alice); // Direct hash calculation
}
The H::combine function handles the underlying algorithm details, including support for recursive hashing of composite types like std::vector and std::string.
Why Prefer AbslHashValue Over std::hash?
Specializing std::hash works for standard containers, but Abseil's framework offers distinct advantages as implemented in absl/hash/internal/hash.h and absl/hash/internal/city.h:
Consistency: All Abseil containers (absl::flat_hash_map, absl::node_hash_set, etc.) use absl::Hash internally, ensuring identical hash values across your codebase regardless of container choice.
Performance: The framework automatically utilizes hardware-accelerated CRC32 when available and employs piece-wise hashing algorithms (derived from CityHash) for large buffers. This optimization happens transparently in absl/hash/internal/hash.h without requiring changes to your AbslHashValue implementation.
Extensibility: When your type contains other hashable types, H::combine recursively invokes their respective AbslHashValue implementations, creating a uniform hashing strategy without manual algorithm mixing.
Advanced: Custom Hash Algorithms
For scenarios requiring a specific mixing strategy, you can create a custom hasher and integrate it within AbslHashValue. The only requirement is satisfying the internal hash_internal::Hasher concept used by the framework.
struct MyHasher {
template <typename T>
static size_t Mix(const T& value, size_t state) {
// Example: simple xor-shift mix
return state ^ std::hash<T>{}(value) + 0x9e3779b97f4a7c15ULL;
}
};
struct MyType {
int field;
template <typename H>
friend H AbslHashValue(H h, const MyType& v) {
// Use the custom mixing function
return H::combine(std::move(h), MyHasher::Mix(v.field, /*state=*/0));
}
};
This approach maintains compatibility with absl::Hash while allowing fine-grained control over the hash computation.
Summary
- Primary extension point: Implement
template <typename H> friend H AbslHashValue(H h, const T& v);to make custom types hashable. - Member mixing: Use
H::combineinsideAbslHashValueto recursively hash data members, handling vectors, strings, and nested types automatically. - Implementation location: The public API lives in
absl/hash/hash.h, while core algorithms and hardware optimizations reside inabsl/hash/internal/hash.handabsl/hash/internal/city.h. - Container compatibility: Once implemented, types work immediately with
absl::flat_hash_map,absl::Hash<T>, and other Abseil containers. - Performance benefits: The framework automatically applies hardware-accelerated CRC32 and CityHash-based algorithms without additional code.
Frequently Asked Questions
What is the exact signature required for AbslHashValue?
The function must be a template friend function accepting a hash state and your type, returning the updated state: template <typename H> friend H AbslHashValue(H h, const MyType& v);. This signature is required by the absl::Hash functor defined in absl/hash/hash.h to properly invoke piece-wise hashing.
How does Abseil's framework handle hashing of composite types (members that are themselves custom types)?
When you call H::combine(h, member) inside AbslHashValue, the framework recursively checks for AbslHashValue on that member's type. If defined, it uses that implementation; otherwise, it falls back to std::hash. This recursive resolution happens transparently in absl/hash/internal/hash.h, allowing complex nested structures to hash correctly without manual delegation.
Can I use std::hash specialization alongside Abseil's framework?
Yes, but Abseil prefers AbslHashValue. If both are defined, AbslHashValue takes precedence because absl::Hash checks for it first before falling back to std::hash. For consistency across Abseil containers, you should implement AbslHashValue even if std::hash is specialized.
Where is the underlying hash algorithm implemented in the Abseil source?
The default algorithms are implemented in absl/hash/internal/hash.h (core combiner logic and hardware CRC32 support) and absl/hash/internal/city.h (CityHash-based fallbacks). The public entry point in absl/hash/hash.h dispatches to these internal implementations based on platform capabilities and type characteristics.
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 →