From e91343d4a8e1c008c8959869126318bc4a02695f Mon Sep 17 00:00:00 2001 From: Matt Borland Date: Thu, 23 Jul 2026 10:01:03 -0400 Subject: [PATCH 1/6] Add hash_value overloads --- include/boost/int128/hash.hpp | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/include/boost/int128/hash.hpp b/include/boost/int128/hash.hpp index 9286c0fd..9d87975e 100644 --- a/include/boost/int128/hash.hpp +++ b/include/boost/int128/hash.hpp @@ -76,4 +76,20 @@ struct hash } // namespace std +namespace boost { +namespace int128 { + +inline std::size_t hash_value(const uint128_t v) noexcept +{ + return std::hash{}(v); +} + +inline std::size_t hash_value(const int128_t v) noexcept +{ + return std::hash{}(v); +} + +} // namespace int128 +} // namespace boost + #endif // BOOST_INT128_HASH_HPP From e94157acf832923cea7f99023df7fef43c936562 Mon Sep 17 00:00:00 2001 From: Matt Borland Date: Thu, 23 Jul 2026 10:20:52 -0400 Subject: [PATCH 2/6] Add container hash test --- test/CMakeLists.txt | 2 +- test/Jamfile | 2 + test/test_container_hash.cpp | 174 +++++++++++++++++++++++++++++++++++ 3 files changed, 177 insertions(+), 1 deletion(-) create mode 100644 test/test_container_hash.cpp diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index ea0cddf7..15ea555f 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -44,7 +44,7 @@ if(HAVE_BOOST_TEST) else() - boost_test_jamfile(FILE Jamfile LINK_LIBRARIES Boost::int128 Boost::core Boost::random Boost::multiprecision Boost::mp11 Boost::charconv) + boost_test_jamfile(FILE Jamfile LINK_LIBRARIES Boost::int128 Boost::core Boost::random Boost::multiprecision Boost::mp11 Boost::charconv Boost::container_hash Boost::unordered) endif() diff --git a/test/Jamfile b/test/Jamfile index cb5170e5..07fc43d1 100644 --- a/test/Jamfile +++ b/test/Jamfile @@ -103,6 +103,8 @@ run test_to_string.cpp ; # Warnings about padding propagate out of run test_hash.cpp : : : msvc:/wd4324 ; +# The Boost.ContainerHash and Boost.Unordered headers contain old-style casts +run test_container_hash.cpp : : : msvc:/wd4324 gcc:-Wno-old-style-cast clang:-Wno-old-style-cast ; run test_boundaries.cpp ; run test_constexpr_boundaries.cpp ; diff --git a/test/test_container_hash.cpp b/test/test_container_hash.cpp new file mode 100644 index 00000000..21e6c3b1 --- /dev/null +++ b/test/test_container_hash.cpp @@ -0,0 +1,174 @@ +// Copyright 2026 Matt Borland +// Distributed under the Boost Software License, Version 1.0. +// https://www.boost.org/LICENSE_1_0.txt + +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +// boost::hash dispatches (via ADL) to boost::int128::hash_value, which in +// turn delegates to std::hash. All three entry points must agree. +void test_boost_hash_matches_std_and_hash_value() +{ + using boost::int128::uint128_t; + using boost::int128::int128_t; + + const boost::hash u_hasher {}; + const std::hash u_std {}; + + std::mt19937_64 rng {42}; + for (int i {0}; i < 1024; ++i) + { + const uint128_t v {rng(), rng()}; + BOOST_TEST_EQ(u_hasher(v), boost::int128::hash_value(v)); + BOOST_TEST_EQ(u_hasher(v), u_std(v)); + } + + const boost::hash s_hasher {}; + const std::hash s_std {}; + + for (int i {0}; i < 1024; ++i) + { + const int128_t v {static_cast(rng()), rng()}; + BOOST_TEST_EQ(s_hasher(v), boost::int128::hash_value(v)); + BOOST_TEST_EQ(s_hasher(v), s_std(v)); + } +} + +// Equal values must hash equal through boost::hash. +void test_boost_hash_equal_values() +{ + using boost::int128::uint128_t; + using boost::int128::int128_t; + + const boost::hash u_hasher {}; + const uint128_t ua {UINT64_C(0xDEADBEEF), UINT64_C(0xCAFEBABE12345678)}; + const uint128_t ub {UINT64_C(0xDEADBEEF), UINT64_C(0xCAFEBABE12345678)}; + BOOST_TEST_EQ(u_hasher(ua), u_hasher(ub)); + + const boost::hash s_hasher {}; + const int128_t sa {INT64_C(-1), UINT64_C(0xCAFEBABE)}; + const int128_t sb {INT64_C(-1), UINT64_C(0xCAFEBABE)}; + BOOST_TEST_EQ(s_hasher(sa), s_hasher(sb)); + + // A value and its negation must not collide. + const boost::hash hasher {}; + for (std::int64_t i {1}; i <= 512; ++i) + { + const int128_t pos {i}; + const int128_t neg {-i}; + BOOST_TEST_NE(hasher(pos), hasher(neg)); + } +} + +// boost::hash_combine must be deterministic and order sensitive. +void test_hash_combine() +{ + using boost::int128::uint128_t; + + const uint128_t a {UINT64_C(1), UINT64_C(2)}; + const uint128_t b {UINT64_C(3), UINT64_C(4)}; + + std::size_t seed1 {0}; + boost::hash_combine(seed1, a); + boost::hash_combine(seed1, b); + + std::size_t seed2 {0}; + boost::hash_combine(seed2, a); + boost::hash_combine(seed2, b); + + // Same values combined in the same order produce the same seed. + BOOST_TEST_EQ(seed1, seed2); + + std::size_t seed3 {0}; + boost::hash_combine(seed3, b); + boost::hash_combine(seed3, a); + + // Order matters when the combined values differ. + BOOST_TEST_NE(seed1, seed3); +} + +// boost::hash_range over a container of 128-bit values. +void test_hash_range() +{ + using boost::int128::uint128_t; + + const std::vector v1 {uint128_t{0, 1}, uint128_t{1, 0}, uint128_t{UINT64_MAX, UINT64_MAX}}; + const std::vector v2 {uint128_t{0, 1}, uint128_t{1, 0}, uint128_t{UINT64_MAX, UINT64_MAX}}; + const std::vector v3 {uint128_t{1, 0}, uint128_t{0, 1}, uint128_t{UINT64_MAX, UINT64_MAX}}; + + // Equal ranges hash equal. + BOOST_TEST_EQ(boost::hash_range(v1.begin(), v1.end()), boost::hash_range(v2.begin(), v2.end())); + + // Reordered ranges hash differently. + BOOST_TEST_NE(boost::hash_range(v1.begin(), v1.end()), boost::hash_range(v3.begin(), v3.end())); +} + +// boost::unordered_map defaults to boost::hash, so the library types drop in +// with no explicit hasher. +void test_unordered_map_default_hash() +{ + using boost::int128::uint128_t; + using boost::int128::int128_t; + + boost::unordered_map umap {}; + umap[uint128_t{0, 1}] = 1; + umap[uint128_t{0, 2}] = 2; + umap[uint128_t{1, 0}] = 3; + umap[uint128_t{UINT64_MAX, UINT64_MAX}] = 4; + + BOOST_TEST_EQ((umap[uint128_t{0, 1}]), 1); + BOOST_TEST_EQ((umap[uint128_t{0, 2}]), 2); + BOOST_TEST_EQ((umap[uint128_t{1, 0}]), 3); + BOOST_TEST_EQ((umap[uint128_t{UINT64_MAX, UINT64_MAX}]), 4); + BOOST_TEST_EQ(umap.size(), (std::size_t {4})); + + boost::unordered_map smap {}; + smap[int128_t{0, 1}] = 1; + smap[int128_t{-1, UINT64_MAX}] = 2; + smap[int128_t{1, 0}] = 3; + smap[int128_t{-1, 0}] = 4; + + BOOST_TEST_EQ((smap[int128_t{0, 1}]), 1); + BOOST_TEST_EQ((smap[int128_t{-1, UINT64_MAX}]), 2); + BOOST_TEST_EQ((smap[int128_t{1, 0}]), 3); + BOOST_TEST_EQ((smap[int128_t{-1, 0}]), 4); + BOOST_TEST_EQ(smap.size(), (std::size_t {4})); +} + +// boost::unordered_set likewise deduplicates via boost::hash. +void test_unordered_set_default_hash() +{ + using boost::int128::uint128_t; + + boost::unordered_set set {}; + set.insert(uint128_t{0, 1}); + set.insert(uint128_t{0, 1}); // duplicate + set.insert(uint128_t{1, 0}); + + BOOST_TEST_EQ(set.size(), (std::size_t {2})); + BOOST_TEST(set.find(uint128_t{0, 1}) != set.end()); + BOOST_TEST(set.find(uint128_t{1, 0}) != set.end()); + BOOST_TEST(set.find(uint128_t{9, 9}) == set.end()); +} + +int main() +{ + test_boost_hash_matches_std_and_hash_value(); + test_boost_hash_equal_values(); + test_hash_combine(); + test_hash_range(); + test_unordered_map_default_hash(); + test_unordered_set_default_hash(); + + return boost::report_errors(); +} From 9bfc6ad48a103bd93f06e8622958c2043f664ea0 Mon Sep 17 00:00:00 2001 From: Matt Borland Date: Thu, 23 Jul 2026 10:31:58 -0400 Subject: [PATCH 3/6] Add container hash example --- examples/container_hash.cpp | 112 ++++++++++++++++++++++++++++++++++++ 1 file changed, 112 insertions(+) create mode 100644 examples/container_hash.cpp diff --git a/examples/container_hash.cpp b/examples/container_hash.cpp new file mode 100644 index 00000000..d9561fc8 --- /dev/null +++ b/examples/container_hash.cpp @@ -0,0 +1,112 @@ +// Copyright 2026 Matt Borland +// Distributed under the Boost Software License, Version 1.0. +// https://www.boost.org/LICENSE_1_0.txt + +// This example demonstrates Boost.ContainerHash integration with int128 types. +// Including injects hash_value overloads for the library +// types, so boost::hash, boost::hash_combine, boost::hash_range, and the +// boost::unordered containers (which default to boost::hash) all work with no +// extra configuration. + +#include +#include +// tag::exclude[] +// The Boost.ContainerHash and Boost.Unordered headers use old-style casts that +// trip the strict -Wold-style-cast the test build enables; silence them for +// these third-party headers only. This is not needed in normal use. +#if defined(__GNUC__) || defined(__clang__) +# pragma GCC diagnostic push +# pragma GCC diagnostic ignored "-Wold-style-cast" +#endif +// end::exclude[] +#include +#include +#include +// tag::exclude[] +#if defined(__GNUC__) || defined(__clang__) +# pragma GCC diagnostic pop +#endif +// end::exclude[] +#include +#include +#include +#include + +using boost::int128::uint128_t; +using boost::int128::int128_t; + +// A user-defined composite key that holds 128-bit fields. Providing a hash_value +// overload in the type's own namespace lets Boost.ContainerHash find it via ADL, +// and boost::hash_combine reuses the int128 hashes supplied by hash.hpp. +struct point +{ + int128_t x; + int128_t y; +}; + +bool operator==(const point& lhs, const point& rhs) +{ + return lhs.x == rhs.x && lhs.y == rhs.y; +} + +std::size_t hash_value(const point& p) +{ + std::size_t seed {0}; + boost::hash_combine(seed, p.x); + boost::hash_combine(seed, p.y); + return seed; +} + +int main() +{ + std::cout << "=== boost::hash on 128-bit types ===" << std::endl; + + // boost::hash dispatches to the hash_value overloads from hash.hpp, which + // delegate to std::hash, so the two functors always agree. + const uint128_t big {UINT64_C(0xDEADBEEF), UINT64_C(0xCAFEBABE12345678)}; + const int128_t neg {-123456789012345678LL}; + + std::cout << "boost::hash matches std::hash (uint128_t): " + << std::boolalpha << (boost::hash{}(big) == std::hash{}(big)) << std::endl; + std::cout << "boost::hash matches std::hash (int128_t): " + << (boost::hash{}(neg) == std::hash{}(neg)) << std::endl; + + std::cout << "\n=== boost::unordered_map ===" << std::endl; + + // boost::unordered_map defaults to boost::hash, so uint128_t keys need + // no explicit hasher. + boost::unordered_map labels {}; + labels[uint128_t{1, 0}] = "two to the sixty-fourth"; // 2^64 + labels[uint128_t{UINT64_C(0x8000000000000000), 0}] = "two to the one hundred twenty-seventh"; // 2^127 + labels[uint128_t{42}] = "forty-two"; + + std::cout << "Entries: " << labels.size() << std::endl; + std::cout << "Label at 2^64: " << labels[uint128_t{1, 0}] << std::endl; + std::cout << "Contains 42: " << (labels.find(uint128_t{42}) != labels.end()) << std::endl; + + std::cout << "\n=== hash_combine for a composite key ===" << std::endl; + + // The point hasher combines two int128_t fields; boost::hash finds it + // via ADL, letting point be used as a key directly. + boost::unordered_map populations {}; + populations[point{10, 20}] = 5000000; + populations[point{-30, 40}] = 250000; + + std::cout << "Cities stored: " << populations.size() << std::endl; + std::cout << "Population at (10, 20): " << populations[point{10, 20}] << std::endl; + std::cout << "Same coordinate hashes equal: " + << (boost::hash{}(point{10, 20}) == boost::hash{}(point{10, 20})) << std::endl; + + std::cout << "\n=== boost::unordered_flat_map ===" << std::endl; + + // The modern flat container also defaults to boost::hash. + boost::unordered_flat_map counts {}; + counts[int128_t{-1}] = 1; + counts[int128_t{0}] = 2; + counts[int128_t{1}] = 3; + + std::cout << "Flat map size: " << counts.size() << std::endl; + std::cout << "counts[-1] = " << counts[int128_t{-1}] << std::endl; + + return 0; +} From d4e4391d2ca28593c2878938551c90abebe9a86d Mon Sep 17 00:00:00 2001 From: Matt Borland Date: Thu, 23 Jul 2026 10:32:04 -0400 Subject: [PATCH 4/6] Fix warnings --- test/Jamfile | 4 ++-- test/test_container_hash.cpp | 14 ++++++++++++++ 2 files changed, 16 insertions(+), 2 deletions(-) diff --git a/test/Jamfile b/test/Jamfile index 07fc43d1..f38218ca 100644 --- a/test/Jamfile +++ b/test/Jamfile @@ -103,8 +103,7 @@ run test_to_string.cpp ; # Warnings about padding propagate out of run test_hash.cpp : : : msvc:/wd4324 ; -# The Boost.ContainerHash and Boost.Unordered headers contain old-style casts -run test_container_hash.cpp : : : msvc:/wd4324 gcc:-Wno-old-style-cast clang:-Wno-old-style-cast ; +run test_container_hash.cpp : : : msvc:/wd4324 ; run test_boundaries.cpp ; run test_constexpr_boundaries.cpp ; @@ -119,6 +118,7 @@ run ../examples/stream.cpp ; run ../examples/basic_arithmetic.cpp ; run ../examples/math_and_random.cpp ; run ../examples/charconv.cpp ; +run ../examples/container_hash.cpp : : : msvc:/wd4324 ; run ../examples/fmt_format.cpp ; run ../examples/cstdlib.cpp ; run ../examples/numeric_algorithms.cpp ; diff --git a/test/test_container_hash.cpp b/test/test_container_hash.cpp index 21e6c3b1..5c0c6397 100644 --- a/test/test_container_hash.cpp +++ b/test/test_container_hash.cpp @@ -4,9 +4,23 @@ #include #include + +// The Boost.ContainerHash and Boost.Unordered headers use old-style casts that +// trip the -Wold-style-cast enabled by the test build; silence them for these +// third-party headers only. +#if defined(__GNUC__) || defined(__clang__) +# pragma GCC diagnostic push +# pragma GCC diagnostic ignored "-Wold-style-cast" +#endif + #include #include #include + +#if defined(__GNUC__) || defined(__clang__) +# pragma GCC diagnostic pop +#endif + #include #include From 5cf24b785b152cb4c5ab6003c864ab290aee9687 Mon Sep 17 00:00:00 2001 From: Matt Borland Date: Thu, 23 Jul 2026 10:32:16 -0400 Subject: [PATCH 5/6] Update docs --- doc/modules/ROOT/pages/examples.adoc | 33 ++++++++++++++++++++++++++++ doc/modules/ROOT/pages/hash.adoc | 23 +++++++++++++++++++ 2 files changed, 56 insertions(+) diff --git a/doc/modules/ROOT/pages/examples.adoc b/doc/modules/ROOT/pages/examples.adoc index 17abcdbd..a7c38f3a 100644 --- a/doc/modules/ROOT/pages/examples.adoc +++ b/doc/modules/ROOT/pages/examples.adoc @@ -394,6 +394,39 @@ Parsed hex "DEADBEEFCAFEBABE12345678" ---- ==== +[#examples_boost_container_hash] +== Boost.ContainerHash Integration + +.This https://github.com/cppalliance/int128/blob/develop/examples/container_hash.cpp[example] demonstrates using the library types with Boost.ContainerHash: the boost::hash functor, boost::hash_combine for composite keys, and the boost::unordered containers that default to boost::hash +==== +[source, c++] +---- +include::example$container_hash.cpp[tags=**;!exclude] +---- + +.Expected Output +[listing] +---- +=== boost::hash on 128-bit types === +boost::hash matches std::hash (uint128_t): true +boost::hash matches std::hash (int128_t): true + +=== boost::unordered_map === +Entries: 3 +Label at 2^64: two to the sixty-fourth +Contains 42: true + +=== hash_combine for a composite key === +Cities stored: 2 +Population at (10, 20): 5000000 +Same coordinate hashes equal: true + +=== boost::unordered_flat_map === +Flat map size: 3 +counts[-1] = 1 +---- +==== + [#examples_to_string] == String Conversion (to_string) diff --git a/doc/modules/ROOT/pages/hash.adoc b/doc/modules/ROOT/pages/hash.adoc index 2182f87c..9d06ed91 100644 --- a/doc/modules/ROOT/pages/hash.adoc +++ b/doc/modules/ROOT/pages/hash.adoc @@ -48,6 +48,29 @@ The two finalized halves are then combined with the `boost::hash_combine` mixing * For any non-zero `v`, `std::hash{}(v) != std::hash{}(-v)`. * The mixing function is asymmetric, so `{high, low}` and `{low, high}` do not collide except by chance. +[#hash_container_hash] +== Boost.ContainerHash + +The header also injects `hash_value` overloads into the `boost::int128` namespace, so the types integrate with https://www.boost.org/doc/libs/master/libs/container_hash/doc/html/hash.html[Boost.ContainerHash] through argument-dependent lookup. + +[source, c++] +---- +namespace boost { +namespace int128 { + +std::size_t hash_value(uint128_t v) noexcept; + +std::size_t hash_value(int128_t v) noexcept; + +} // namespace int128 +} // namespace boost +---- + +These functions delegate to the `std::hash` specializations above, so every entry point produces the same result for a given value. +With them, `boost::hash`, `boost::hash_combine`, and `boost::hash_range` accept the library types, and the types can be used as keys in `boost::unordered_map`, `boost::unordered_set`, and the flat and node container variants (which default to `boost::hash`) without an explicit hasher. + +See the xref:examples.adoc#examples_boost_container_hash[Boost.ContainerHash integration example] for a complete program. + [#hash_example] == Example From efa35fb519001dbbe517970be30db4d5cfa684a9 Mon Sep 17 00:00:00 2001 From: Matt Borland Date: Thu, 23 Jul 2026 11:27:19 -0400 Subject: [PATCH 6/6] Ignore double promotion warnings from container hash --- examples/container_hash.cpp | 8 +++++--- test/test_container_hash.cpp | 7 ++++--- 2 files changed, 9 insertions(+), 6 deletions(-) diff --git a/examples/container_hash.cpp b/examples/container_hash.cpp index d9561fc8..e299bef5 100644 --- a/examples/container_hash.cpp +++ b/examples/container_hash.cpp @@ -11,12 +11,14 @@ #include #include // tag::exclude[] -// The Boost.ContainerHash and Boost.Unordered headers use old-style casts that -// trip the strict -Wold-style-cast the test build enables; silence them for -// these third-party headers only. This is not needed in normal use. +// The Boost.ContainerHash and Boost.Unordered headers trip strict warnings the +// test build enables (old-style casts, and a float-to-double promotion in +// std::ceil on older clang); silence them for these third-party headers only. +// This is not needed in normal use. #if defined(__GNUC__) || defined(__clang__) # pragma GCC diagnostic push # pragma GCC diagnostic ignored "-Wold-style-cast" +# pragma GCC diagnostic ignored "-Wdouble-promotion" #endif // end::exclude[] #include diff --git a/test/test_container_hash.cpp b/test/test_container_hash.cpp index 5c0c6397..a648ff91 100644 --- a/test/test_container_hash.cpp +++ b/test/test_container_hash.cpp @@ -5,12 +5,13 @@ #include #include -// The Boost.ContainerHash and Boost.Unordered headers use old-style casts that -// trip the -Wold-style-cast enabled by the test build; silence them for these -// third-party headers only. +// The Boost.ContainerHash and Boost.Unordered headers trip strict warnings +// enabled by the test build (old-style casts, and a float-to-double promotion +// in std::ceil on older clang); silence them for these third-party headers only. #if defined(__GNUC__) || defined(__clang__) # pragma GCC diagnostic push # pragma GCC diagnostic ignored "-Wold-style-cast" +# pragma GCC diagnostic ignored "-Wdouble-promotion" #endif #include