Skip to content

Commit e09c5b8

Browse files
committed
Add linked_map key traits
1 parent b918bad commit e09c5b8

2 files changed

Lines changed: 269 additions & 73 deletions

File tree

‎include/stdcorelib/adt/linked_map.h‎

Lines changed: 182 additions & 68 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,8 @@
99
#include <list>
1010
#include <map>
1111
#include <memory>
12+
#include <string>
13+
#include <string_view>
1214
#include <tuple>
1315
#include <type_traits>
1416
#include <unordered_map>
@@ -18,7 +20,10 @@
1820
#include <stdcorelib/stdc_global.h>
1921

2022
#ifdef QT_CORE_LIB
23+
# include <QHashFunctions>
2124
# include <QList>
25+
# include <QString>
26+
# include <QStringView>
2227
# include <QVector>
2328
#endif
2429

@@ -27,6 +32,10 @@ namespace stdc {
2732
/// \addtogroup containers
2833
/// @{
2934

35+
/// Associates an owning key type with a non-owning view type.
36+
template <class K>
37+
struct linked_map_view_traits {};
38+
3039
namespace detail {
3140

3241
template <class>
@@ -46,81 +55,162 @@ namespace stdc {
4655
struct has_capacity<T, std::void_t<decltype(std::declval<const T &>().capacity())>>
4756
: std::true_type {};
4857

49-
template <class T>
50-
struct linked_map_index_traits {
51-
static_assert(dependent_false<T>::value,
52-
"linked_map supports std::map and std::unordered_map indexes");
53-
};
58+
}
5459

55-
template <class K, class V, class Compare, class Allocator>
56-
struct linked_map_index_traits<std::map<K, V, Compare, Allocator>> {
57-
template <class Mapped>
58-
using allocator_for = typename std::allocator_traits<Allocator>::template rebind_alloc<
59-
std::pair<const K, Mapped>>;
60+
/// Hashes an index key with its standard hash function.
61+
struct linked_map_hash {
62+
using is_transparent = void;
6063

61-
template <class Mapped>
62-
using rebind = std::map<K, Mapped, Compare, allocator_for<Mapped>>;
64+
template <class Key>
65+
size_t operator()(const Key &key) const noexcept(noexcept(std::hash<Key>()(key))) {
66+
return std::hash<Key>()(key);
67+
}
6368

64-
template <class Mapped>
65-
static rebind<Mapped> make(const Allocator &allocator) {
66-
return rebind<Mapped>(Compare(), allocator_for<Mapped>(allocator));
67-
}
69+
#ifdef QT_CORE_LIB
70+
size_t operator()(QStringView key) const noexcept {
71+
return qHash(key);
72+
}
73+
#endif
74+
};
6875

69-
template <class Mapped>
70-
static rebind<Mapped> copy_configuration(const rebind<Mapped> &RHS,
71-
const Allocator &allocator) {
72-
return rebind<Mapped>(RHS.key_comp(), allocator_for<Mapped>(allocator));
73-
}
74-
};
76+
/// Stores a copy of each key in the lookup index.
77+
template <class K>
78+
struct linked_map_key_traits {
79+
using key_type = K;
80+
using index_key_type = K;
7581

76-
template <class K, class V, class Hash, class KeyEqual, class Allocator>
77-
struct linked_map_index_traits<std::unordered_map<K, V, Hash, KeyEqual, Allocator>> {
78-
template <class Mapped>
79-
using allocator_for = typename std::allocator_traits<Allocator>::template rebind_alloc<
80-
std::pair<const K, Mapped>>;
82+
static const index_key_type &index_key(const key_type &key) noexcept {
83+
return key;
84+
}
85+
};
8186

82-
template <class Mapped>
83-
using rebind = std::unordered_map<K, Mapped, Hash, KeyEqual, allocator_for<Mapped>>;
87+
namespace detail {
8488

85-
template <class Mapped>
86-
static rebind<Mapped> make(const Allocator &allocator) {
87-
return rebind<Mapped>(0, Hash(), KeyEqual(), allocator_for<Mapped>(allocator));
88-
}
89+
template <class K, class = void>
90+
struct linked_map_default_key_traits {
91+
using type = linked_map_key_traits<K>;
92+
};
8993

90-
template <class Mapped>
91-
static rebind<Mapped> copy_configuration(const rebind<Mapped> &RHS,
92-
const Allocator &allocator) {
93-
rebind<Mapped> result(RHS.bucket_count(), RHS.hash_function(), RHS.key_eq(),
94-
allocator_for<Mapped>(allocator));
95-
result.max_load_factor(RHS.max_load_factor());
96-
return result;
97-
}
94+
template <class K>
95+
struct linked_map_default_key_traits<
96+
K, std::void_t<typename linked_map_view_traits<K>::index_key_type>> {
97+
using type = linked_map_view_traits<K>;
9898
};
9999

100100
}
101101

102+
/// Configures an unordered lookup index independently of its key representation.
103+
template <class Hash = linked_map_hash, class KeyEqual = std::equal_to<>,
104+
class Allocator = std::allocator<std::byte>>
105+
struct linked_map_unordered_traits {
106+
template <class Value>
107+
using allocator_type =
108+
typename std::allocator_traits<Allocator>::template rebind_alloc<Value>;
109+
110+
template <class Key, class Mapped, class BaseAllocator>
111+
using map_type =
112+
std::unordered_map<Key, Mapped, Hash, KeyEqual,
113+
typename std::allocator_traits<BaseAllocator>::template rebind_alloc<
114+
std::pair<const Key, Mapped>>>;
115+
116+
template <class Map, class BaseAllocator>
117+
static Map make(const BaseAllocator &allocator) {
118+
using map_allocator = typename Map::allocator_type;
119+
return Map(0, typename Map::hasher(), typename Map::key_equal(),
120+
map_allocator(allocator));
121+
}
122+
123+
template <class Map, class BaseAllocator>
124+
static Map copy_configuration(const Map &RHS, const BaseAllocator &allocator) {
125+
using map_allocator = typename Map::allocator_type;
126+
Map result(RHS.bucket_count(), RHS.hash_function(), RHS.key_eq(),
127+
map_allocator(allocator));
128+
result.max_load_factor(RHS.max_load_factor());
129+
return result;
130+
}
131+
};
132+
133+
/// Configures an ordered lookup index independently of its key representation.
134+
template <class Compare = std::less<>, class Allocator = std::allocator<std::byte>>
135+
struct linked_map_ordered_traits {
136+
template <class Value>
137+
using allocator_type =
138+
typename std::allocator_traits<Allocator>::template rebind_alloc<Value>;
139+
140+
template <class Key, class Mapped, class BaseAllocator>
141+
using map_type =
142+
std::map<Key, Mapped, Compare,
143+
typename std::allocator_traits<BaseAllocator>::template rebind_alloc<
144+
std::pair<const Key, Mapped>>>;
145+
146+
template <class Map, class BaseAllocator>
147+
static Map make(const BaseAllocator &allocator) {
148+
using map_allocator = typename Map::allocator_type;
149+
return Map(typename Map::key_compare(), map_allocator(allocator));
150+
}
151+
152+
template <class Map, class BaseAllocator>
153+
static Map copy_configuration(const Map &RHS, const BaseAllocator &allocator) {
154+
using map_allocator = typename Map::allocator_type;
155+
return Map(RHS.key_comp(), map_allocator(allocator));
156+
}
157+
};
158+
159+
/// Combines an index container policy with an independently reusable key representation.
160+
template <class MapTraits, class KeyTraits>
161+
struct linked_map_index_traits {
162+
using key_type = typename KeyTraits::key_type;
163+
using index_key_type = typename KeyTraits::index_key_type;
164+
165+
template <class Value>
166+
using allocator_type = typename MapTraits::template allocator_type<Value>;
167+
168+
template <class Mapped, class BaseAllocator>
169+
using map_type =
170+
typename MapTraits::template map_type<index_key_type, Mapped, BaseAllocator>;
171+
172+
static decltype(auto)
173+
index_key(const key_type &key) noexcept(noexcept(KeyTraits::index_key(key))) {
174+
return KeyTraits::index_key(key);
175+
}
176+
177+
template <class Map, class BaseAllocator>
178+
static Map make(const BaseAllocator &allocator) {
179+
return MapTraits::template make<Map>(allocator);
180+
}
181+
182+
template <class Map, class BaseAllocator>
183+
static Map copy_configuration(const Map &RHS, const BaseAllocator &allocator) {
184+
return MapTraits::template copy_configuration<Map>(RHS, allocator);
185+
}
186+
};
187+
102188
/// An associative container that preserves insertion order.
103189
///
104-
/// Values live in a list so their order and iterators remain stable. A \c std::map or
105-
/// \c std::unordered_map indexes the list by key. The index stores its own copy of each key.
106-
template <class K, class V, template <class, class, class...> class Map = std::unordered_map,
107-
class... Mods>
190+
/// Values live in a list so their order and iterators remain stable. An independently
191+
/// configured associative container indexes the list. Its key traits decide whether the
192+
/// index owns another key or refers to the key in the list node.
193+
template <class K, class V, class MapTraits = linked_map_unordered_traits<>,
194+
class KeyTraits = typename detail::linked_map_default_key_traits<K>::type>
108195
class linked_map {
109196
public:
110197
using key_type = K;
111198
using mapped_type = V;
112199
using value_type = std::pair<const K, V>;
113200

114201
private:
115-
using configured_map = Map<K, V, Mods...>;
116-
using index_traits = detail::linked_map_index_traits<configured_map>;
117-
using allocator_traits = std::allocator_traits<typename configured_map::allocator_type>;
202+
using index_traits = linked_map_index_traits<MapTraits, KeyTraits>;
203+
static_assert(std::is_same_v<K, typename index_traits::key_type>,
204+
"linked_map key and index key traits must have the same key type");
205+
206+
using allocator_type_impl = typename index_traits::template allocator_type<value_type>;
207+
using allocator_traits = std::allocator_traits<allocator_type_impl>;
118208

119209
public:
120210
using list_allocator_type = typename allocator_traits::template rebind_alloc<value_type>;
121211
using list_type = std::list<value_type, list_allocator_type>;
122212

123-
using allocator_type = typename configured_map::allocator_type;
213+
using allocator_type = allocator_type_impl;
124214
using iterator = typename list_type::iterator;
125215
using const_iterator = typename list_type::const_iterator;
126216
using reverse_iterator = typename list_type::reverse_iterator;
@@ -133,14 +223,14 @@ namespace stdc {
133223
using const_pointer = typename list_type::const_pointer;
134224

135225
public:
136-
using map_type = typename index_traits::template rebind<iterator>;
226+
using map_type = typename index_traits::template map_type<iterator, allocator_type>;
137227

138228
linked_map() : linked_map(allocator_type()) {
139229
}
140230

141231
explicit linked_map(const allocator_type &allocator)
142232
: _list(list_allocator_type(allocator)),
143-
_map(index_traits::template make<iterator>(allocator)) {
233+
_map(index_traits::template make<map_type>(allocator)) {
144234
}
145235

146236
linked_map(const linked_map &RHS)
@@ -243,7 +333,7 @@ namespace stdc {
243333
}
244334

245335
bool remove(const K &key) {
246-
auto found = _map.find(key);
336+
auto found = _map.find(index_traits::index_key(key));
247337
if (found == _map.end()) {
248338
return false;
249339
}
@@ -263,29 +353,29 @@ namespace stdc {
263353

264354
iterator erase(const_iterator position) {
265355
assert(position != _list.end());
266-
auto found = _map.find(position->first);
356+
auto found = _map.find(index_traits::index_key(position->first));
267357
assert(found != _map.end() && const_iterator(found->second) == position);
268358
_map.erase(found);
269359
return _list.erase(position);
270360
}
271361

272362
iterator find(const K &key) {
273-
auto found = _map.find(key);
363+
auto found = _map.find(index_traits::index_key(key));
274364
return found == _map.end() ? end() : found->second;
275365
}
276366

277367
const_iterator find(const K &key) const {
278-
auto found = _map.find(key);
368+
auto found = _map.find(index_traits::index_key(key));
279369
return found == _map.end() ? cend() : const_iterator(found->second);
280370
}
281371

282372
V value(const K &key) const {
283-
auto found = _map.find(key);
373+
auto found = _map.find(index_traits::index_key(key));
284374
return found == _map.end() ? V() : found->second->second;
285375
}
286376

287377
V value(const K &key, const V &defaultValue) const {
288-
auto found = _map.find(key);
378+
auto found = _map.find(index_traits::index_key(key));
289379
return found == _map.end() ? defaultValue : found->second->second;
290380
}
291381

@@ -338,7 +428,7 @@ namespace stdc {
338428
}
339429

340430
bool contains(const K &key) const {
341-
return _map.find(key) != _map.end();
431+
return _map.find(index_traits::index_key(key)) != _map.end();
342432
}
343433

344434
size_type size() const noexcept {
@@ -441,7 +531,7 @@ namespace stdc {
441531

442532
linked_map(empty_copy_t, const linked_map &RHS, const allocator_type &allocator)
443533
: _list(list_allocator_type(allocator)),
444-
_map(index_traits::template copy_configuration<iterator>(RHS._map, allocator)) {
534+
_map(index_traits::template copy_configuration<map_type>(RHS._map, allocator)) {
445535
}
446536

447537
linked_map(const linked_map &RHS, const allocator_type &allocator)
@@ -454,22 +544,26 @@ namespace stdc {
454544
template <class... Args>
455545
std::pair<iterator, bool> emplace_impl(const_iterator position, const K &key,
456546
Args &&...args) {
457-
auto indexed = _map.emplace(key, _list.end());
458-
if (!indexed.second) {
459-
return {indexed.first->second, false};
547+
auto found = _map.find(index_traits::index_key(key));
548+
if (found != _map.end()) {
549+
return {found->second, false};
460550
}
461551

552+
auto inserted =
553+
_list.emplace(position, std::piecewise_construct, std::forward_as_tuple(key),
554+
std::forward_as_tuple(std::forward<Args>(args)...));
462555
#ifdef STDC_HAS_EXCEPTIONS
463556
try {
464557
#endif
465-
auto inserted =
466-
_list.emplace(position, std::piecewise_construct, std::forward_as_tuple(key),
467-
std::forward_as_tuple(std::forward<Args>(args)...));
468-
indexed.first->second = inserted;
469-
return {inserted, true};
558+
auto indexed = _map.emplace(index_traits::index_key(inserted->first), inserted);
559+
if (!indexed.second) {
560+
_list.erase(inserted);
561+
return {indexed.first->second, false};
562+
}
563+
return {indexed.first->second, true};
470564
#ifdef STDC_HAS_EXCEPTIONS
471565
} catch (...) {
472-
_map.erase(indexed.first);
566+
_list.erase(inserted);
473567
throw;
474568
}
475569
#endif
@@ -488,4 +582,24 @@ namespace stdc {
488582
/// @}
489583
}
490584

585+
/// Declares the view type used by the default linked map index for the given key type.
586+
#define STDC_DECLARE_LINKED_MAP_KEY_VIEW(Key, View) \
587+
template <> \
588+
struct stdc::linked_map_view_traits<Key> { \
589+
using key_type = Key; \
590+
using index_key_type = View; \
591+
\
592+
static index_key_type index_key(const key_type &key) noexcept( \
593+
std::is_nothrow_constructible_v<index_key_type, const key_type &>) { \
594+
return index_key_type(key); \
595+
} \
596+
};
597+
598+
STDC_DECLARE_LINKED_MAP_KEY_VIEW(std::string, std::string_view)
599+
STDC_DECLARE_LINKED_MAP_KEY_VIEW(std::wstring, std::wstring_view)
600+
601+
#ifdef QT_CORE_LIB
602+
STDC_DECLARE_LINKED_MAP_KEY_VIEW(QString, QStringView)
603+
#endif
604+
491605
#endif // STDCORELIB_LINKED_MAP_H

0 commit comments

Comments
 (0)