Coverage Report

Created: 2025-08-28 06:18

/src/immer/immer/map.hpp
Line
Count
Source
1
//
2
// immer: immutable data structures for C++
3
// Copyright (C) 2016, 2017, 2018 Juan Pedro Bolivar Puente
4
//
5
// This software is distributed under the Boost Software License, Version 1.0.
6
// See accompanying file LICENSE or copy at http://boost.org/LICENSE_1_0.txt
7
//
8
9
#pragma once
10
11
#include <immer/config.hpp>
12
#include <immer/detail/hamts/champ.hpp>
13
#include <immer/detail/hamts/champ_iterator.hpp>
14
#include <immer/memory_policy.hpp>
15
16
#include <cassert>
17
#include <functional>
18
#include <stdexcept>
19
20
namespace immer {
21
22
template <typename K,
23
          typename T,
24
          typename Hash,
25
          typename Equal,
26
          typename MemoryPolicy,
27
          detail::hamts::bits_t B>
28
class map_transient;
29
30
/*!
31
 * Immutable unordered mapping of values from type `K` to type `T`.
32
 *
33
 * @tparam K    The type of the keys.
34
 * @tparam T    The type of the values to be stored in the container.
35
 * @tparam Hash The type of a function object capable of hashing
36
 *              values of type `T`.
37
 * @tparam Equal The type of a function object capable of comparing
38
 *              values of type `T`.
39
 * @tparam MemoryPolicy Memory management policy. See @ref
40
 *              memory_policy.
41
 *
42
 * @rst
43
 *
44
 * This container provides a good trade-off between cache locality,
45
 * search, update performance and structural sharing.  It does so by
46
 * storing the data in contiguous chunks of :math:`2^{B}` elements.
47
 * When storing big objects, the size of these contiguous chunks can
48
 * become too big, damaging performance.  If this is measured to be
49
 * problematic for a specific use-case, it can be solved by using a
50
 * `immer::box` to wrap the type `T`.
51
 *
52
 * **Example**
53
 *   .. literalinclude:: ../example/map/intro.cpp
54
 *      :language: c++
55
 *      :start-after: intro/start
56
 *      :end-before:  intro/end
57
 *
58
 * @endrst
59
 *
60
 */
61
template <typename K,
62
          typename T,
63
          typename Hash           = std::hash<K>,
64
          typename Equal          = std::equal_to<K>,
65
          typename MemoryPolicy   = default_memory_policy,
66
          detail::hamts::bits_t B = default_bits>
67
class map
68
{
69
    using value_t = std::pair<K, T>;
70
71
    using move_t =
72
        std::integral_constant<bool, MemoryPolicy::use_transient_rvalues>;
73
74
    struct project_value
75
    {
76
        const T& operator()(const value_t& v) const noexcept
77
7.16k
        {
78
7.16k
            return v.second;
79
7.16k
        }
80
        T&& operator()(value_t&& v) const noexcept
81
143k
        {
82
143k
            return std::move(v.second);
83
143k
        }
84
    };
85
86
    struct project_value_ptr
87
    {
88
        const T* operator()(const value_t& v) const noexcept
89
3.53k
        {
90
3.53k
            return &v.second;
91
3.53k
        }
92
    };
93
94
    struct combine_value
95
    {
96
        template <typename Kf, typename Tf>
97
        value_t operator()(Kf&& k, Tf&& v) const
98
164k
        {
99
164k
            return {std::forward<Kf>(k), std::forward<Tf>(v)};
100
164k
        }
101
    };
102
103
    struct default_value
104
    {
105
        const T& operator()() const
106
13.7k
        {
107
13.7k
            static T v{};
108
13.7k
            return v;
109
13.7k
        }
110
    };
111
112
    struct error_value
113
    {
114
        const T& operator()() const
115
        {
116
            IMMER_THROW(std::out_of_range{"key not found"});
117
        }
118
    };
119
120
    struct hash_key
121
    {
122
13.8M
        auto operator()(const value_t& v) { return Hash{}(v.first); }
immer::map<std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> >, immer::box<std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> >, immer::memory_policy<immer::free_list_heap_policy<immer::cpp_heap, 1024ul>, immer::refcount_policy, immer::spinlock_policy, immer::no_transience_policy, false, true> >, colliding_hash_t, std::__1::equal_to<void>, immer::memory_policy<immer::heap_policy<immer::cpp_heap>, immer::unsafe_refcount_policy, immer::no_lock_policy, immer::no_transience_policy, false, true>, 5u>::hash_key::operator()(std::__1::pair<std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> >, immer::box<std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> >, immer::memory_policy<immer::free_list_heap_policy<immer::cpp_heap, 1024ul>, immer::refcount_policy, immer::spinlock_policy, immer::no_transience_policy, false, true> > > const&)
Line
Count
Source
122
13.8M
        auto operator()(const value_t& v) { return Hash{}(v.first); }
Unexecuted instantiation: immer::map<int, int, std::__1::hash<int>, std::__1::equal_to<int>, immer::memory_policy<immer::free_list_heap_policy<immer::cpp_heap, 1024ul>, immer::refcount_policy, immer::spinlock_policy, immer::no_transience_policy, false, true>, 5u>::hash_key::operator()(std::__1::pair<int, int> const&)
123
124
        template <typename Key>
125
        auto operator()(const Key& v)
126
485k
        {
127
485k
            return Hash{}(v);
128
485k
        }
129
    };
130
131
    struct equal_key
132
    {
133
        auto operator()(const value_t& a, const value_t& b)
134
363k
        {
135
363k
            return Equal{}(a.first, b.first);
136
363k
        }
137
138
        template <typename Key>
139
        auto operator()(const value_t& a, const Key& b)
140
367k
        {
141
367k
            return Equal{}(a.first, b);
142
367k
        }
143
    };
144
145
    struct equal_value
146
    {
147
        auto operator()(const value_t& a, const value_t& b)
148
        {
149
            return Equal{}(a.first, b.first) && a.second == b.second;
150
        }
151
    };
152
153
    using impl_t =
154
        detail::hamts::champ<value_t, hash_key, equal_key, MemoryPolicy, B>;
155
156
public:
157
    using key_type        = K;
158
    using mapped_type     = T;
159
    using value_type      = std::pair<K, T>;
160
    using size_type       = detail::hamts::size_t;
161
    using difference_type = std::ptrdiff_t;
162
    using hasher          = Hash;
163
    using key_equal       = Equal;
164
    using reference       = const value_type&;
165
    using const_reference = const value_type&;
166
167
    using iterator = detail::hamts::
168
        champ_iterator<value_t, hash_key, equal_key, MemoryPolicy, B>;
169
    using const_iterator = iterator;
170
171
    using transient_type = map_transient<K, T, Hash, Equal, MemoryPolicy, B>;
172
173
    using memory_policy_type = MemoryPolicy;
174
175
    /*!
176
     * Constructs a map containing the elements in `values`.
177
     */
178
    map(std::initializer_list<value_type> values)
179
        : impl_{impl_t::from_initializer_list(values)}
180
    {
181
    }
182
183
    /*!
184
     * Constructs a map containing the elements in the range
185
     * defined by the input iterator `first` and range sentinel `last`.
186
     */
187
    template <typename Iter,
188
              typename Sent,
189
              std::enable_if_t<detail::compatible_sentinel_v<Iter, Sent>,
190
                               bool> = true>
191
    map(Iter first, Sent last)
192
        : impl_{impl_t::from_range(first, last)}
193
    {
194
    }
195
196
    /*!
197
     * Default constructor.  It creates a map of `size() == 0`.  It
198
     * does not allocate memory and its complexity is @f$ O(1) @f$.
199
     */
200
16.0k
    map() = default;
201
202
    /*!
203
     * Returns an iterator pointing at the first element of the
204
     * collection. It does not allocate memory and its complexity is
205
     * @f$ O(1) @f$.
206
     */
207
8.45k
    IMMER_NODISCARD iterator begin() const { return {impl_}; }
208
209
    /*!
210
     * Returns an iterator pointing just after the last element of the
211
     * collection. It does not allocate and its complexity is @f$ O(1) @f$.
212
     */
213
    IMMER_NODISCARD iterator end() const
214
8.45k
    {
215
8.45k
        return {impl_, typename iterator::end_t{}};
216
8.45k
    }
217
218
    /*!
219
     * Returns the number of elements in the container.  It does
220
     * not allocate memory and its complexity is @f$ O(1) @f$.
221
     */
222
    IMMER_NODISCARD size_type size() const { return impl_.size; }
223
224
    /*!
225
     * Returns `true` if there are no elements in the container.  It
226
     * does not allocate memory and its complexity is @f$ O(1) @f$.
227
     */
228
    IMMER_NODISCARD bool empty() const { return impl_.size == 0; }
229
230
    /*!
231
     * Returns `1` when the key `k` is contained in the map or `0`
232
     * otherwise. It won't allocate memory and its complexity is
233
     * *effectively* @f$ O(1) @f$.
234
     *
235
     * This overload participates in overload resolution only if
236
     * `Hash::is_transparent` is valid and denotes a type.
237
     */
238
    template <typename Key,
239
              typename U = Hash,
240
              typename   = typename U::is_transparent>
241
    IMMER_NODISCARD size_type count(const Key& k) const
242
    {
243
        return impl_.template get<detail::constantly<size_type, 1>,
244
                                  detail::constantly<size_type, 0>>(k);
245
    }
246
247
    /*!
248
     * Returns `1` when the key `k` is contained in the map or `0`
249
     * otherwise. It won't allocate memory and its complexity is
250
     * *effectively* @f$ O(1) @f$.
251
     */
252
    IMMER_NODISCARD size_type count(const K& k) const
253
291k
    {
254
291k
        return impl_.template get<detail::constantly<size_type, 1>,
255
291k
                                  detail::constantly<size_type, 0>>(k);
256
291k
    }
257
258
    /*!
259
     * Returns a `const` reference to the values associated to the key
260
     * `k`.  If the key is not contained in the map, it returns a
261
     * default constructed value.  It does not allocate memory and its
262
     * complexity is *effectively* @f$ O(1) @f$.
263
     *
264
     * This overload participates in overload resolution only if
265
     * `Hash::is_transparent` is valid and denotes a type.
266
     */
267
    template <typename Key,
268
              typename U = Hash,
269
              typename   = typename U::is_transparent>
270
    IMMER_NODISCARD const T& operator[](const Key& k) const
271
    {
272
        return impl_.template get<project_value, default_value>(k);
273
    }
274
275
    /*!
276
     * Returns a `const` reference to the values associated to the key
277
     * `k`.  If the key is not contained in the map, it returns a
278
     * default constructed value.  It does not allocate memory and its
279
     * complexity is *effectively* @f$ O(1) @f$.
280
     */
281
    IMMER_NODISCARD const T& operator[](const K& k) const
282
    {
283
        return impl_.template get<project_value, default_value>(k);
284
    }
285
286
    /*!
287
     * Returns a `const` reference to the values associated to the key
288
     * `k`.  If the key is not contained in the map, throws an
289
     * `std::out_of_range` error.  It does not allocate memory and its
290
     * complexity is *effectively* @f$ O(1) @f$.
291
     */
292
    template <typename Key,
293
              typename U = Hash,
294
              typename   = typename U::is_transparent>
295
    const T& at(const Key& k) const
296
    {
297
        return impl_.template get<project_value, error_value>(k);
298
    }
299
300
    /*!
301
     * Returns a `const` reference to the values associated to the key
302
     * `k`.  If the key is not contained in the map, throws an
303
     * `std::out_of_range` error.  It does not allocate memory and its
304
     * complexity is *effectively* @f$ O(1) @f$.
305
     *
306
     * This overload participates in overload resolution only if
307
     * `Hash::is_transparent` is valid and denotes a type.
308
     */
309
    const T& at(const K& k) const
310
    {
311
        return impl_.template get<project_value, error_value>(k);
312
    }
313
314
    /*!
315
     * Returns a pointer to the value associated with the key `k`.  If
316
     * the key is not contained in the map, a `nullptr` is returned.
317
     * It does not allocate memory and its complexity is *effectively*
318
     * @f$ O(1) @f$.
319
     *
320
     * @rst
321
     *
322
     * .. admonition:: Why doesn't this function return an iterator?
323
     *
324
     *   Associative containers from the C++ standard library provide a
325
     *   ``find`` method that returns an iterator pointing to the
326
     *   element in the container or ``end()`` when the key is missing.
327
     *   In the case of an unordered container, the only meaningful
328
     *   thing one may do with it is to compare it with the end, to
329
     *   test if the find was succesfull, and dereference it.  This
330
     *   comparison is cumbersome compared to testing for a non-empty
331
     *   optional value.  Furthermore, for an immutable container,
332
     *   returning an iterator would have some additional performance
333
     *   cost, with no benefits otherwise.
334
     *
335
     *   In our opinion, this function should return a
336
     *   ``std::optional<const T&>`` but this construction is not valid
337
     *   in any current standard.  As a compromise we return a
338
     *   pointer, which has similar syntactic properties yet it is
339
     *   unfortunately unnecessarily unrestricted.
340
     *
341
     * @endrst
342
     */
343
    IMMER_NODISCARD const T* find(const K& k) const
344
5.37k
    {
345
5.37k
        return impl_.template get<project_value_ptr,
346
5.37k
                                  detail::constantly<const T*, nullptr>>(k);
347
5.37k
    }
348
349
    /*!
350
     * Returns a pointer to the value associated with the key `k`.  If
351
     * the key is not contained in the map, a `nullptr` is returned.
352
     * It does not allocate memory and its complexity is *effectively*
353
     * @f$ O(1) @f$.
354
     *
355
     * This overload participates in overload resolution only if
356
     * `Hash::is_transparent` is valid and denotes a type.
357
     */
358
    template <typename Key,
359
              typename U = Hash,
360
              typename   = typename U::is_transparent>
361
    IMMER_NODISCARD const T* find(const Key& k) const
362
    {
363
        return impl_.template get<project_value_ptr,
364
                                  detail::constantly<const T*, nullptr>>(k);
365
    }
366
367
    /*!
368
     * Returns whether the maps are equal.
369
     */
370
    IMMER_NODISCARD bool operator==(const map& other) const
371
    {
372
        return impl_.template equals<equal_value>(other.impl_);
373
    }
374
    IMMER_NODISCARD bool operator!=(const map& other) const
375
    {
376
        return !(*this == other);
377
    }
378
379
    /*!
380
     * Returns a map containing the association `value`.  If the key is
381
     * already in the map, it replaces its association in the map.
382
     * It may allocate memory and its complexity is *effectively* @f$
383
     * O(1) @f$.
384
     */
385
    IMMER_NODISCARD map insert(value_type value) const&
386
    {
387
        return impl_.add(std::move(value));
388
    }
389
    IMMER_NODISCARD decltype(auto) insert(value_type value) &&
390
    {
391
        return insert_move(move_t{}, std::move(value));
392
    }
393
394
    /*!
395
     * Returns a map containing the association `(k, v)`.  If the key
396
     * is already in the map, it replaces its association in the map.
397
     * It may allocate memory and its complexity is *effectively* @f$
398
     * O(1) @f$.
399
     */
400
    IMMER_NODISCARD map set(key_type k, mapped_type v) const&
401
232k
    {
402
232k
        return impl_.add({std::move(k), std::move(v)});
403
232k
    }
404
    IMMER_NODISCARD decltype(auto) set(key_type k, mapped_type v) &&
405
12.9k
    {
406
12.9k
        return set_move(move_t{}, std::move(k), std::move(v));
407
12.9k
    }
408
409
    /*!
410
     * Returns a map replacing the association `(k, v)` by the
411
     * association new association `(k, fn(v))`, where `v` is the
412
     * currently associated value for `k` in the map or a default
413
     * constructed value otherwise. It may allocate memory
414
     * and its complexity is *effectively* @f$ O(1) @f$.
415
     */
416
    template <typename Fn>
417
    IMMER_NODISCARD map update(key_type k, Fn&& fn) const&
418
3.32k
    {
419
3.32k
        return impl_
420
3.32k
            .template update<project_value, default_value, combine_value>(
421
3.32k
                std::move(k), std::forward<Fn>(fn));
422
3.32k
    }
423
    template <typename Fn>
424
    IMMER_NODISCARD decltype(auto) update(key_type k, Fn&& fn) &&
425
160k
    {
426
160k
        return update_move(move_t{}, std::move(k), std::forward<Fn>(fn));
427
160k
    }
428
429
    /*!
430
     * Returns a map replacing the association `(k, v)` by the association new
431
     * association `(k, fn(v))`, where `v` is the currently associated value for
432
     * `k` in the map.  It does nothing if `k` is not present in the map. It
433
     * may allocate memory and its complexity is *effectively* @f$ O(1) @f$.
434
     */
435
    template <typename Fn>
436
    IMMER_NODISCARD map update_if_exists(key_type k, Fn&& fn) const&
437
    {
438
        return impl_.template update_if_exists<project_value, combine_value>(
439
            std::move(k), std::forward<Fn>(fn));
440
    }
441
    template <typename Fn>
442
    IMMER_NODISCARD decltype(auto) update_if_exists(key_type k, Fn&& fn) &&
443
    {
444
        return update_if_exists_move(
445
            move_t{}, std::move(k), std::forward<Fn>(fn));
446
    }
447
448
    /*!
449
     * Returns a map without the key `k`.  If the key is not
450
     * associated in the map it returns the same map.  It may allocate
451
     * memory and its complexity is *effectively* @f$ O(1) @f$.
452
     */
453
14.6k
    IMMER_NODISCARD map erase(const K& k) const& { return impl_.sub(k); }
454
    IMMER_NODISCARD decltype(auto) erase(const K& k) &&
455
10.4k
    {
456
10.4k
        return erase_move(move_t{}, k);
457
10.4k
    }
458
459
    /*!
460
     * Returns a @a transient form of this container, an
461
     * `immer::map_transient`.
462
     */
463
    IMMER_NODISCARD transient_type transient() const&
464
    {
465
        return transient_type{impl_};
466
    }
467
    IMMER_NODISCARD transient_type transient() &&
468
    {
469
        return transient_type{std::move(impl_)};
470
    }
471
472
    /*!
473
     * Returns a value that can be used as identity for the container.  If two
474
     * values have the same identity, they are guaranteed to be equal and to
475
     * contain the same objects.  However, two equal containers are not
476
     * guaranteed to have the same identity.
477
     */
478
    void* identity() const { return impl_.root; }
479
480
    // Semi-private
481
366k
    const impl_t& impl() const { return impl_; }
482
483
private:
484
    friend transient_type;
485
486
    map&& insert_move(std::true_type, value_type value)
487
    {
488
        impl_.add_mut({}, std::move(value));
489
        return std::move(*this);
490
    }
491
    map insert_move(std::false_type, value_type value)
492
    {
493
        return impl_.add(std::move(value));
494
    }
495
496
    map&& set_move(std::true_type, key_type k, mapped_type m)
497
12.9k
    {
498
12.9k
        impl_.add_mut({}, {std::move(k), std::move(m)});
499
12.9k
        return std::move(*this);
500
12.9k
    }
501
    map set_move(std::false_type, key_type k, mapped_type m)
502
    {
503
        return impl_.add({std::move(k), std::move(m)});
504
    }
505
506
    template <typename Fn>
507
    map&& update_move(std::true_type, key_type k, Fn&& fn)
508
160k
    {
509
160k
        impl_.template update_mut<project_value, default_value, combine_value>(
510
160k
            {}, std::move(k), std::forward<Fn>(fn));
511
160k
        return std::move(*this);
512
160k
    }
513
    template <typename Fn>
514
    map update_move(std::false_type, key_type k, Fn&& fn)
515
    {
516
        return impl_
517
            .template update<project_value, default_value, combine_value>(
518
                std::move(k), std::forward<Fn>(fn));
519
    }
520
521
    template <typename Fn>
522
    map&& update_if_exists_move(std::true_type, key_type k, Fn&& fn)
523
    {
524
        impl_.template update_if_exists_mut<project_value, combine_value>(
525
            {}, std::move(k), std::forward<Fn>(fn));
526
        return std::move(*this);
527
    }
528
    template <typename Fn>
529
    map update_if_exists_move(std::false_type, key_type k, Fn&& fn)
530
    {
531
        return impl_.template update_if_exists<project_value, combine_value>(
532
            std::move(k), std::forward<Fn>(fn));
533
    }
534
535
    map&& erase_move(std::true_type, const key_type& value)
536
10.4k
    {
537
10.4k
        impl_.sub_mut({}, value);
538
10.4k
        return std::move(*this);
539
10.4k
    }
540
    map erase_move(std::false_type, const key_type& value)
541
    {
542
        return impl_.sub(value);
543
    }
544
545
    // for immer::persist
546
public:
547
    map(impl_t impl)
548
250k
        : impl_(std::move(impl))
549
250k
    {
550
250k
    }
551
552
private:
553
    impl_t impl_ = impl_t::empty();
554
};
555
556
static_assert(std::is_nothrow_move_constructible<map<int, int>>::value,
557
              "map is not nothrow move constructible");
558
static_assert(std::is_nothrow_move_assignable<map<int, int>>::value,
559
              "map is not nothrow move assignable");
560
561
} // namespace immer