Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
33 changes: 33 additions & 0 deletions doc/modules/ROOT/pages/examples.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -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<uint128_t, ...> ===
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<int128_t, ...> ===
Flat map size: 3
counts[-1] = 1
----
====

[#examples_to_string]
== String Conversion (to_string)

Expand Down
23 changes: 23 additions & 0 deletions doc/modules/ROOT/pages/hash.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,29 @@ The two finalized halves are then combined with the `boost::hash_combine` mixing
* For any non-zero `v`, `std::hash<int128_t>{}(v) != std::hash<int128_t>{}(-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

Expand Down
114 changes: 114 additions & 0 deletions examples/container_hash.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
// 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 <boost/int128/hash.hpp> 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 <boost/int128/int128.hpp>
#include <boost/int128/hash.hpp>
// tag::exclude[]
// 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 <boost/container_hash/hash.hpp>
#include <boost/unordered/unordered_map.hpp>
#include <boost/unordered/unordered_flat_map.hpp>
// tag::exclude[]
#if defined(__GNUC__) || defined(__clang__)
# pragma GCC diagnostic pop
#endif
// end::exclude[]
#include <cstddef>
#include <functional>
#include <iostream>
#include <string>

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<T> 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<uint128_t>{}(big) == std::hash<uint128_t>{}(big)) << std::endl;
std::cout << "boost::hash matches std::hash (int128_t): "
<< (boost::hash<int128_t>{}(neg) == std::hash<int128_t>{}(neg)) << std::endl;

std::cout << "\n=== boost::unordered_map<uint128_t, ...> ===" << std::endl;

// boost::unordered_map defaults to boost::hash<Key>, so uint128_t keys need
// no explicit hasher.
boost::unordered_map<uint128_t, std::string> 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<point> finds it
// via ADL, letting point be used as a key directly.
boost::unordered_map<point, long> 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>{}(point{10, 20}) == boost::hash<point>{}(point{10, 20})) << std::endl;

std::cout << "\n=== boost::unordered_flat_map<int128_t, ...> ===" << std::endl;

// The modern flat container also defaults to boost::hash.
boost::unordered_flat_map<int128_t, int> 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;
}
16 changes: 16 additions & 0 deletions include/boost/int128/hash.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -76,4 +76,20 @@ struct hash<boost::int128::uint128_t>

} // namespace std

namespace boost {
namespace int128 {

inline std::size_t hash_value(const uint128_t v) noexcept
{
return std::hash<uint128_t>{}(v);
}

inline std::size_t hash_value(const int128_t v) noexcept
{
return std::hash<int128_t>{}(v);
}

} // namespace int128
} // namespace boost

#endif // BOOST_INT128_HASH_HPP
2 changes: 1 addition & 1 deletion test/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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()

Expand Down
2 changes: 2 additions & 0 deletions test/Jamfile
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,7 @@ run test_to_string.cpp ;

# Warnings about padding propagate out of <utility>
run test_hash.cpp : : : <toolset>msvc:<cxxflags>/wd4324 ;
run test_container_hash.cpp : : : <toolset>msvc:<cxxflags>/wd4324 ;

run test_boundaries.cpp ;
run test_constexpr_boundaries.cpp ;
Expand All @@ -117,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 : : : <toolset>msvc:<cxxflags>/wd4324 ;
run ../examples/fmt_format.cpp ;
run ../examples/cstdlib.cpp ;
run ../examples/numeric_algorithms.cpp ;
Expand Down
Loading
Loading