From aab900f00278ffaeb664be268dad4e7e4d2ad3ca Mon Sep 17 00:00:00 2001 From: Jesse Li Date: Thu, 1 Sep 2022 11:41:09 -0700 Subject: [PATCH] Permit non-default-constructable hashes and key-equals (#583) Co-authored-by: Jesse --- include/etl/unordered_map.h | 47 +++++++------- test/test_unordered_map.cpp | 122 +++++++++++++++++++++++++++++++++++- 2 files changed, 141 insertions(+), 28 deletions(-) diff --git a/include/etl/unordered_map.h b/include/etl/unordered_map.h index 130d30ed..4eac7c92 100644 --- a/include/etl/unordered_map.h +++ b/include/etl/unordered_map.h @@ -792,7 +792,7 @@ namespace etl while (inode != bucket.end()) { // Do we already have this key? - if (inode->key_value_pair.first == key) + if (key_equal_function(inode->key_value_pair.first, key)) { break; } @@ -851,8 +851,8 @@ namespace etl ::new (&node.key_value_pair) value_type(etl::move(key_value_pair)); ETL_INCREMENT_DEBUG_COUNT - // Just add the pointer to the bucket; - bucket.insert_after(bucket.before_begin(), node); + // Just add the pointer to the bucket; + bucket.insert_after(bucket.before_begin(), node); adjust_first_last_markers_after_insert(pbucket); @@ -868,7 +868,7 @@ namespace etl while (inode != bucket.end()) { // Do we already have this key? - if (inode->key_value_pair.first == key) + if (key_equal_function(inode->key_value_pair.first, key)) { break; } @@ -885,8 +885,8 @@ namespace etl ::new (&node.key_value_pair) value_type(etl::move(key_value_pair)); ETL_INCREMENT_DEBUG_COUNT - // Add the node to the end of the bucket; - bucket.insert_after(inode_previous, node); + // Add the node to the end of the bucket; + bucket.insert_after(inode_previous, node); adjust_first_last_markers_after_insert(&bucket); ++inode_previous; @@ -1278,6 +1278,8 @@ namespace etl // Skip if doing self assignment if (this != &rhs) { + key_hash_function = rhs.hash_function(); + key_equal_function = rhs.key_eq(); assign(rhs.cbegin(), rhs.cend()); } @@ -1292,6 +1294,8 @@ namespace etl // Skip if doing self assignment if (this != &rhs) { + key_hash_function = rhs.hash_function(); + key_equal_function = rhs.key_eq(); this->move(rhs.begin(), rhs.end()); } @@ -1304,12 +1308,14 @@ namespace etl //********************************************************************* /// Constructor. //********************************************************************* - iunordered_map(pool_t& node_pool_, bucket_t* pbuckets_, size_t number_of_buckets_) + iunordered_map(pool_t& node_pool_, bucket_t* pbuckets_, size_t number_of_buckets_, hasher key_hash_function_, key_equal key_equal_function_) : pnodepool(&node_pool_) , pbuckets(pbuckets_) , number_of_buckets(number_of_buckets_) , first(pbuckets) , last(pbuckets) + , key_hash_function(key_hash_function_) + , key_equal_function(key_equal_function_) { } @@ -1528,8 +1534,8 @@ namespace etl //************************************************************************* /// Default constructor. //************************************************************************* - unordered_map() - : base(node_pool, buckets, MAX_BUCKETS_) + unordered_map(const THash& hash = THash(), const TKeyEqual equal = TKeyEqual()) + : base(node_pool, buckets, MAX_BUCKETS_, hash, equal) { } @@ -1537,7 +1543,7 @@ namespace etl /// Copy constructor. //************************************************************************* unordered_map(const unordered_map& other) - : base(node_pool, buckets, MAX_BUCKETS_) + : base(node_pool, buckets, MAX_BUCKETS_, other.hash_function(), other.key_eq()) { base::assign(other.cbegin(), other.cend()); } @@ -1547,7 +1553,7 @@ namespace etl /// Move constructor. //************************************************************************* unordered_map(unordered_map&& other) - : base(node_pool, buckets, MAX_BUCKETS_) + : base(node_pool, buckets, MAX_BUCKETS_, other.hash_function(), other.key_eq()) { if (this != &other) { @@ -1564,7 +1570,7 @@ namespace etl //************************************************************************* template unordered_map(TIterator first_, TIterator last_) - : base(node_pool, buckets, MAX_BUCKETS_) + : unordered_map() { base::assign(first_, last_); } @@ -1574,7 +1580,7 @@ namespace etl /// Construct from initializer_list. //************************************************************************* unordered_map(std::initializer_list> init) - : base(node_pool, buckets, MAX_BUCKETS_) + : unordered_map() { base::assign(init.begin(), init.end()); } @@ -1593,12 +1599,7 @@ namespace etl //************************************************************************* unordered_map& operator = (const unordered_map& rhs) { - // Skip if doing self assignment - if (this != &rhs) - { - base::assign(rhs.cbegin(), rhs.cend()); - } - + base::operator=(rhs); return *this; } @@ -1608,13 +1609,7 @@ namespace etl //************************************************************************* unordered_map& operator = (unordered_map&& rhs) { - // Skip if doing self assignment - if (this != &rhs) - { - base::clear(); - base::move(rhs.begin(), rhs.end()); - } - + base::operator=(etl::move(rhs)); return *this; } #endif diff --git a/test/test_unordered_map.cpp b/test/test_unordered_map.cpp index 70c49338..4f49203f 100644 --- a/test/test_unordered_map.cpp +++ b/test/test_unordered_map.cpp @@ -56,6 +56,61 @@ namespace } }; + //************************************************************************* + // Non-default-constructible hasher + struct ndc_hash + { + int id; + ndc_hash(int id_) : id(id_){} + + size_t operator()(size_t val) const + { + return val; + } + }; + + //************************************************************************* + // Non-default-constructible equality checker + struct ndc_key_eq + { + int id; + ndc_key_eq(int id_) : id(id_){} + + bool operator()(size_t val1, size_t val2) const + { + return val1 == val2; + } + }; + + //************************************************************************* + // Hasher whose hash behaviour depends on provided data. + struct parameterized_hash + { + size_t modulus; + + parameterized_hash(size_t modulus_ = 2) : modulus(modulus_){} + + size_t operator()(size_t val) const + { + return val % modulus; + } + }; + + //************************************************************************* + // Equality checker whose behaviour depends on provided data. + struct parameterized_equal + { + size_t modulus; + + // Hasher whose hash behaviour depends on provided data. + parameterized_equal(size_t modulus_ = 2) : modulus(modulus_){} + + bool operator()(size_t lhs, size_t rhs) const + { + return (lhs % modulus) == (rhs % modulus); + } + }; + //************************************************************************* template bool Check_Equal(T1 begin1, T1 end1, T2 begin2) @@ -350,9 +405,9 @@ namespace DataNDC data(initial_data.begin(), initial_data.end()); DataNDC other_data(data); -#include "etl/private/diagnostic_self_assign_overloaded_push.h" +#include "etl/private/diagnostic_self_assign_overloaded_push.h" other_data = other_data; -#include "etl/private/diagnostic_pop.h" +#include "etl/private/diagnostic_pop.h" bool isEqual = std::equal(data.begin(), data.end(), @@ -894,5 +949,68 @@ namespace CHECK_EQUAL('c', map[2]); CHECK_EQUAL('d', map[3]); } + + TEST(test_ndc_hasher_and_key_eq) { + typedef etl::unordered_map Map; + ndc_hash hasher1(1); + ndc_hash hasher2(2); + ndc_key_eq eq1(1); + ndc_key_eq eq2(2); + + Map map1(hasher1, eq1); + CHECK_EQUAL(map1.hash_function().id, 1); + CHECK_EQUAL(map1.key_eq().id, 1); + + Map map2(hasher2, eq2); + + Map copyConstructed(map1); + CHECK_EQUAL(copyConstructed.hash_function().id, 1); + CHECK_EQUAL(copyConstructed.key_eq().id, 1); + + Map copyAssigned(hasher2, eq2); + CHECK_EQUAL(copyAssigned.hash_function().id, 2); + CHECK_EQUAL(copyAssigned.key_eq().id, 2); + copyAssigned = map1; + CHECK_EQUAL(copyAssigned.hash_function().id, 1); + CHECK_EQUAL(copyAssigned.key_eq().id, 1); + + Map moveConstructed = std::move(map1); + CHECK_EQUAL(moveConstructed.hash_function().id, 1); + CHECK_EQUAL(moveConstructed.key_eq().id, 1); + + Map moveAssigned(hasher1, eq1); + CHECK_EQUAL(moveAssigned.hash_function().id, 1); + CHECK_EQUAL(moveAssigned.key_eq().id, 1); + moveAssigned = std::move(map2); + CHECK_EQUAL(moveAssigned.hash_function().id, 2); + CHECK_EQUAL(moveAssigned.key_eq().id, 2); + + // make sure that map operations still work + moveAssigned[5] = 7; + CHECK_EQUAL(7, moveAssigned[5]); + } + + TEST(test_parameterized_eq) { + constexpr std::size_t MODULO = 4; + parameterized_hash hash{MODULO}; + parameterized_equal eq{MODULO}; + // values are equal modulo 4 + etl::unordered_map map; + map.insert(etl::make_pair(2, 3)); + + const auto& constmap = map; + + CHECK_EQUAL(map[10], 3); + CHECK_EQUAL(map.at(10), 3); + CHECK_EQUAL(constmap.at(10), 3); + + CHECK_FALSE(map.insert(etl::make_pair(6, 7)).second); + + CHECK(map.find(14) != map.end()); + CHECK(constmap.find(14) != constmap.end()); + + map.erase(2); + CHECK(map.find(6) == map.end()); + } }; }