How to Integrate Custom Types with absl::Hash for Swiss Tables in Abseil
To make a custom type hashable for Abseil's Swiss-table containers, provide an AbslHashValue overload in the same namespace as your type that combines the object's fields into the provided hash state, which the container automatically discovers via argument-dependent lookup.
Abseil's hashing framework decouples the hash algorithm from the hash state, enabling efficient "Swiss-table" containers like absl::flat_hash_set and absl::node_hash_map. Integrating custom types requires implementing the AbslHashValue extension point defined in absl/hash/hash.h. This article demonstrates the exact implementation patterns used in the abseil/abseil-cpp repository.
The absl::Hash Lookup Order
When a Swiss-table container needs to hash a value, it invokes absl::Hash<T>, which follows a strict resolution order defined in absl/hash/hash.h (lines 155–162). The framework checks three possible sources:
- Built-in support — Primitives, strings, pointers, and other fundamental types handled internally.
- User-provided
AbslHashValue— A free function discovered via argument-dependent lookup (ADL) in the type's namespace. std::hash<T>specialization — A backward-compatibility fallback maintained for legacy code.
The Swiss-table containers use the default hash policy defined in absl/container/internal/hash_function_defaults.h (lines 15–37), which forwards directly to absl::Hash. This means any type satisfying step two above works automatically without specializing the container's template arguments.
Implementing the AbslHashValue Extension Point
The AbslHashValue function receives a hash state (H) and must return the state after combining the type's members. The state's combine method—implemented in absl/hash/internal/hash.h—mixes each field using the currently selected hash algorithm (chosen per-process at startup).
Public Struct with Public Members
For simple aggregates, declare the overload as a friend function inside the struct definition. This places the function in the surrounding namespace while granting it access to members.
// point.h
#include "absl/hash/hash.h"
struct Point {
int x;
int y;
template <typename H>
friend H AbslHashValue(H state, const Point& p) {
return H::combine(std::move(state), p.x, p.y);
}
};
// main.cc
#include "absl/container/flat_hash_set.h"
#include "point.h"
int main() {
absl::flat_hash_set<Point> points;
points.insert({1, 2});
// Lookups automatically use the custom hash
if (points.contains(Point{1, 2})) { /* ... */ }
}
Private Members and Friend Declarations
When members are private, the friend declaration remains the idiomatic pattern. The function is defined inline within the class, making it a non-member function found via ADL while accessing private data.
// secret_id.h
#include "absl/hash/hash.h"
class SecretId {
public:
explicit SecretId(uint64_t v) : value_(v) {}
private:
uint64_t value_;
template <typename H>
friend H AbslHashValue(H state, const SecretId& id) {
return H::combine(std::move(state), id.value_);
}
};
// main.cc
#include "absl/container/node_hash_map.h"
#include "secret_id.h"
int main() {
absl::node_hash_map<SecretId, std::string> map;
map.emplace(SecretId{42}, "answer");
}
Enabling Heterogeneous Lookup
To avoid constructing temporary objects during lookups, provide an additional AbslHashValue overload that accepts a compatible "view" type. This allows the Swiss table to hash a absl::string_view when the key type is a custom string wrapper, for example.
// name.h
#include "absl/hash/hash.h"
#include <string>
class Name {
public:
explicit Name(std::string s) : data_(std::move(s)) {}
const std::string& get() const { return data_; }
private:
std::string data_;
template <typename H>
friend H AbslHashValue(H state, const Name& n) {
return H::combine(std::move(state), n.data_);
}
};
// Heterogeneous overload for string_view
template <typename H>
inline H AbslHashValue(H state, absl::string_view sv) {
return H::combine(std::move(state), sv);
}
// main.cc
#include "absl/container/flat_hash_set.h"
#include "name.h"
int main() {
absl::flat_hash_set<Name> names;
names.emplace(Name{"Alice"});
// Lookup without constructing a Name object
if (names.contains(absl::string_view("Alice"))) { /* ... */ }
}
How Swiss Tables Consume Hash Values
The core Swiss-table implementation in absl/container/internal/raw_hash_set.h delegates all hashing operations to the hash functor provided by the container's policy. By default, absl::flat_hash_set, absl::node_hash_map, and related containers use the Hash alias defined in hash_function_defaults.h, which is absl::Hash<T>.
Because absl::Hash resolves to user-defined AbslHashValue overloads via ADL, no additional configuration is required. Once the overload is visible at the point of container instantiation, insertions, lookups, and rehashing automatically utilize the custom hash logic.
Summary
- Provide
AbslHashValueas a free function in your type's namespace (often as afriendinside the class) to make it hashable. - Combine fields using
H::combine(std::move(state), member1, member2, ...)to mix data into the hash state using the process-wide algorithm. - Support heterogeneous lookup by overloading
AbslHashValuefor view types compatible with your class, avoiding temporary constructions during find operations. - No container specialization is needed;
absl::flat_hash_setand other Swiss tables automatically useabsl::Hash, which discovers your overload via argument-dependent lookup.
Frequently Asked Questions
Why does Abseil use ADL to find AbslHashValue instead of requiring std::hash specialization?
Requiring std::hash specialization forces users to open the std namespace and cannot access private class members without exposing them or using hacky workarounds. The ADL-based AbslHashValue mechanism allows the hash logic to reside naturally in the type's namespace and leverages friend declarations for clean encapsulation of private data.
What header files must I include to implement AbslHashValue?
Include "absl/hash/hash.h" to access the AbslHashValue extension point and the hash state interface. When using the type in containers, include the specific container header (e.g., "absl/container/flat_hash_set.h"), which transitively includes the default hash policies that forward to absl::Hash.
How does the combine method handle different hash algorithms?
The combine method defined in absl/hash/internal/hash.h type-erases the specific algorithm being used. When your AbslHashValue overload calls H::combine, it mixes the provided fields into the state using whichever algorithm was selected at process startup (e.g., Wyhash), ensuring consistent behavior across all types without hard-coding algorithm details.
Can I use AbslHashValue with existing types I don't control?
No. AbslHashValue overloads must be defined in the same namespace as the type they hash (or as friends within the class). For types from external libraries where you cannot modify the namespace, specialize absl::Hash<T> directly, though this is discouraged in favor of wrapping the type in a custom struct that defines AbslHashValue.
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 →