Ver Fonte

dense_map:
* uses-allocator construction
* iterators model forward/random access iter types but are in the input iter category
* iter value_type is pair<const T &, U &>/pair<const T &, const U &> (zip iter types)
* no risky UBs due to type punning or destroy/construct within a vector

Michele Caini há 4 anos atrás
pai
commit
f432dc6bdc

+ 14 - 3
docs/md/container.md

@@ -39,9 +39,20 @@ iterations.<br/>
 The implementation is based on _sparse sets_ and each bucket is identified by an
 implicit list within the packed array itself.
 
-The interface is in all respects similar to its counterpart in the standard
-library, that is, `std::unordered_map`.<br/>
-Therefore, there is no need to go into the API description.
+The interface is very close to its counterpart in the standard library, that is,
+`std::unordered_map`.<br/>
+However, both local and non-local iterators returned by a dense map belong to
+the input iterator category although they respectively model the concepts of a
+_forward iterator_ type and a _random access iterator_ type.<br/>
+This is because they return a pair of references rather than a reference to a
+pair. In other words, dense maps return a so called _proxy iterator_ the value
+type of which is:
+
+* `std::pair<const Key &, Type &>` for non-const iterator types.
+* `std::pair<const Key &, const Type &>` for const iterator types.
+
+This is quite different from what any standard library map returns and should be
+taken into account when looking for a drop-in replacement.
 
 ## Dense set
 

+ 105 - 117
src/entt/container/dense_map.hpp

@@ -14,6 +14,7 @@
 #include <vector>
 #include "../config/config.h"
 #include "../core/compressed_pair.hpp"
+#include "../core/iterator.hpp"
 #include "../core/memory.hpp"
 #include "../core/type_traits.hpp"
 #include "fwd.hpp"
@@ -29,27 +30,45 @@ namespace internal {
 
 template<typename Key, typename Type>
 struct dense_map_node final {
+    using value_type = std::pair<Key, Type>;
+
     template<typename... Args>
     dense_map_node(const std::size_t pos, Args &&...args)
         : next{pos},
           element{std::forward<Args>(args)...} {}
 
+    template<typename Allocator, typename... Args>
+    dense_map_node(std::allocator_arg_t, const Allocator &allocator, const std::size_t pos, Args &&...args)
+        : next{pos},
+          element{entt::make_obj_using_allocator<value_type>(allocator, std::forward<Args>(args)...)} {}
+
+    template<typename Allocator>
+    dense_map_node(std::allocator_arg_t, const Allocator &allocator, const dense_map_node &other)
+        : next{other.next},
+          element{entt::make_obj_using_allocator<value_type>(allocator, other.element)} {}
+
+    template<typename Allocator>
+    dense_map_node(std::allocator_arg_t, const Allocator &allocator, dense_map_node &&other)
+        : next{other.next},
+          element{entt::make_obj_using_allocator<value_type>(allocator, std::move(other.element))} {}
+
     std::size_t next;
-    std::pair<Key, Type> element;
+    value_type element;
 };
 
 template<typename It>
 class dense_map_iterator final {
     friend dense_map_iterator<const std::remove_pointer_t<It> *>;
 
-    using iterator_traits = std::iterator_traits<decltype(std::addressof(std::declval<It>()->element))>;
+    using first_type = decltype(std::as_const(std::declval<It>()->element.first));
+    using second_type = decltype((std::declval<It>()->element.second));
 
 public:
-    using value_type = typename iterator_traits::value_type;
-    using pointer = typename iterator_traits::pointer;
-    using reference = typename iterator_traits::reference;
-    using difference_type = typename iterator_traits::difference_type;
-    using iterator_category = std::random_access_iterator_tag;
+    using value_type = std::pair<first_type, second_type>;
+    using pointer = input_iterator_pointer<value_type>;
+    using reference = value_type;
+    using difference_type = std::ptrdiff_t;
+    using iterator_category = std::input_iterator_tag;
 
     dense_map_iterator() ENTT_NOEXCEPT = default;
 
@@ -97,20 +116,19 @@ public:
     }
 
     [[nodiscard]] reference operator[](const difference_type value) const ENTT_NOEXCEPT {
-        return it[value].element;
+        return *dense_map_iterator{it + value};
     }
 
     [[nodiscard]] pointer operator->() const ENTT_NOEXCEPT {
-        return std::addressof(it->element);
+        return operator*();
     }
 
     [[nodiscard]] reference operator*() const ENTT_NOEXCEPT {
-        return *operator->();
+        return value_type{it->element.first, it->element.second};
     }
 
     template<typename ILhs, typename IRhs>
-    // auto is fine here but gcc 7.5.0 has a nasty issue with deduced return types and friend functions
-    friend /* auto */ std::ptrdiff_t operator-(const dense_map_iterator<ILhs> &, const dense_map_iterator<IRhs> &) ENTT_NOEXCEPT;
+    friend std::ptrdiff_t operator-(const dense_map_iterator<ILhs> &, const dense_map_iterator<IRhs> &) ENTT_NOEXCEPT;
 
     template<typename ILhs, typename IRhs>
     friend bool operator==(const dense_map_iterator<ILhs> &, const dense_map_iterator<IRhs> &) ENTT_NOEXCEPT;
@@ -123,8 +141,7 @@ private:
 };
 
 template<typename ILhs, typename IRhs>
-// auto is fine here but gcc 7.5.0 has a nasty issue with deduced return types and friend functions
-[[nodiscard]] /* auto */ std::ptrdiff_t operator-(const dense_map_iterator<ILhs> &lhs, const dense_map_iterator<IRhs> &rhs) ENTT_NOEXCEPT {
+[[nodiscard]] std::ptrdiff_t operator-(const dense_map_iterator<ILhs> &lhs, const dense_map_iterator<IRhs> &rhs) ENTT_NOEXCEPT {
     return lhs.it - rhs.it;
 }
 
@@ -162,14 +179,15 @@ template<typename It>
 class dense_map_local_iterator final {
     friend dense_map_local_iterator<const std::remove_pointer_t<It> *>;
 
-    using iterator_traits = std::iterator_traits<decltype(std::addressof(std::declval<It>()->element))>;
+    using first_type = decltype(std::as_const(std::declval<It>()->element.first));
+    using second_type = decltype((std::declval<It>()->element.second));
 
 public:
-    using value_type = typename iterator_traits::value_type;
-    using pointer = typename iterator_traits::pointer;
-    using reference = typename iterator_traits::reference;
-    using difference_type = typename iterator_traits::difference_type;
-    using iterator_category = std::forward_iterator_tag;
+    using value_type = std::pair<first_type, second_type>;
+    using pointer = input_iterator_pointer<value_type>;
+    using reference = value_type;
+    using difference_type = std::ptrdiff_t;
+    using iterator_category = std::input_iterator_tag;
 
     dense_map_local_iterator() ENTT_NOEXCEPT = default;
 
@@ -192,11 +210,11 @@ public:
     }
 
     [[nodiscard]] pointer operator->() const ENTT_NOEXCEPT {
-        return std::addressof(it[offset].element);
+        return operator*();
     }
 
     [[nodiscard]] reference operator*() const ENTT_NOEXCEPT {
-        return *operator->();
+        return value_type{it[offset].element.first, it[offset].element.second};
     }
 
     [[nodiscard]] std::size_t index() const ENTT_NOEXCEPT {
@@ -243,10 +261,8 @@ class dense_map {
     static constexpr float default_threshold = 0.875f;
     static constexpr std::size_t minimum_capacity = 8u;
 
-    using alloc_traits = std::allocator_traits<Allocator>;
-    static_assert(std::is_same_v<typename alloc_traits::value_type, std::pair<const Key, Type>>);
-
-    using node_type = internal::dense_map_node<const Key, Type>;
+    using node_type = internal::dense_map_node<Key, Type>;
+    using alloc_traits = typename std::allocator_traits<Allocator>;
     using sparse_container_type = std::vector<std::size_t, typename alloc_traits::template rebind_alloc<std::size_t>>;
     using packed_container_type = std::vector<node_type, typename alloc_traits::template rebind_alloc<node_type>>;
 
@@ -276,41 +292,12 @@ class dense_map {
         return cend();
     }
 
-    template<typename... Args>
-    [[nodiscard]] auto get_or_emplace(const Key &key, Args &&...args) {
-        const auto hash = sparse.second()(key);
-        auto index = hash_to_bucket(hash);
-
-        if(auto it = constrained_find(key, index); it != end()) {
-            return std::make_pair(it, false);
-        }
-
-        if(const auto count = size() + 1u; count > (bucket_count() * max_load_factor())) {
-            rehash(bucket_count() * 2u);
-            index = hash_to_bucket(hash);
-        }
-
-        packed.first().emplace_back(sparse.first()[index], std::forward<Args>(args)...);
-        // update goes after emplace to enforce exception guarantees
-        sparse.first()[index] = size() - 1u;
-
-        return std::make_pair(--end(), true);
-    }
-
     void move_and_pop(const std::size_t pos) {
         if(const auto last = size() - 1u; pos != last) {
+            packed.first()[pos] = std::move(packed.first().back());
             size_type *curr = sparse.first().data() + bucket(packed.first().back().element.first);
             for(; *curr != last; curr = &packed.first()[*curr].next) {}
             *curr = pos;
-
-            using node_alloc_traits = typename alloc_traits::template rebind_traits<decltype(node_type::element)>;
-            typename node_alloc_traits::allocator_type allocator = packed.first().get_allocator();
-            auto *ptr = std::addressof(packed.first()[pos].element);
-
-            std::destroy_at(ptr);
-            packed.first()[pos].next = packed.first().back().next;
-            // no exception guarantees when mapped type has a throwing move constructor (we're technically doomed)
-            node_alloc_traits::construct(allocator, ptr, std::move(packed.first().back().element));
         }
 
         packed.first().pop_back();
@@ -385,12 +372,8 @@ public:
         rehash(bucket_count);
     }
 
-    /**
-     * @brief Copy constructor.
-     * @param other The instance to copy from.
-     */
-    dense_map(const dense_map &other)
-        : dense_map{other, alloc_traits::select_on_container_copy_construction(other.get_allocator())} {}
+    /*! @brief Default copy constructor. */
+    dense_map(const dense_map &) = default;
 
     /**
      * @brief Allocator-extended copy constructor.
@@ -398,17 +381,12 @@ public:
      * @param allocator The allocator to use.
      */
     dense_map(const dense_map &other, const allocator_type &allocator)
-        : sparse{sparse_container_type{other.sparse.first(), allocator}, other.sparse.second()},
-          // cannot copy the container directly due to a nasty issue of apple clang :(
-          packed{packed_container_type{other.packed.first().begin(), other.packed.first().end(), allocator}, other.packed.second()},
-          threshold{other.threshold} {
-    }
+        : sparse{std::piecewise_construct, std::forward_as_tuple(other.sparse.first(), allocator), std::forward_as_tuple(other.sparse.second())},
+          packed{std::piecewise_construct, std::forward_as_tuple(other.packed.first(), allocator), std::forward_as_tuple(other.packed.second())},
+          threshold{other.threshold} {}
 
-    /**
-     * @brief Default move constructor.
-     * @param other The instance to move from.
-     */
-    dense_map(dense_map &&other) = default;
+    /*! @brief Default move constructor. */
+    dense_map(dense_map &&) = default;
 
     /**
      * @brief Allocator-extended move constructor.
@@ -416,35 +394,21 @@ public:
      * @param allocator The allocator to use.
      */
     dense_map(dense_map &&other, const allocator_type &allocator)
-        : sparse{sparse_container_type{std::move(other.sparse.first()), allocator}, std::move(other.sparse.second())},
-          // cannot move the container directly due to a nasty issue of apple clang :(
-          packed{packed_container_type{std::make_move_iterator(other.packed.first().begin()), std::make_move_iterator(other.packed.first().end()), allocator}, std::move(other.packed.second())},
+        : sparse{std::piecewise_construct, std::forward_as_tuple(std::move(other.sparse.first()), allocator), std::forward_as_tuple(std::move(other.sparse.second()))},
+          packed{std::piecewise_construct, std::forward_as_tuple(std::move(other.packed.first()), allocator), std::forward_as_tuple(std::move(other.packed.second()))},
           threshold{other.threshold} {}
 
-    /*! @brief Default destructor. */
-    ~dense_map() = default;
-
     /**
-     * @brief Copy assignment operator.
-     * @param other The instance to copy from.
+     * @brief Default copy assignment operator.
      * @return This container.
      */
-    dense_map &operator=(const dense_map &other) {
-        threshold = other.threshold;
-        sparse.first().clear();
-        packed.first().clear();
-        rehash(other.bucket_count());
-        packed.first().reserve(other.packed.first().size());
-        insert(other.cbegin(), other.cend());
-        return *this;
-    }
+    dense_map &operator=(const dense_map &) = default;
 
     /**
      * @brief Default move assignment operator.
-     * @param other The instance to move from.
      * @return This container.
      */
-    dense_map &operator=(dense_map &&other) = default;
+    dense_map &operator=(dense_map &&) = default;
 
     /**
      * @brief Returns the associated allocator.
@@ -573,29 +537,31 @@ public:
      */
     template<typename Arg>
     std::pair<iterator, bool> insert_or_assign(const key_type &key, Arg &&value) {
-        auto result = try_emplace(key, std::forward<Arg>(value));
-
-        if(!result.second) {
-            result.first->second = std::forward<Arg>(value);
+        if(const auto it = find(key); it != end()) {
+            it->second = std::forward<Arg>(value);
+            return {it, false};
         }
 
-        return result;
+        return emplace(key, std::forward<Arg>(value));
     }
 
     /*! @copydoc insert_or_assign */
     template<typename Arg>
     std::pair<iterator, bool> insert_or_assign(key_type &&key, Arg &&value) {
-        auto result = try_emplace(std::move(key), std::forward<Arg>(value));
-
-        if(!result.second) {
-            result.first->second = std::forward<Arg>(value);
+        if(const auto it = find(key); it != end()) {
+            it->second = std::forward<Arg>(value);
+            return {it, false};
         }
 
-        return result;
+        return emplace(std::move(key), std::forward<Arg>(value));
     }
 
     /**
      * @brief Constructs an element in-place, if the key does not exist.
+     *
+     * The element is also constructed when the container already has the key,
+     * in which case the newly constructed object is destroyed immediately.
+     *
      * @tparam Args Types of arguments to forward to the constructor of the
      * element.
      * @param args Arguments to forward to the constructor of the element.
@@ -605,16 +571,22 @@ public:
      */
     template<typename... Args>
     std::pair<iterator, bool> emplace(Args &&...args) {
-        if constexpr(sizeof...(Args) == 0u) {
-            return get_or_emplace(key_type{});
-        } else if constexpr(sizeof...(Args) == 1u) {
-            return get_or_emplace(args.first..., std::forward<Args>(args)...);
-        } else if constexpr(sizeof...(Args) == 2u) {
-            return get_or_emplace(std::get<0u>(std::tie(args...)), std::forward<Args>(args)...);
-        } else {
-            static_assert(sizeof...(Args) == 3u, "Invalid arguments");
-            return emplace(std::pair<key_type, mapped_type>{std::forward<Args>(args)...});
+        auto &node = packed.first().emplace_back(packed.first().size(), std::forward<Args>(args)...);
+        const auto hash = sparse.second()(node.element.first);
+        auto index = hash_to_bucket(hash);
+
+        if(auto it = constrained_find(node.element.first, index); it != end()) {
+            packed.first().pop_back();
+            return std::make_pair(it, false);
+        }
+
+        node.next = std::exchange(sparse.first()[index], size() - 1u);
+
+        if(size() > (bucket_count() * max_load_factor())) {
+            rehash(bucket_count() * 2u);
         }
+
+        return std::make_pair(--end(), true);
     }
 
     /**
@@ -630,13 +602,13 @@ public:
      */
     template<typename... Args>
     std::pair<iterator, bool> try_emplace(const key_type &key, Args &&...args) {
-        return get_or_emplace(key, std::piecewise_construct, std::forward_as_tuple(key), std::forward_as_tuple(std::forward<Args>(args)...));
+        return emplace(std::piecewise_construct, std::forward_as_tuple(key), std::forward_as_tuple(std::forward<Args>(args)...));
     }
 
     /*! @copydoc try_emplace */
     template<typename... Args>
     std::pair<iterator, bool> try_emplace(key_type &&key, Args &&...args) {
-        return get_or_emplace(key, std::piecewise_construct, std::forward_as_tuple(std::move(key)), std::forward_as_tuple(std::forward<Args>(args)...));
+        return emplace(std::piecewise_construct, std::forward_as_tuple(std::move(key)), std::forward_as_tuple(std::forward<Args>(args)...));
     }
 
     /**
@@ -657,13 +629,11 @@ public:
      * @return An iterator following the last removed element.
      */
     iterator erase(const_iterator first, const_iterator last) {
-        const auto dist = std::distance(cbegin(), first);
-
-        for(auto rfirst = std::make_reverse_iterator(last), rlast = std::make_reverse_iterator(first); rfirst != rlast; ++rfirst) {
-            erase(rfirst->first);
+        for(; last != first; --last) {
+            erase((last - 1u)->first);
         }
 
-        return dist > static_cast<decltype(dist)>(size()) ? end() : (begin() + dist);
+        return (begin() + std::distance(cbegin(), last));
     }
 
     /**
@@ -957,4 +927,22 @@ private:
 
 } // namespace entt
 
+/**
+ * @cond TURN_OFF_DOXYGEN
+ * Internal details not to be documented.
+ */
+
+namespace std {
+
+template<typename Key, typename Value, typename Allocator>
+struct uses_allocator<entt::internal::dense_map_node<Key, Value>, Allocator>
+    : std::true_type {};
+
+} // namespace std
+
+/**
+ * Internal details not to be documented.
+ * @endcond
+ */
+
 #endif

+ 4 - 5
src/entt/entity/registry.hpp

@@ -42,15 +42,14 @@ class storage_proxy_iterator final {
     template<typename Other>
     friend class storage_proxy_iterator;
 
-    using iterator_traits = std::iterator_traits<It>;
-    using first_type = typename iterator_traits::value_type::first_type;
-    using second_type = typename iterator_traits::value_type::second_type::element_type;
+    using first_type = std::remove_reference_t<decltype(std::declval<It>()->first)>;
+    using second_type = std::remove_reference_t<decltype(std::declval<It>()->second)>;
 
 public:
-    using value_type = std::pair<first_type, constness_as_t<second_type, std::remove_reference_t<typename iterator_traits::reference>> &>;
+    using value_type = std::pair<first_type, constness_as_t<typename second_type::element_type, second_type> &>;
     using pointer = input_iterator_pointer<value_type>;
     using reference = value_type;
-    using difference_type = typename iterator_traits::difference_type;
+    using difference_type = std::ptrdiff_t;
     using iterator_category = std::input_iterator_tag;
 
     storage_proxy_iterator() ENTT_NOEXCEPT = default;

+ 84 - 18
test/entt/container/dense_map.cpp

@@ -7,9 +7,11 @@
 #include <utility>
 #include <gtest/gtest.h>
 #include <entt/container/dense_map.hpp>
+#include <entt/core/iterator.hpp>
 #include <entt/core/memory.hpp>
 #include <entt/core/utility.hpp>
 #include "../common/throwing_allocator.hpp"
+#include "../common/tracked_memory_resource.hpp"
 
 struct transparent_equal_to {
     using is_transparent = void;
@@ -175,9 +177,9 @@ TEST(DenseMap, Move) {
 TEST(DenseMap, Iterator) {
     using iterator = typename entt::dense_map<int, int>::iterator;
 
-    static_assert(std::is_same_v<iterator::value_type, std::pair<const int, int>>);
-    static_assert(std::is_same_v<iterator::pointer, std::pair<const int, int> *>);
-    static_assert(std::is_same_v<iterator::reference, std::pair<const int, int> &>);
+    static_assert(std::is_same_v<iterator::value_type, std::pair<const int &, int &>>);
+    static_assert(std::is_same_v<iterator::pointer, entt::input_iterator_pointer<std::pair<const int &, int &>>>);
+    static_assert(std::is_same_v<iterator::reference, std::pair<const int &, int &>>);
 
     entt::dense_map<int, int> map;
     map.emplace(3, 42);
@@ -228,9 +230,9 @@ TEST(DenseMap, Iterator) {
 TEST(DenseMap, ConstIterator) {
     using iterator = typename entt::dense_map<int, int>::const_iterator;
 
-    static_assert(std::is_same_v<iterator::value_type, std::pair<const int, int>>);
-    static_assert(std::is_same_v<iterator::pointer, const std::pair<const int, int> *>);
-    static_assert(std::is_same_v<iterator::reference, const std::pair<const int, int> &>);
+    static_assert(std::is_same_v<iterator::value_type, std::pair<const int &, const int &>>);
+    static_assert(std::is_same_v<iterator::pointer, entt::input_iterator_pointer<std::pair<const int &, const int &>>>);
+    static_assert(std::is_same_v<iterator::reference, std::pair<const int &, const int &>>);
 
     entt::dense_map<int, int> map;
     map.emplace(3, 42);
@@ -285,8 +287,8 @@ TEST(DenseMap, IteratorConversion) {
     typename entt::dense_map<int, int>::iterator it = map.begin();
     typename entt::dense_map<int, int>::const_iterator cit = it;
 
-    static_assert(std::is_same_v<decltype(*it), std::pair<const int, int> &>);
-    static_assert(std::is_same_v<decltype(*cit), const std::pair<const int, int> &>);
+    static_assert(std::is_same_v<decltype(*it), std::pair<const int &, int &>>);
+    static_assert(std::is_same_v<decltype(*cit), std::pair<const int &, const int &>>);
 
     ASSERT_EQ(it->first, 3);
     ASSERT_EQ((*it).second, 42);
@@ -906,9 +908,9 @@ TEST(DenseMapDeathTest, Indexing) {
 TEST(DenseMap, LocalIterator) {
     using iterator = typename entt::dense_map<std::size_t, std::size_t, entt::identity>::local_iterator;
 
-    static_assert(std::is_same_v<iterator::value_type, std::pair<const std::size_t, std::size_t>>);
-    static_assert(std::is_same_v<iterator::pointer, std::pair<const std::size_t, std::size_t> *>);
-    static_assert(std::is_same_v<iterator::reference, std::pair<const std::size_t, std::size_t> &>);
+    static_assert(std::is_same_v<iterator::value_type, std::pair<const std::size_t &, std::size_t &>>);
+    static_assert(std::is_same_v<iterator::pointer, entt::input_iterator_pointer<std::pair<const std::size_t &, std::size_t &>>>);
+    static_assert(std::is_same_v<iterator::reference, std::pair<const std::size_t &, std::size_t &>>);
 
     static constexpr std::size_t minimum_bucket_count = 8u;
     entt::dense_map<std::size_t, std::size_t, entt::identity> map;
@@ -934,9 +936,9 @@ TEST(DenseMap, LocalIterator) {
 TEST(DenseMap, ConstLocalIterator) {
     using iterator = typename entt::dense_map<std::size_t, std::size_t, entt::identity>::const_local_iterator;
 
-    static_assert(std::is_same_v<iterator::value_type, std::pair<const std::size_t, std::size_t>>);
-    static_assert(std::is_same_v<iterator::pointer, const std::pair<const std::size_t, std::size_t> *>);
-    static_assert(std::is_same_v<iterator::reference, const std::pair<const std::size_t, std::size_t> &>);
+    static_assert(std::is_same_v<iterator::value_type, std::pair<const std::size_t &, const std::size_t &>>);
+    static_assert(std::is_same_v<iterator::pointer, entt::input_iterator_pointer<std::pair<const std::size_t &, const std::size_t &>>>);
+    static_assert(std::is_same_v<iterator::reference, std::pair<const std::size_t &, const std::size_t &>>);
 
     static constexpr std::size_t minimum_bucket_count = 8u;
     entt::dense_map<std::size_t, std::size_t, entt::identity> map;
@@ -966,8 +968,8 @@ TEST(DenseMap, LocalIteratorConversion) {
     typename entt::dense_map<int, int>::local_iterator it = map.begin(map.bucket(3));
     typename entt::dense_map<int, int>::const_local_iterator cit = it;
 
-    static_assert(std::is_same_v<decltype(*it), std::pair<const int, int> &>);
-    static_assert(std::is_same_v<decltype(*cit), const std::pair<const int, int> &>);
+    static_assert(std::is_same_v<decltype(*it), std::pair<const int &, int &>>);
+    static_assert(std::is_same_v<decltype(*cit), std::pair<const int &, const int &>>);
 
     ASSERT_EQ(it->first, 3);
     ASSERT_EQ((*it).second, 42);
@@ -1078,8 +1080,8 @@ TEST(DenseMap, Reserve) {
 }
 
 TEST(DenseMap, ThrowingAllocator) {
-    using allocator = test::throwing_allocator<std::pair<const std::size_t, std::size_t>>;
-    using packed_allocator = test::throwing_allocator<entt::internal::dense_map_node<const std::size_t, std::size_t>>;
+    using allocator = test::throwing_allocator<std::pair<std::size_t, std::size_t>>;
+    using packed_allocator = test::throwing_allocator<entt::internal::dense_map_node<std::size_t, std::size_t>>;
     using packed_exception = typename packed_allocator::exception_type;
 
     static constexpr std::size_t minimum_bucket_count = 8u;
@@ -1091,3 +1093,67 @@ TEST(DenseMap, ThrowingAllocator) {
     ASSERT_THROW(map.reserve(2u * map.bucket_count()), packed_exception);
     ASSERT_EQ(map.bucket_count(), minimum_bucket_count);
 }
+
+#if defined(ENTT_HAS_TRACKED_MEMORY_RESOURCE)
+
+TEST(DenseMap, NoUsesAllocatorConstruction) {
+    using allocator = std::pmr::polymorphic_allocator<std::pair<int, int>>;
+
+    test::tracked_memory_resource memory_resource{};
+    entt::dense_map<int, int, std::hash<int>, std::equal_to<int>, allocator> map{&memory_resource};
+
+    map.reserve(1u);
+    memory_resource.reset();
+    map.emplace(0, 0);
+
+    ASSERT_TRUE(map.get_allocator().resource()->is_equal(memory_resource));
+    ASSERT_EQ(memory_resource.do_allocate_counter(), 0u);
+    ASSERT_EQ(memory_resource.do_deallocate_counter(), 0u);
+}
+
+TEST(DenseMap, KeyUsesAllocatorConstruction) {
+    using string_type = typename test::tracked_memory_resource::string_type;
+    using allocator = std::pmr::polymorphic_allocator<std::pair<string_type, int>>;
+
+    test::tracked_memory_resource memory_resource{};
+    entt::dense_map<string_type, int, std::hash<string_type>, std::equal_to<string_type>, allocator> map{&memory_resource};
+
+    map.reserve(1u);
+    memory_resource.reset();
+    map.emplace(test::tracked_memory_resource::default_value, 0);
+
+    ASSERT_TRUE(map.get_allocator().resource()->is_equal(memory_resource));
+    ASSERT_GT(memory_resource.do_allocate_counter(), 0u);
+    ASSERT_EQ(memory_resource.do_deallocate_counter(), 0u);
+
+    memory_resource.reset();
+    decltype(map) other{map, &memory_resource};
+
+    ASSERT_TRUE(memory_resource.is_equal(*other.get_allocator().resource()));
+    ASSERT_GT(memory_resource.do_allocate_counter(), 0u);
+    ASSERT_EQ(memory_resource.do_deallocate_counter(), 0u);
+}
+
+TEST(DenseMap, ValueUsesAllocatorConstruction) {
+    using string_type = typename test::tracked_memory_resource::string_type;
+    using allocator = std::pmr::polymorphic_allocator<std::pair<int, string_type>>;
+
+    test::tracked_memory_resource memory_resource{};
+    entt::dense_map<int, string_type, std::hash<int>, std::equal_to<int>, allocator> map{std::pmr::get_default_resource()};
+
+    map.reserve(1u);
+    memory_resource.reset();
+    map.emplace(0, test::tracked_memory_resource::default_value);
+
+    ASSERT_FALSE(map.get_allocator().resource()->is_equal(memory_resource));
+    ASSERT_EQ(memory_resource.do_allocate_counter(), 0u);
+    ASSERT_EQ(memory_resource.do_deallocate_counter(), 0u);
+
+    decltype(map) other{std::move(map), &memory_resource};
+
+    ASSERT_TRUE(other.get_allocator().resource()->is_equal(memory_resource));
+    ASSERT_GT(memory_resource.do_allocate_counter(), 0u);
+    ASSERT_EQ(memory_resource.do_deallocate_counter(), 0u);
+}
+
+#endif