Skip to content

Commit ae034fd

Browse files
authored
Merge pull request #404 from cppalliance/402
Add support for `std::hash`
2 parents e69f3fa + abd8979 commit ae034fd

9 files changed

Lines changed: 391 additions & 0 deletions

File tree

doc/modules/ROOT/nav.adoc

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
*** xref:api_reference.adoc#api_cstdlib[`<cstdlib>`]
2424
*** xref:api_reference.adoc#api_charconv[`<charconv>`]
2525
*** xref:api_reference.adoc#api_cmath[`<cmath>`]
26+
*** xref:api_reference.adoc#api_functional[`<functional>`]
2627
*** xref:api_reference.adoc#api_iostream[`<iostream>`]
2728
*** xref:api_reference.adoc#api_ios[`<ios>`]
2829
*** xref:api_reference.adoc#api_numeric[`<numeric>`]
@@ -54,6 +55,7 @@
5455
* xref:cstdlib.adoc[]
5556
* xref:charconv.adoc[]
5657
* xref:stream.adoc[]
58+
* xref:hash.adoc[]
5759
* xref:numeric.adoc[]
5860
* xref:string.adoc[]
5961
* xref:utilities.adoc[]

doc/modules/ROOT/pages/api_reference.adoc

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,12 @@ https://www.boost.org/LICENSE_1_0.txt
7272
| https://en.cppreference.com/w/cpp/types/numeric_limits[`std::numeric_limits<int128_t>`]
7373
| Numeric limits specialization for `int128_t`
7474

75+
| xref:hash.adoc[`std::hash<uint128_t>`]
76+
| Hash specialization for `uint128_t`
77+
78+
| xref:hash.adoc[`std::hash<int128_t>`]
79+
| Hash specialization for `int128_t`
80+
7581
| xref:cstdlib.adoc#div_structs[`u128div_t`]
7682
| Result type for `div(uint128_t, uint128_t)`
7783

@@ -164,6 +170,20 @@ Listed by analogous STL header.
164170
| Computes quotient and remainder simultaneously
165171
|===
166172

173+
[#api_functional]
174+
=== xref:hash.adoc[`<functional>`]
175+
176+
[cols="1,2", options="header"]
177+
|===
178+
| Specialization | Description
179+
180+
| xref:hash.adoc[`std::hash<uint128_t>`]
181+
| Enables `uint128_t` as a key in unordered associative containers
182+
183+
| xref:hash.adoc[`std::hash<int128_t>`]
184+
| Enables `int128_t` as a key in unordered associative containers
185+
|===
186+
167187
[#api_formatting]
168188
=== xref:format.adoc[Formatting]
169189

@@ -367,6 +387,9 @@ Listed by analogous STL header.
367387
| xref:format.adoc#std_format[`<boost/int128/format.hpp>`]
368388
| Formatting integration for pass:[C++20] `<format>`
369389

390+
| xref:hash.adoc[`<boost/int128/hash.hpp>`]
391+
| `std::hash` specializations for `int128_t` and `uint128_t`
392+
370393
| `<boost/int128/int128.hpp>`
371394
| The xref:uint128_t.adoc[`uint128_t`] and xref:int128_t.adoc[`int128_t`] types
372395

doc/modules/ROOT/pages/file_structure.adoc

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,9 @@ The entire library can be consumed via `<boost/int128.hpp>`, or by independently
3939
| xref:format.adoc[`<boost/int128/format.hpp>`]
4040
| C++20 `std::format` support
4141

42+
| xref:hash.adoc[`<boost/int128/hash.hpp>`]
43+
| `std::hash` specializations for `int128_t` and `uint128_t`
44+
4245
| `<boost/int128/int128.hpp>`
4346
| Core type definitions (`uint128_t`, `int128_t`)
4447

doc/modules/ROOT/pages/hash.adoc

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
////
2+
Copyright 2026 Matt Borland
3+
Distributed under the Boost Software License, Version 1.0.
4+
https://www.boost.org/LICENSE_1_0.txt
5+
////
6+
7+
[#hash]
8+
= Hashing
9+
:idprefix: hash_
10+
11+
The `<boost/int128/hash.hpp>` header provides specializations of `std::hash` for `uint128_t` and `int128_t`, allowing the library types to be used as keys in `std::unordered_map`, `std::unordered_set`, and any other container that relies on `std::hash`.
12+
13+
[source, c++]
14+
----
15+
#include <boost/int128/hash.hpp>
16+
----
17+
18+
[#hash_specializations]
19+
== Specializations
20+
21+
[source, c++]
22+
----
23+
namespace std {
24+
25+
template <>
26+
struct hash<boost::int128::int128_t>
27+
{
28+
std::size_t operator()(boost::int128::int128_t v) const noexcept;
29+
};
30+
31+
template <>
32+
struct hash<boost::int128::uint128_t>
33+
{
34+
std::size_t operator()(boost::int128::uint128_t v) const noexcept;
35+
};
36+
37+
} // namespace std
38+
----
39+
40+
Each 64-bit half of the value is first run through a SplitMix64 finalizer so that every input bit influences the lower bits of the result.
41+
This is necessary because `std::hash<std::uint64_t>` is permitted to truncate to `std::size_t`, which would lose the upper 32 bits on 32-bit platforms and cause distinct 128-bit values to collide.
42+
The two finalized halves are then combined with the `boost::hash_combine` mixing formula.
43+
44+
[#hash_guarantees]
45+
== Guarantees
46+
47+
* Two values comparing equal under `operator==` produce the same hash.
48+
* For any non-zero `v`, `std::hash<int128_t>{}(v) != std::hash<int128_t>{}(-v)`.
49+
* The mixing function is asymmetric, so `{high, low}` and `{low, high}` do not collide except by chance.
50+
* The hash value is implementation-defined and may differ across platforms, compilers, or library versions. Do not persist hash values across runs.
51+
52+
[#hash_example]
53+
== Example
54+
55+
[source, c++]
56+
----
57+
#include <boost/int128/int128.hpp>
58+
#include <boost/int128/hash.hpp>
59+
#include <unordered_map>
60+
61+
int main()
62+
{
63+
std::unordered_map<boost::int128::uint128_t, int> counts {};
64+
counts[boost::int128::uint128_t{1, 0}] = 1;
65+
counts[boost::int128::uint128_t{0, 1}] = 2;
66+
67+
return 0;
68+
}
69+
----

include/boost/int128.hpp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,5 +16,6 @@
1616
#include <boost/int128/cstdlib.hpp>
1717
#include <boost/int128/string.hpp>
1818
#include <boost/int128/utilities.hpp>
19+
#include <boost/int128/hash.hpp>
1920

2021
#endif // BOOST_INT128_HPP

include/boost/int128/hash.hpp

Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
// Copyright 2026 Matt Borland
2+
// Distributed under the Boost Software License, Version 1.0.
3+
// https://www.boost.org/LICENSE_1_0.txt
4+
5+
#ifndef BOOST_INT128_HASH_HPP
6+
#define BOOST_INT128_HASH_HPP
7+
8+
#include <boost/int128/int128.hpp>
9+
10+
#ifndef BOOST_INT128_BUILD_MODULE
11+
12+
#include <cstddef>
13+
#include <cstdint>
14+
#include <functional>
15+
16+
#endif
17+
18+
namespace boost {
19+
namespace int128 {
20+
namespace detail {
21+
22+
// The cast is only useless for 64-bit platforms
23+
// Without we get an implicit conversion warning which is arguably worse
24+
#if defined(__GNUC__) && !defined(__clang__)
25+
# pragma GCC diagnostic push
26+
# pragma GCC diagnostic ignored "-Wuseless-cast"
27+
#endif
28+
29+
// splitmix64 finalizer: mixes all 64 input bits into the result before any narrowing to size_t.
30+
// This is required for correctness on platforms where size_t is 32 bits
31+
inline std::size_t hash_finalize_64(std::uint64_t v) noexcept
32+
{
33+
v ^= v >> 30;
34+
v *= UINT64_C(0xbf58476d1ce4e5b9);
35+
v ^= v >> 27;
36+
v *= UINT64_C(0x94d049bb133111eb);
37+
v ^= v >> 31;
38+
return static_cast<std::size_t>(v);
39+
}
40+
41+
#if defined(__GNUC__) && !defined(__clang__)
42+
# pragma GCC diagnostic pop
43+
#endif
44+
45+
} // namespace detail
46+
} // namespace int128
47+
} // namespace boost
48+
49+
namespace std {
50+
51+
template <>
52+
struct hash<boost::int128::int128_t>
53+
{
54+
auto operator()(const boost::int128::int128_t v) const noexcept -> std::size_t
55+
{
56+
const std::size_t low_hash {boost::int128::detail::hash_finalize_64(v.low)};
57+
const std::size_t high_hash {boost::int128::detail::hash_finalize_64(static_cast<std::uint64_t>(v.high))};
58+
59+
// boost::hash_combine style mixing of the two finalized halves
60+
return low_hash ^ (high_hash + static_cast<std::size_t>(0x9e3779b9) + (low_hash << 6) + (low_hash >> 2));
61+
}
62+
};
63+
64+
template <>
65+
struct hash<boost::int128::uint128_t>
66+
{
67+
auto operator()(const boost::int128::uint128_t v) const noexcept -> std::size_t
68+
{
69+
const std::size_t low_hash {boost::int128::detail::hash_finalize_64(v.low)};
70+
const std::size_t high_hash {boost::int128::detail::hash_finalize_64(v.high)};
71+
72+
// boost::hash_combine style mixing of the two finalized halves
73+
return low_hash ^ (high_hash + static_cast<std::size_t>(0x9e3779b9) + (low_hash << 6) + (low_hash >> 2));
74+
}
75+
};
76+
77+
} // namespace std
78+
79+
#endif // BOOST_INT128_HASH_HPP

test/Jamfile

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,9 @@ run test_num_digits.cpp ;
9090
run test_spaceship_operator.cpp ;
9191
run test_to_string.cpp ;
9292

93+
# Warnings about padding propagate out of <utility>
94+
run test_hash.cpp : : : <toolset>msvc:<cxxflags>/wd4324 ;
95+
9396
# Make sure we run the examples as well
9497
run ../examples/construction.cpp ;
9598
run ../examples/bit.cpp ;
@@ -123,6 +126,7 @@ compile compile_tests/charconv_compile.cpp ;
123126
compile compile_tests/climits_compile.cpp ;
124127
compile compile_tests/cstdlib_compile.cpp ;
125128
compile compile_tests/format_compile.cpp ;
129+
compile compile_tests/hash_compile.cpp ;
126130
compile compile_tests/int128_compile.cpp ;
127131
compile compile_tests/iostream_compile.cpp ;
128132
compile compile_tests/limits_compile.cpp ;
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
// Copyright 2026 Matt Borland
2+
// Distributed under the Boost Software License, Version 1.0.
3+
// https://www.boost.org/LICENSE_1_0.txt
4+
5+
#include <boost/int128/hash.hpp>
6+
7+
int main()
8+
{
9+
return 0;
10+
}

0 commit comments

Comments
 (0)