Coverage Report

Created: 2025-11-24 06:11

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/immer/immer/vector.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/detail/rbts/rbtree.hpp>
12
#include <immer/detail/rbts/rbtree_iterator.hpp>
13
#include <immer/memory_policy.hpp>
14
15
#if IMMER_DEBUG_PRINT
16
#include <immer/flex_vector.hpp>
17
#endif
18
19
namespace immer {
20
21
template <typename T,
22
          typename MemoryPolicy,
23
          detail::rbts::bits_t B,
24
          detail::rbts::bits_t BL>
25
class flex_vector;
26
27
template <typename T,
28
          typename MemoryPolicy,
29
          detail::rbts::bits_t B,
30
          detail::rbts::bits_t BL>
31
class vector_transient;
32
33
/*!
34
 * Immutable sequential container supporting both random access and
35
 * structural sharing.
36
 *
37
 * @tparam T The type of the values to be stored in the container.
38
 * @tparam MemoryPolicy Memory management policy. See @ref
39
 *         memory_policy.
40
 *
41
 * @rst
42
 *
43
 * This container provides a good trade-off between cache locality,
44
 * random access, update performance and structural sharing.  It does
45
 * so by storing the data in contiguous chunks of :math:`2^{BL}`
46
 * elements.  By default, when ``sizeof(T) == sizeof(void*)`` then
47
 * :math:`B=BL=5`, such that data would be stored in contiguous
48
 * chunks of :math:`32` elements.
49
 *
50
 * You may learn more about the meaning and implications of ``B`` and
51
 * ``BL`` parameters in the :doc:`implementation` section.
52
 *
53
 * .. note:: In several methods we say that their complexity is
54
 *    *effectively* :math:`O(...)`. Do not confuse this with the word
55
 *    *amortized*, which has a very different meaning.  In this
56
 *    context, *effective* means that while the
57
 *    mathematically rigurous
58
 *    complexity might be higher, for all practical matters the
59
 *    provided complexity is more useful to think about the actual
60
 *    cost of the operation.
61
 *
62
 * **Example**
63
 *   .. literalinclude:: ../example/vector/intro.cpp
64
 *      :language: c++
65
 *      :start-after: intro/start
66
 *      :end-before:  intro/end
67
 *
68
 * @endrst
69
 */
70
template <typename T,
71
          typename MemoryPolicy  = default_memory_policy,
72
          detail::rbts::bits_t B = default_bits,
73
          detail::rbts::bits_t BL =
74
              detail::rbts::derive_bits_leaf<T, MemoryPolicy, B>>
75
class vector
76
{
77
    using impl_t = detail::rbts::rbtree<T, MemoryPolicy, B, BL>;
78
    using flex_t = flex_vector<T, MemoryPolicy, B, BL>;
79
80
    using move_t =
81
        std::integral_constant<bool, MemoryPolicy::use_transient_rvalues>;
82
83
public:
84
    static constexpr auto bits      = B;
85
    static constexpr auto bits_leaf = BL;
86
    using memory_policy             = MemoryPolicy;
87
88
    using value_type      = T;
89
    using reference       = const T&;
90
    using size_type       = detail::rbts::size_t;
91
    using difference_type = std::ptrdiff_t;
92
    using const_reference = const T&;
93
94
    using iterator = detail::rbts::rbtree_iterator<T, MemoryPolicy, B, BL>;
95
    using const_iterator   = iterator;
96
    using reverse_iterator = std::reverse_iterator<iterator>;
97
98
    using transient_type = vector_transient<T, MemoryPolicy, B, BL>;
99
100
    /*!
101
     * Returns the maximum theoretical size supported by the internal structure
102
     * given the current B, BL.
103
     */
104
    constexpr static size_type max_size() { return impl_t::max_size(); }
105
106
    /*!
107
     * Default constructor.  It creates a vector of `size() == 0`.  It
108
     * does not allocate memory and its complexity is @f$ O(1) @f$.
109
     */
110
9.17k
    vector() = default;
111
112
    /*!
113
     * Constructs a vector containing the elements in `values`.
114
     */
115
    vector(std::initializer_list<T> values)
116
        : impl_{impl_t::from_initializer_list(values)}
117
    {
118
    }
119
120
    /*!
121
     * Constructs a vector containing the elements in the range
122
     * defined by the input iterator `first` and range sentinel `last`.
123
     */
124
    template <typename Iter,
125
              typename Sent,
126
              std::enable_if_t<detail::compatible_sentinel_v<Iter, Sent>,
127
                               bool> = true>
128
    vector(Iter first, Sent last)
129
        : impl_{impl_t::from_range(first, last)}
130
    {
131
    }
132
133
    /*!
134
     * Constructs a vector containing the element `val` repeated `n`
135
     * times.
136
     */
137
    vector(size_type n, T v = {})
138
        : impl_{impl_t::from_fill(n, v)}
139
    {
140
    }
141
142
    /*!
143
     * Returns an iterator pointing at the first element of the
144
     * collection. It does not allocate memory and its complexity is
145
     * @f$ O(1) @f$.
146
     */
147
    IMMER_NODISCARD iterator begin() const { return {impl_}; }
148
149
    /*!
150
     * Returns an iterator pointing just after the last element of the
151
     * collection. It does not allocate and its complexity is @f$ O(1) @f$.
152
     */
153
    IMMER_NODISCARD iterator end() const
154
    {
155
        return {impl_, typename iterator::end_t{}};
156
    }
157
158
    /*!
159
     * Returns an iterator that traverses the collection backwards,
160
     * pointing at the first element of the reversed collection. It
161
     * does not allocate memory and its complexity is @f$ O(1) @f$.
162
     */
163
    IMMER_NODISCARD reverse_iterator rbegin() const
164
    {
165
        return reverse_iterator{end()};
166
    }
167
168
    /*!
169
     * Returns an iterator that traverses the collection backwards,
170
     * pointing after the last element of the reversed collection. It
171
     * does not allocate memory and its complexity is @f$ O(1) @f$.
172
     */
173
    IMMER_NODISCARD reverse_iterator rend() const
174
    {
175
        return reverse_iterator{begin()};
176
    }
177
178
    /*!
179
     * Returns the number of elements in the container.  It does
180
     * not allocate memory and its complexity is @f$ O(1) @f$.
181
     */
182
102k
    IMMER_NODISCARD size_type size() const { return impl_.size; }
183
184
    /*!
185
     * Returns `true` if there are no elements in the container.  It
186
     * does not allocate memory and its complexity is @f$ O(1) @f$.
187
     */
188
    IMMER_NODISCARD bool empty() const { return impl_.size == 0; }
189
190
    /*!
191
     * Access the last element.
192
     */
193
    IMMER_NODISCARD const T& back() const { return impl_.back(); }
194
195
    /*!
196
     * Access the first element.
197
     */
198
    IMMER_NODISCARD const T& front() const { return impl_.front(); }
199
200
    /*!
201
     * Returns a `const` reference to the element at position `index`.
202
     * It is undefined when @f$ 0 index \geq size() @f$.  It does not
203
     * allocate memory and its complexity is *effectively* @f$ O(1)
204
     * @f$.
205
     */
206
    IMMER_NODISCARD reference operator[](size_type index) const
207
    {
208
        return impl_.get(index);
209
    }
210
211
    /*!
212
     * Returns a `const` reference to the element at position
213
     * `index`. It throws an `std::out_of_range` exception when @f$
214
     * index \geq size() @f$.  It does not allocate memory and its
215
     * complexity is *effectively* @f$ O(1) @f$.
216
     */
217
    reference at(size_type index) const { return impl_.get_check(index); }
218
219
    /*!
220
     * Returns whether the vectors are equal.
221
     */
222
    IMMER_NODISCARD bool operator==(const vector& other) const
223
    {
224
        return impl_.equals(other.impl_);
225
    }
226
    IMMER_NODISCARD bool operator!=(const vector& other) const
227
    {
228
        return !(*this == other);
229
    }
230
231
    /*!
232
     * Returns a vector with `value` inserted at the end.  It may
233
     * allocate memory and its complexity is *effectively* @f$ O(1) @f$.
234
     *
235
     * @rst
236
     *
237
     * **Example**
238
     *   .. literalinclude:: ../example/vector/vector.cpp
239
     *      :language: c++
240
     *      :dedent: 8
241
     *      :start-after: push-back/start
242
     *      :end-before:  push-back/end
243
     *
244
     * @endrst
245
     */
246
    IMMER_NODISCARD vector push_back(value_type value) const&
247
1.24M
    {
248
1.24M
        return impl_.push_back(std::move(value));
249
1.24M
    }
250
251
    IMMER_NODISCARD decltype(auto) push_back(value_type value) &&
252
361k
    {
253
361k
        return push_back_move(move_t{}, std::move(value));
254
361k
    }
255
256
    /*!
257
     * Returns a vector containing value `value` at position `idx`.
258
     * Undefined for `index >= size()`.
259
     * It may allocate memory and its complexity is
260
     * *effectively* @f$ O(1) @f$.
261
     *
262
     * @rst
263
     *
264
     * **Example**
265
     *   .. literalinclude:: ../example/vector/vector.cpp
266
     *      :language: c++
267
     *      :dedent: 8
268
     *      :start-after: set/start
269
     *      :end-before:  set/end
270
     *
271
     * @endrst
272
     */
273
    IMMER_NODISCARD vector set(size_type index, value_type value) const&
274
    {
275
        return impl_.assoc(index, std::move(value));
276
    }
277
278
    IMMER_NODISCARD decltype(auto) set(size_type index, value_type value) &&
279
    {
280
        return set_move(move_t{}, index, std::move(value));
281
    }
282
283
    /*!
284
     * Returns a vector containing the result of the expression
285
     * `fn((*this)[idx])` at position `idx`.
286
     * Undefined for `0 >= size()`.
287
     * It may allocate memory and its complexity is
288
     * *effectively* @f$ O(1) @f$.
289
     *
290
     * @rst
291
     *
292
     * **Example**
293
     *   .. literalinclude:: ../example/vector/vector.cpp
294
     *      :language: c++
295
     *      :dedent: 8
296
     *      :start-after: update/start
297
     *      :end-before:  update/end
298
     *
299
     * @endrst
300
     */
301
    template <typename FnT>
302
    IMMER_NODISCARD vector update(size_type index, FnT&& fn) const&
303
21.2k
    {
304
21.2k
        return impl_.update(index, std::forward<FnT>(fn));
305
21.2k
    }
306
307
    template <typename FnT>
308
    IMMER_NODISCARD decltype(auto) update(size_type index, FnT&& fn) &&
309
5.90k
    {
310
5.90k
        return update_move(move_t{}, index, std::forward<FnT>(fn));
311
5.90k
    }
312
313
    /*!
314
     * Returns a vector containing only the first `min(elems, size())`
315
     * elements. It may allocate memory and its complexity is
316
     * *effectively* @f$ O(1) @f$.
317
     *
318
     * @rst
319
     *
320
     * **Example**
321
     *   .. literalinclude:: ../example/vector/vector.cpp
322
     *      :language: c++
323
     *      :dedent: 8
324
     *      :start-after: take/start
325
     *      :end-before:  take/end
326
     *
327
     * @endrst
328
     */
329
    IMMER_NODISCARD vector take(size_type elems) const&
330
14.1k
    {
331
14.1k
        return impl_.take(elems);
332
14.1k
    }
333
334
    IMMER_NODISCARD decltype(auto) take(size_type elems) &&
335
35.2k
    {
336
35.2k
        return take_move(move_t{}, elems);
337
35.2k
    }
338
339
    /*!
340
     * Returns an @a transient form of this container, an
341
     * `immer::vector_transient`.
342
     */
343
    IMMER_NODISCARD transient_type transient() const& { return impl_; }
344
    IMMER_NODISCARD transient_type transient() && { return std::move(impl_); }
345
346
    /*!
347
     * Returns a value that can be used as identity for the container.  If two
348
     * values have the same identity, they are guaranteed to be equal and to
349
     * contain the same objects.  However, two equal containers are not
350
     * guaranteed to have the same identity.
351
     */
352
    std::pair<void*, void*> identity() const
353
    {
354
        return {impl_.root, impl_.tail};
355
    }
356
357
    // Semi-private
358
    const impl_t& impl() const { return impl_; }
359
360
#if IMMER_DEBUG_PRINT
361
    void debug_print(std::ostream& out = std::cerr) const
362
    {
363
        flex_t{*this}.debug_print(out);
364
    }
365
#endif
366
367
private:
368
    friend flex_t;
369
    friend transient_type;
370
371
    // for immer::persist
372
public:
373
    vector(impl_t impl)
374
1.27M
        : impl_(std::move(impl))
375
1.27M
    {
376
#if IMMER_DEBUG_PRINT
377
        // force the compiler to generate debug_print, so we can call
378
        // it from a debugger
379
        [](volatile auto) {}(&vector::debug_print);
380
#endif
381
1.27M
    }
382
383
private:
384
    vector&& push_back_move(std::true_type, value_type value)
385
361k
    {
386
361k
        impl_.push_back_mut({}, std::move(value));
387
361k
        return std::move(*this);
388
361k
    }
389
    vector push_back_move(std::false_type, value_type value)
390
    {
391
        return impl_.push_back(std::move(value));
392
    }
393
394
    vector&& set_move(std::true_type, size_type index, value_type value)
395
    {
396
        impl_.assoc_mut({}, index, std::move(value));
397
        return std::move(*this);
398
    }
399
    vector set_move(std::false_type, size_type index, value_type value)
400
    {
401
        return impl_.assoc(index, std::move(value));
402
    }
403
404
    template <typename Fn>
405
    vector&& update_move(std::true_type, size_type index, Fn&& fn)
406
5.90k
    {
407
5.90k
        impl_.update_mut({}, index, std::forward<Fn>(fn));
408
5.90k
        return std::move(*this);
409
5.90k
    }
410
    template <typename Fn>
411
    vector update_move(std::false_type, size_type index, Fn&& fn)
412
    {
413
        return impl_.update(index, std::forward<Fn>(fn));
414
    }
415
416
    vector&& take_move(std::true_type, size_type elems)
417
35.2k
    {
418
35.2k
        impl_.take_mut({}, elems);
419
35.2k
        return std::move(*this);
420
35.2k
    }
421
    vector take_move(std::false_type, size_type elems)
422
    {
423
        return impl_.take(elems);
424
    }
425
426
    impl_t impl_ = {};
427
};
428
429
} // namespace immer