Custom Hash Functions
Hash your own types.
Custom Hash Functions is a free C++ Academy lesson on CoddyKit. This is lesson 3 of 4. Any 3 lessons of this learning path are free to read in full — after that, CoddyKit PRO unlocks every lesson, plus hands-on practice with a built-in code editor and a 24/7 AI tutor. It is part of the C++ Academy learning path, and your progress syncs across the web and the CoddyKit app. The C++ Academy course includes 4 lessons in total.
Why Custom Hashes?
Unordered containers need a way to hash their keys. Built-in types and std::string already have hashes, but your own types do not. You must provide one.
#include <iostream>
#include <unordered_set>
#include <string>
int main() {
std::unordered_set<std::string> s{"hi"};
std::cout << s.count("hi") << '\n';
return 0;
}The std::hash Template
std::hash is a functor that maps a value to a size_t. You call it like a function.
#include <iostream>
#include <functional>
#include <string>
int main() {
std::hash<std::string> h;
std::cout << "hash exists and returns a size_t\n";
std::size_t v = h("hello");
std::cout << (v != 0 ? "non-zero hash" : "zero") << '\n';
return 0;
}A Struct to Hash
Suppose we have a Point with two ints. To store it in an unordered_set we need both equality and a hash.
#include <iostream>
struct Point {
int x, y;
bool operator==(const Point& o) const { return x == o.x && y == o.y; }
};
int main() {
Point a{1, 2}, b{1, 2};
std::cout << std::boolalpha << (a == b) << '\n';
return 0;
}Writing a Hash Functor
A hash functor is a struct with operator() returning size_t. Combine field hashes, often with XOR and a shift.
#include <iostream>
#include <functional>
struct Point { int x, y; };
struct PointHash {
std::size_t operator()(const Point& p) const {
return std::hash<int>()(p.x) ^ (std::hash<int>()(p.y) << 1);
}
};
int main() {
PointHash h;
std::cout << "hashed: " << (h({3, 4}) != 0 ? "ok" : "zero") << '\n';
return 0;
}Using the Hash Functor
Pass the hash functor as the second template argument of the unordered container.
#include <iostream>
#include <unordered_set>
#include <functional>
struct Point {
int x, y;
bool operator==(const Point& o) const { return x == o.x && y == o.y; }
};
struct PointHash {
std::size_t operator()(const Point& p) const {
return std::hash<int>()(p.x) ^ (std::hash<int>()(p.y) << 1);
}
};
int main() {
std::unordered_set<Point, PointHash> pts;
pts.insert({1, 2});
pts.insert({1, 2});
std::cout << pts.size() << '\n';
return 0;
}Equality Is Required Too
Two keys land in the same bucket if their hashes collide. The container then uses operator== to tell them apart, so equality is mandatory.
#include <iostream>
#include <unordered_set>
struct Point {
int x, y;
bool operator==(const Point& o) const { return x == o.x && y == o.y; }
};
struct PointHash {
std::size_t operator()(const Point& p) const {
return std::hash<int>()(p.x * 31 + p.y);
}
};
int main() {
std::unordered_set<Point, PointHash> s{{1, 1}, {2, 2}};
std::cout << s.count({1, 1}) << '\n';
return 0;
}Hashing as a map Key
The same custom hash lets a struct be the key in an unordered_map.
#include <iostream>
#include <unordered_map>
#include <functional>
struct Point {
int x, y;
bool operator==(const Point& o) const { return x == o.x && y == o.y; }
};
struct PointHash {
std::size_t operator()(const Point& p) const {
return std::hash<int>()(p.x) ^ (std::hash<int>()(p.y) << 1);
}
};
int main() {
std::unordered_map<Point, std::string, PointHash> m;
m[{0, 0}] = "origin";
std::cout << m[{0, 0}] << '\n';
return 0;
}Combining Multiple Fields
A common helper combines hashes one field at a time using a multiplier-and-add pattern similar to boost::hash_combine.
#include <iostream>
#include <functional>
std::size_t combine(std::size_t seed, std::size_t v) {
return seed ^ (v + 0x9e3779b9 + (seed << 6) + (seed >> 2));
}
int main() {
std::size_t h = 0;
h = combine(h, std::hash<int>()(10));
h = combine(h, std::hash<int>()(20));
std::cout << (h != 0 ? "combined ok" : "zero") << '\n';
return 0;
}Good Hash Distribution
A poor hash that returns a constant puts everything in one bucket, degrading to O(n). Mix the bits of all fields well.
#include <iostream>
#include <functional>
struct Bad { std::size_t operator()(int) const { return 0; } };
struct Good { std::size_t operator()(int x) const { return std::hash<int>()(x); } };
int main() {
std::cout << Bad()(5) << ' ' << (Good()(5) != 0 ? "varies" : "0") << '\n';
return 0;
}Specializing std::hash
Alternatively, specialize std::hash for your type so it works without passing a functor explicitly.
#include <iostream>
#include <unordered_set>
struct Point {
int x, y;
bool operator==(const Point& o) const { return x == o.x && y == o.y; }
};
namespace std {
template <> struct hash<Point> {
std::size_t operator()(const Point& p) const {
return hash<int>()(p.x) ^ (hash<int>()(p.y) << 1);
}
};
}
int main() {
std::unordered_set<Point> s{{1, 2}};
std::cout << s.count({1, 2}) << '\n';
return 0;
}Lambda as a Hash
In C++20 you can even use a stateless lambda as a hash by passing its type.
#include <iostream>
#include <unordered_set>
int main() {
auto h = [](int x) { return std::hash<int>()(x * 2654435761u); };
std::unordered_set<int, decltype(h)> s(8, h);
s.insert(42);
std::cout << s.count(42) << '\n';
return 0;
}Quick Check
Test your understanding of custom hashing.
Recap
You learned to hash custom types:
- provide a hash functor (or specialize
std::hash) returningsize_t - also provide operator== so colliding keys are distinguished
- combine field hashes well for a good distribution
Next, you'll explore buckets and load factor performance.
Learn C++ with an AI tutor — free
Write and run real code in your browser, get instant help from a 24/7 AI tutor, and pick up where you left off on the web or in the app.
- Courses
- 51
- Lessons
- 203
Frequently Asked Questions
Is the “Custom Hash Functions” lesson free?
Yes — any 3 lessons of the C++ Academy learning path, including “Custom Hash Functions”, are free to read in full here on the web. After that, CoddyKit PRO unlocks every lesson, plus interactive practice with a built-in code editor and a 24/7 AI tutor. The C++ Academy course includes 4 lessons in total.
What will I learn in “Custom Hash Functions”?
Hash your own types. You practise C++ Academy with hands-on code you run directly in the browser, and a 24/7 AI tutor answers your questions as you work through the lesson.
Do I need any experience to start C++ Academy?
No prior experience is required. C++ Academy on CoddyKit is structured for beginners through advanced learners, so you can start here or from the beginning and move at your own pace. This is lesson 3 of 4.
How long does the “Custom Hash Functions” lesson take?
Most CoddyKit lessons take about 5–10 minutes. Each one is bite-sized and interactive, so you make steady progress and pick up exactly where you left off across the web and the app.
Can I write and run code in this C++ Academy lesson?
Yes. Every C++ Academy lesson includes a built-in code editor, so you write and run real code right in your browser and get instant AI feedback — no local setup required.
All lessons in this course
- std::unordered_map
- unordered_set
- Custom Hash Functions
- Performance Considerations