From f70fd39a960385ef0a963aef00b750e4740228c0 Mon Sep 17 00:00:00 2001 From: Wenzel Jakob Date: Tue, 9 Jun 2026 22:11:11 +0200 Subject: [PATCH 1/2] Add hash-aware insertion functions with a precalculated hash The `precalculated_hash` overloads currently exist only on the const lookup functions and `erase`; none of the insertion functions can accept a caller-provided hash. This commit adds `try_emplace_hash()`, `insert_or_assign_hash()` and `insert_hash()` to `robin_map`, and `insert_hash()` to `robin_set`. --- include/tsl/robin_hash.h | 51 ++++++++++++++++++++++++++++++- include/tsl/robin_map.h | 63 +++++++++++++++++++++++++++++++++++++++ include/tsl/robin_set.h | 18 +++++++++++ tests/robin_map_tests.cpp | 39 ++++++++++++++++++++++++ tests/robin_set_tests.cpp | 49 ++++++++++++++++++++++++++++++ 5 files changed, 219 insertions(+), 1 deletion(-) diff --git a/include/tsl/robin_hash.h b/include/tsl/robin_hash.h index 163e0e2..5b2fa66 100644 --- a/include/tsl/robin_hash.h +++ b/include/tsl/robin_hash.h @@ -686,6 +686,18 @@ class robin_hash : private Hash, private KeyEqual, private GrowthPolicy { return insert_impl(KeySelect()(value), std::forward

(value)); } + /** + * Use the hash value 'precalculated_hash' instead of hashing the key. The + * hash value should be the same as hash_function()(KeySelect()(value)), + * otherwise the behaviour is undefined. + */ + template + std::pair insert_hash(std::size_t precalculated_hash, + P&& value) { + return insert_impl_hash(precalculated_hash, KeySelect()(value), + std::forward

(value)); + } + template iterator insert_hint(const_iterator hint, P&& value) { if (hint != cend() && @@ -726,6 +738,23 @@ class robin_hash : private Hash, private KeyEqual, private GrowthPolicy { return it; } + /** + * Use the hash value 'precalculated_hash' instead of hashing the key. The + * hash value should be the same as hash_function()(key), otherwise the + * behaviour is undefined. + */ + template + std::pair insert_or_assign_hash( + std::size_t precalculated_hash, K&& key, M&& obj) { + auto it = try_emplace_hash(precalculated_hash, std::forward(key), + std::forward(obj)); + if (!it.second) { + it.first.value() = std::forward(obj); + } + + return it; + } + template iterator insert_or_assign(const_iterator hint, K&& key, M&& obj) { if (hint != cend() && compare_keys(KeySelect()(*hint), key)) { @@ -755,6 +784,19 @@ class robin_hash : private Hash, private KeyEqual, private GrowthPolicy { std::forward_as_tuple(std::forward(args)...)); } + /** + * Use the hash value 'precalculated_hash' instead of hashing the key. The + * hash value should be the same as hash_function()(key), otherwise the + * behaviour is undefined. + */ + template + std::pair try_emplace_hash(std::size_t precalculated_hash, + K&& key, Args&&... args) { + return insert_impl_hash(precalculated_hash, key, std::piecewise_construct, + std::forward_as_tuple(std::forward(key)), + std::forward_as_tuple(std::forward(args)...)); + } + template iterator try_emplace_hint(const_iterator hint, K&& key, Args&&... args) { if (hint != cend() && compare_keys(KeySelect()(*hint), key)) { @@ -1161,7 +1203,14 @@ class robin_hash : private Hash, private KeyEqual, private GrowthPolicy { template std::pair insert_impl(const K& key, Args&&... value_type_args) { - const std::size_t hash = hash_key(key); + return insert_impl_hash(hash_key(key), key, + std::forward(value_type_args)...); + } + + template + std::pair insert_impl_hash(std::size_t hash, const K& key, + Args&&... value_type_args) { + tsl_rh_assert(hash == hash_key(key)); std::size_t ibucket = bucket_for_hash(hash); distance_type dist_from_ideal_bucket = 0; diff --git a/include/tsl/robin_map.h b/include/tsl/robin_map.h index b594810..b6edd11 100644 --- a/include/tsl/robin_map.h +++ b/include/tsl/robin_map.h @@ -244,6 +244,24 @@ class robin_map { return m_ht.insert(std::move(value)); } + /** + * Use the hash value 'precalculated_hash' instead of hashing the key. The + * hash value should be the same as hash_function()(value.first). Useful to + * speed-up the insertion if you already have the hash. + */ + std::pair insert_hash(std::size_t precalculated_hash, + const value_type& value) { + return m_ht.insert_hash(precalculated_hash, value); + } + + /** + * @copydoc insert_hash(std::size_t precalculated_hash, const value_type& value) + */ + std::pair insert_hash(std::size_t precalculated_hash, + value_type&& value) { + return m_ht.insert_hash(precalculated_hash, std::move(value)); + } + iterator insert(const_iterator hint, const value_type& value) { return m_ht.insert_hint(hint, value); } @@ -287,6 +305,28 @@ class robin_map { return m_ht.insert_or_assign(hint, std::move(k), std::forward(obj)); } + /** + * Use the hash value 'precalculated_hash' instead of hashing the key. The + * hash value should be the same as hash_function()(k). Useful to speed-up + * the insertion if you already have the hash. + */ + template + std::pair insert_or_assign_hash( + std::size_t precalculated_hash, const key_type& k, M&& obj) { + return m_ht.insert_or_assign_hash(precalculated_hash, k, + std::forward(obj)); + } + + /** + * @copydoc insert_or_assign_hash(std::size_t precalculated_hash, const key_type& k, M&& obj) + */ + template + std::pair insert_or_assign_hash( + std::size_t precalculated_hash, key_type&& k, M&& obj) { + return m_ht.insert_or_assign_hash(precalculated_hash, std::move(k), + std::forward(obj)); + } + /** * Due to the way elements are stored, emplace will need to move or copy the * key-value once. The method is equivalent to @@ -332,6 +372,29 @@ class robin_map { std::forward(args)...); } + /** + * Use the hash value 'precalculated_hash' instead of hashing the key. The + * hash value should be the same as hash_function()(k). Useful to speed-up + * the insertion if you already have the hash. + */ + template + std::pair try_emplace_hash(std::size_t precalculated_hash, + const key_type& k, + Args&&... args) { + return m_ht.try_emplace_hash(precalculated_hash, k, + std::forward(args)...); + } + + /** + * @copydoc try_emplace_hash(std::size_t precalculated_hash, const key_type& k, Args&&... args) + */ + template + std::pair try_emplace_hash(std::size_t precalculated_hash, + key_type&& k, Args&&... args) { + return m_ht.try_emplace_hash(precalculated_hash, std::move(k), + std::forward(args)...); + } + iterator erase(iterator pos) { return m_ht.erase(pos); } iterator erase(const_iterator pos) { return m_ht.erase(pos); } iterator erase(const_iterator first, const_iterator last) { diff --git a/include/tsl/robin_set.h b/include/tsl/robin_set.h index e115007..9e19d22 100644 --- a/include/tsl/robin_set.h +++ b/include/tsl/robin_set.h @@ -215,6 +215,24 @@ class robin_set { return m_ht.insert(std::move(value)); } + /** + * Use the hash value 'precalculated_hash' instead of hashing the key. The + * hash value should be the same as hash_function()(value). Useful to speed-up + * the insertion if you already have the hash. + */ + std::pair insert_hash(std::size_t precalculated_hash, + const value_type& value) { + return m_ht.insert_hash(precalculated_hash, value); + } + + /** + * @copydoc insert_hash(std::size_t precalculated_hash, const value_type& value) + */ + std::pair insert_hash(std::size_t precalculated_hash, + value_type&& value) { + return m_ht.insert_hash(precalculated_hash, std::move(value)); + } + iterator insert(const_iterator hint, const value_type& value) { return m_ht.insert_hint(hint, value); } diff --git a/tests/robin_map_tests.cpp b/tests/robin_map_tests.cpp index 68398e2..25fc7e7 100644 --- a/tests/robin_map_tests.cpp +++ b/tests/robin_map_tests.cpp @@ -1439,6 +1439,45 @@ BOOST_AUTO_TEST_CASE(test_precalculated_hash) { BOOST_REQUIRE_NE(map.hash_function()(2), map.hash_function()(3)); BOOST_CHECK_EQUAL(std::distance(it_range.first, it_range.second), 0); + /** + * try_emplace_hash + */ + auto it_te = map.try_emplace_hash(map.hash_function()(7), 7, -7); + BOOST_CHECK(it_te.second); + BOOST_CHECK_EQUAL(it_te.first->first, 7); + BOOST_CHECK_EQUAL(it_te.first->second, -7); + + // Key already present: no insert, value left untouched. + it_te = map.try_emplace_hash(map.hash_function()(7), 7, -70); + BOOST_CHECK(!it_te.second); + BOOST_CHECK_EQUAL(it_te.first->second, -7); + + /** + * insert_or_assign_hash + */ + auto it_ioa = map.insert_or_assign_hash(map.hash_function()(8), 8, -8); + BOOST_CHECK(it_ioa.second); + BOOST_CHECK_EQUAL(it_ioa.first->first, 8); + BOOST_CHECK_EQUAL(it_ioa.first->second, -8); + + // Key already present: no insert, but value is overwritten. + it_ioa = map.insert_or_assign_hash(map.hash_function()(8), 8, -80); + BOOST_CHECK(!it_ioa.second); + BOOST_CHECK_EQUAL(it_ioa.first->second, -80); + + /** + * insert_hash + */ + auto it_ins = map.insert_hash(map.hash_function()(9), {9, -9}); + BOOST_CHECK(it_ins.second); + BOOST_CHECK_EQUAL(it_ins.first->first, 9); + BOOST_CHECK_EQUAL(it_ins.first->second, -9); + + // Key already present: no insert, value left untouched. + it_ins = map.insert_hash(map.hash_function()(9), {9, -90}); + BOOST_CHECK(!it_ins.second); + BOOST_CHECK_EQUAL(it_ins.first->second, -9); + /** * erase */ diff --git a/tests/robin_set_tests.cpp b/tests/robin_set_tests.cpp index e68b514..e59b1e8 100644 --- a/tests/robin_set_tests.cpp +++ b/tests/robin_set_tests.cpp @@ -171,4 +171,53 @@ BOOST_AUTO_TEST_CASE(test_erase_fast) { BOOST_CHECK(set.size() == 0); } +BOOST_AUTO_TEST_CASE(test_precalculated_hash) { + tsl::robin_set> set = {1, 2, 3, 4, 5, 6}; + const tsl::robin_set> set_const = set; + + /** + * find + */ + BOOST_REQUIRE(set.find(3, set.hash_function()(3)) != set.end()); + BOOST_CHECK_EQUAL(*set.find(3, set.hash_function()(3)), 3); + + BOOST_REQUIRE(set_const.find(3, set_const.hash_function()(3)) != + set_const.end()); + + BOOST_REQUIRE_NE(set.hash_function()(2), set.hash_function()(3)); + BOOST_CHECK(set.find(3, set.hash_function()(2)) == set.end()); + + /** + * contains + */ + BOOST_CHECK(set.contains(3, set.hash_function()(3))); + BOOST_CHECK(!set.contains(3, set.hash_function()(2))); + + /** + * count + */ + BOOST_CHECK_EQUAL(set.count(3, set.hash_function()(3)), 1); + BOOST_CHECK_EQUAL(set.count(3, set.hash_function()(2)), 0); + + /** + * insert_hash + */ + auto it_ins = set.insert_hash(set.hash_function()(7), 7); + BOOST_CHECK(it_ins.second); + BOOST_CHECK_EQUAL(*it_ins.first, 7); + + // Value already present: no insert. + it_ins = set.insert_hash(set.hash_function()(7), 7); + BOOST_CHECK(!it_ins.second); + BOOST_CHECK_EQUAL(*it_ins.first, 7); + + /** + * erase + */ + BOOST_CHECK_EQUAL(set.erase(3, set.hash_function()(3)), 1); + + BOOST_REQUIRE_NE(set.hash_function()(2), set.hash_function()(4)); + BOOST_CHECK_EQUAL(set.erase(4, set.hash_function()(2)), 0); +} + BOOST_AUTO_TEST_SUITE_END() From e344d5f5c1af1f4f993e3a1f3461466de4564940 Mon Sep 17 00:00:00 2001 From: Wenzel Jakob Date: Sat, 13 Jun 2026 11:16:10 +0200 Subject: [PATCH 2/2] Rename functions to ``*_with_hash`` --- include/tsl/robin_hash.h | 33 ++++++++++++++------------- include/tsl/robin_map.h | 47 +++++++++++++++++++-------------------- include/tsl/robin_set.h | 14 ++++++------ tests/robin_map_tests.cpp | 18 +++++++-------- tests/robin_set_tests.cpp | 6 ++--- 5 files changed, 59 insertions(+), 59 deletions(-) diff --git a/include/tsl/robin_hash.h b/include/tsl/robin_hash.h index 5b2fa66..51662be 100644 --- a/include/tsl/robin_hash.h +++ b/include/tsl/robin_hash.h @@ -692,10 +692,10 @@ class robin_hash : private Hash, private KeyEqual, private GrowthPolicy { * otherwise the behaviour is undefined. */ template - std::pair insert_hash(std::size_t precalculated_hash, - P&& value) { - return insert_impl_hash(precalculated_hash, KeySelect()(value), - std::forward

(value)); + std::pair insert_with_hash(std::size_t precalculated_hash, + P&& value) { + return insert_impl_with_hash(precalculated_hash, KeySelect()(value), + std::forward

(value)); } template @@ -744,10 +744,10 @@ class robin_hash : private Hash, private KeyEqual, private GrowthPolicy { * behaviour is undefined. */ template - std::pair insert_or_assign_hash( + std::pair insert_or_assign_with_hash( std::size_t precalculated_hash, K&& key, M&& obj) { - auto it = try_emplace_hash(precalculated_hash, std::forward(key), - std::forward(obj)); + auto it = try_emplace_with_hash(precalculated_hash, std::forward(key), + std::forward(obj)); if (!it.second) { it.first.value() = std::forward(obj); } @@ -790,11 +790,12 @@ class robin_hash : private Hash, private KeyEqual, private GrowthPolicy { * behaviour is undefined. */ template - std::pair try_emplace_hash(std::size_t precalculated_hash, - K&& key, Args&&... args) { - return insert_impl_hash(precalculated_hash, key, std::piecewise_construct, - std::forward_as_tuple(std::forward(key)), - std::forward_as_tuple(std::forward(args)...)); + std::pair try_emplace_with_hash( + std::size_t precalculated_hash, K&& key, Args&&... args) { + return insert_impl_with_hash( + precalculated_hash, key, std::piecewise_construct, + std::forward_as_tuple(std::forward(key)), + std::forward_as_tuple(std::forward(args)...)); } template @@ -1203,13 +1204,13 @@ class robin_hash : private Hash, private KeyEqual, private GrowthPolicy { template std::pair insert_impl(const K& key, Args&&... value_type_args) { - return insert_impl_hash(hash_key(key), key, - std::forward(value_type_args)...); + return insert_impl_with_hash(hash_key(key), key, + std::forward(value_type_args)...); } template - std::pair insert_impl_hash(std::size_t hash, const K& key, - Args&&... value_type_args) { + std::pair insert_impl_with_hash( + std::size_t hash, const K& key, Args&&... value_type_args) { tsl_rh_assert(hash == hash_key(key)); std::size_t ibucket = bucket_for_hash(hash); diff --git a/include/tsl/robin_map.h b/include/tsl/robin_map.h index b6edd11..daec9e6 100644 --- a/include/tsl/robin_map.h +++ b/include/tsl/robin_map.h @@ -249,17 +249,17 @@ class robin_map { * hash value should be the same as hash_function()(value.first). Useful to * speed-up the insertion if you already have the hash. */ - std::pair insert_hash(std::size_t precalculated_hash, - const value_type& value) { - return m_ht.insert_hash(precalculated_hash, value); + std::pair insert_with_hash(std::size_t precalculated_hash, + const value_type& value) { + return m_ht.insert_with_hash(precalculated_hash, value); } /** - * @copydoc insert_hash(std::size_t precalculated_hash, const value_type& value) + * @copydoc insert_with_hash(std::size_t precalculated_hash, const value_type& value) */ - std::pair insert_hash(std::size_t precalculated_hash, - value_type&& value) { - return m_ht.insert_hash(precalculated_hash, std::move(value)); + std::pair insert_with_hash(std::size_t precalculated_hash, + value_type&& value) { + return m_ht.insert_with_hash(precalculated_hash, std::move(value)); } iterator insert(const_iterator hint, const value_type& value) { @@ -311,20 +311,20 @@ class robin_map { * the insertion if you already have the hash. */ template - std::pair insert_or_assign_hash( + std::pair insert_or_assign_with_hash( std::size_t precalculated_hash, const key_type& k, M&& obj) { - return m_ht.insert_or_assign_hash(precalculated_hash, k, - std::forward(obj)); + return m_ht.insert_or_assign_with_hash(precalculated_hash, k, + std::forward(obj)); } /** - * @copydoc insert_or_assign_hash(std::size_t precalculated_hash, const key_type& k, M&& obj) + * @copydoc insert_or_assign_with_hash(std::size_t precalculated_hash, const key_type& k, M&& obj) */ template - std::pair insert_or_assign_hash( + std::pair insert_or_assign_with_hash( std::size_t precalculated_hash, key_type&& k, M&& obj) { - return m_ht.insert_or_assign_hash(precalculated_hash, std::move(k), - std::forward(obj)); + return m_ht.insert_or_assign_with_hash(precalculated_hash, std::move(k), + std::forward(obj)); } /** @@ -378,21 +378,20 @@ class robin_map { * the insertion if you already have the hash. */ template - std::pair try_emplace_hash(std::size_t precalculated_hash, - const key_type& k, - Args&&... args) { - return m_ht.try_emplace_hash(precalculated_hash, k, - std::forward(args)...); + std::pair try_emplace_with_hash( + std::size_t precalculated_hash, const key_type& k, Args&&... args) { + return m_ht.try_emplace_with_hash(precalculated_hash, k, + std::forward(args)...); } /** - * @copydoc try_emplace_hash(std::size_t precalculated_hash, const key_type& k, Args&&... args) + * @copydoc try_emplace_with_hash(std::size_t precalculated_hash, const key_type& k, Args&&... args) */ template - std::pair try_emplace_hash(std::size_t precalculated_hash, - key_type&& k, Args&&... args) { - return m_ht.try_emplace_hash(precalculated_hash, std::move(k), - std::forward(args)...); + std::pair try_emplace_with_hash( + std::size_t precalculated_hash, key_type&& k, Args&&... args) { + return m_ht.try_emplace_with_hash(precalculated_hash, std::move(k), + std::forward(args)...); } iterator erase(iterator pos) { return m_ht.erase(pos); } diff --git a/include/tsl/robin_set.h b/include/tsl/robin_set.h index 9e19d22..c48bb5c 100644 --- a/include/tsl/robin_set.h +++ b/include/tsl/robin_set.h @@ -220,17 +220,17 @@ class robin_set { * hash value should be the same as hash_function()(value). Useful to speed-up * the insertion if you already have the hash. */ - std::pair insert_hash(std::size_t precalculated_hash, - const value_type& value) { - return m_ht.insert_hash(precalculated_hash, value); + std::pair insert_with_hash(std::size_t precalculated_hash, + const value_type& value) { + return m_ht.insert_with_hash(precalculated_hash, value); } /** - * @copydoc insert_hash(std::size_t precalculated_hash, const value_type& value) + * @copydoc insert_with_hash(std::size_t precalculated_hash, const value_type& value) */ - std::pair insert_hash(std::size_t precalculated_hash, - value_type&& value) { - return m_ht.insert_hash(precalculated_hash, std::move(value)); + std::pair insert_with_hash(std::size_t precalculated_hash, + value_type&& value) { + return m_ht.insert_with_hash(precalculated_hash, std::move(value)); } iterator insert(const_iterator hint, const value_type& value) { diff --git a/tests/robin_map_tests.cpp b/tests/robin_map_tests.cpp index 25fc7e7..3facbd7 100644 --- a/tests/robin_map_tests.cpp +++ b/tests/robin_map_tests.cpp @@ -1440,41 +1440,41 @@ BOOST_AUTO_TEST_CASE(test_precalculated_hash) { BOOST_CHECK_EQUAL(std::distance(it_range.first, it_range.second), 0); /** - * try_emplace_hash + * try_emplace_with_hash */ - auto it_te = map.try_emplace_hash(map.hash_function()(7), 7, -7); + auto it_te = map.try_emplace_with_hash(map.hash_function()(7), 7, -7); BOOST_CHECK(it_te.second); BOOST_CHECK_EQUAL(it_te.first->first, 7); BOOST_CHECK_EQUAL(it_te.first->second, -7); // Key already present: no insert, value left untouched. - it_te = map.try_emplace_hash(map.hash_function()(7), 7, -70); + it_te = map.try_emplace_with_hash(map.hash_function()(7), 7, -70); BOOST_CHECK(!it_te.second); BOOST_CHECK_EQUAL(it_te.first->second, -7); /** - * insert_or_assign_hash + * insert_or_assign_with_hash */ - auto it_ioa = map.insert_or_assign_hash(map.hash_function()(8), 8, -8); + auto it_ioa = map.insert_or_assign_with_hash(map.hash_function()(8), 8, -8); BOOST_CHECK(it_ioa.second); BOOST_CHECK_EQUAL(it_ioa.first->first, 8); BOOST_CHECK_EQUAL(it_ioa.first->second, -8); // Key already present: no insert, but value is overwritten. - it_ioa = map.insert_or_assign_hash(map.hash_function()(8), 8, -80); + it_ioa = map.insert_or_assign_with_hash(map.hash_function()(8), 8, -80); BOOST_CHECK(!it_ioa.second); BOOST_CHECK_EQUAL(it_ioa.first->second, -80); /** - * insert_hash + * insert_with_hash */ - auto it_ins = map.insert_hash(map.hash_function()(9), {9, -9}); + auto it_ins = map.insert_with_hash(map.hash_function()(9), {9, -9}); BOOST_CHECK(it_ins.second); BOOST_CHECK_EQUAL(it_ins.first->first, 9); BOOST_CHECK_EQUAL(it_ins.first->second, -9); // Key already present: no insert, value left untouched. - it_ins = map.insert_hash(map.hash_function()(9), {9, -90}); + it_ins = map.insert_with_hash(map.hash_function()(9), {9, -90}); BOOST_CHECK(!it_ins.second); BOOST_CHECK_EQUAL(it_ins.first->second, -9); diff --git a/tests/robin_set_tests.cpp b/tests/robin_set_tests.cpp index e59b1e8..79b5693 100644 --- a/tests/robin_set_tests.cpp +++ b/tests/robin_set_tests.cpp @@ -200,14 +200,14 @@ BOOST_AUTO_TEST_CASE(test_precalculated_hash) { BOOST_CHECK_EQUAL(set.count(3, set.hash_function()(2)), 0); /** - * insert_hash + * insert_with_hash */ - auto it_ins = set.insert_hash(set.hash_function()(7), 7); + auto it_ins = set.insert_with_hash(set.hash_function()(7), 7); BOOST_CHECK(it_ins.second); BOOST_CHECK_EQUAL(*it_ins.first, 7); // Value already present: no insert. - it_ins = set.insert_hash(set.hash_function()(7), 7); + it_ins = set.insert_with_hash(set.hash_function()(7), 7); BOOST_CHECK(!it_ins.second); BOOST_CHECK_EQUAL(*it_ins.first, 7);