Coverage Report

Created: 2023-06-07 06:37

/src/immer/immer/flex_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/rrbtree.hpp>
12
#include <immer/detail/rbts/rrbtree_iterator.hpp>
13
#include <immer/memory_policy.hpp>
14
15
namespace immer {
16
17
template <typename T,
18
          typename MP,
19
          detail::rbts::bits_t B,
20
          detail::rbts::bits_t BL>
21
class vector;
22
23
template <typename T,
24
          typename MP,
25
          detail::rbts::bits_t B,
26
          detail::rbts::bits_t BL>
27
class flex_vector_transient;
28
29
/*!
30
 * Immutable sequential container supporting both random access,
31
 * structural sharing and efficient concatenation and slicing.
32
 *
33
 * @tparam T The type of the values to be stored in the container.
34
 * @tparam MemoryPolicy Memory management policy. See @ref
35
 *         memory_policy.
36
 *
37
 * @rst
38
 *
39
 * This container is very similar to `vector`_ but also supports
40
 * :math:`O(log(size))` *concatenation*, *slicing* and *insertion* at
41
 * any point. Its performance characteristics are almost identical
42
 * until one of these operations is performed.  After that,
43
 * performance is degraded by a constant factor that usually oscilates
44
 * in the range :math:`[1, 2)` depending on the operation and the
45
 * amount of flexible operations that have been performed.
46
 *
47
 * .. tip:: A `vector`_ can be converted to a `flex_vector`_ in
48
 *    constant time without any allocation.  This is so because the
49
 *    internal structure of a *vector* is a strict subset of the
50
 *    internal structure of a *flexible vector*.  You can take
51
 *    advantage of this property by creating normal vectors as long as
52
 *    the flexible operations are not needed, and convert later in
53
 *    your processing pipeline once and if these are needed.
54
 *
55
 * @endrst
56
 */
57
template <typename T,
58
          typename MemoryPolicy  = default_memory_policy,
59
          detail::rbts::bits_t B = default_bits,
60
          detail::rbts::bits_t BL =
61
              detail::rbts::derive_bits_leaf<T, MemoryPolicy, B>>
62
class flex_vector
63
{
64
    using impl_t = detail::rbts::rrbtree<T, MemoryPolicy, B, BL>;
65
66
    using move_t =
67
        std::integral_constant<bool, MemoryPolicy::use_transient_rvalues>;
68
69
public:
70
    static constexpr auto bits      = B;
71
    static constexpr auto bits_leaf = BL;
72
    using memory_policy             = MemoryPolicy;
73
74
    using value_type      = T;
75
    using reference       = const T&;
76
    using size_type       = detail::rbts::size_t;
77
    using difference_type = std::ptrdiff_t;
78
    using const_reference = const T&;
79
80
    using iterator = detail::rbts::rrbtree_iterator<T, MemoryPolicy, B, BL>;
81
    using const_iterator   = iterator;
82
    using reverse_iterator = std::reverse_iterator<iterator>;
83
84
    using transient_type = flex_vector_transient<T, MemoryPolicy, B, BL>;
85
86
    /*!
87
     * Returns the maximum theoretical size supported by the internal structure
88
     * given the current B, BL.
89
     */
90
1.67M
    constexpr static size_type max_size() { return impl_t::max_size(); }
91
92
    /*!
93
     * Default constructor.  It creates a flex_vector of `size() == 0`.
94
     * It does not allocate memory and its complexity is @f$ O(1) @f$.
95
     */
96
80.7k
    flex_vector() = default;
97
98
    /*!
99
     * Constructs a flex_vector containing the elements in `values`.
100
     */
101
    flex_vector(std::initializer_list<T> values)
102
        : impl_{impl_t::from_initializer_list(values)}
103
    {}
104
105
    /*!
106
     * Constructs a flex_vector containing the elements in the range
107
     * defined by the input iterator `first` and range sentinel `last`.
108
     */
109
    template <typename Iter,
110
              typename Sent,
111
              std::enable_if_t<detail::compatible_sentinel_v<Iter, Sent>,
112
                               bool> = true>
113
    flex_vector(Iter first, Sent last)
114
        : impl_{impl_t::from_range(first, last)}
115
    {}
116
117
    /*!
118
     * Constructs a vector containing the element `val` repeated `n`
119
     * times.
120
     */
121
    flex_vector(size_type n, T v = {})
122
        : impl_{impl_t::from_fill(n, v)}
123
    {}
124
125
    /*!
126
     * Default constructor.  It creates a flex_vector with the same
127
     * contents as `v`.  It does not allocate memory and is
128
     * @f$ O(1) @f$.
129
     */
130
    flex_vector(vector<T, MemoryPolicy, B, BL> v)
131
        : impl_{v.impl_.size,
132
                v.impl_.shift,
133
                v.impl_.root->inc(),
134
                v.impl_.tail->inc()}
135
    {}
136
137
    /*!
138
     * Returns an iterator pointing at the first element of the
139
     * collection. It does not allocate memory and its complexity is
140
     * @f$ O(1) @f$.
141
     */
142
    IMMER_NODISCARD iterator begin() const { return {impl_}; }
143
144
    /*!
145
     * Returns an iterator pointing just after the last element of the
146
     * collection. It does not allocate and its complexity is @f$ O(1) @f$.
147
     */
148
    IMMER_NODISCARD iterator end() const
149
    {
150
        return {impl_, typename iterator::end_t{}};
151
    }
152
153
    /*!
154
     * Returns an iterator that traverses the collection backwards,
155
     * pointing at the first element of the reversed collection. It
156
     * does not allocate memory and its complexity is @f$ O(1) @f$.
157
     */
158
    IMMER_NODISCARD reverse_iterator rbegin() const
159
    {
160
        return reverse_iterator{end()};
161
    }
162
163
    /*!
164
     * Returns an iterator that traverses the collection backwards,
165
     * pointing after the last element of the reversed collection. It
166
     * does not allocate memory and its complexity is @f$ O(1) @f$.
167
     */
168
    IMMER_NODISCARD reverse_iterator rend() const
169
    {
170
        return reverse_iterator{begin()};
171
    }
172
173
    /*!
174
     * Returns the number of elements in the container.  It does
175
     * not allocate memory and its complexity is @f$ O(1) @f$.
176
     */
177
3.91M
    IMMER_NODISCARD size_type size() const { return impl_.size; }
178
179
    /*!
180
     * Returns `true` if there are no elements in the container.  It
181
     * does not allocate memory and its complexity is @f$ O(1) @f$.
182
     */
183
    IMMER_NODISCARD bool empty() const { return impl_.size == 0; }
184
185
    /*!
186
     * Access the last element.
187
     */
188
    IMMER_NODISCARD const T& back() const { return impl_.back(); }
189
190
    /*!
191
     * Access the first element.
192
     */
193
    IMMER_NODISCARD const T& front() const { return impl_.front(); }
194
195
    /*!
196
     * Returns a `const` reference to the element at position `index`.
197
     * It is undefined when @f$ 0 index \geq size() @f$.  It does not
198
     * allocate memory and its complexity is *effectively* @f$ O(1)
199
     * @f$.
200
     */
201
    IMMER_NODISCARD reference operator[](size_type index) const
202
    {
203
        return impl_.get(index);
204
    }
205
206
    /*!
207
     * Returns a `const` reference to the element at position
208
     * `index`. It throws an `std::out_of_range` exception when @f$
209
     * index \geq size() @f$.  It does not allocate memory and its
210
     * complexity is *effectively* @f$ O(1) @f$.
211
     */
212
    reference at(size_type index) const { return impl_.get_check(index); }
213
214
    /*!
215
     * Returns whether the vectors are equal.
216
     */
217
    IMMER_NODISCARD bool operator==(const flex_vector& other) const
218
27.0k
    {
219
27.0k
        return impl_.equals(other.impl_);
220
27.0k
    }
221
    IMMER_NODISCARD bool operator!=(const flex_vector& other) const
222
    {
223
        return !(*this == other);
224
    }
225
226
    /*!
227
     * Returns a flex_vector with `value` inserted at the end.  It may
228
     * allocate memory and its complexity is *effectively* @f$ O(1) @f$.
229
     *
230
     * @rst
231
     *
232
     * **Example**
233
     *   .. literalinclude:: ../example/flex-vector/flex-vector.cpp
234
     *      :language: c++
235
     *      :dedent: 8
236
     *      :start-after: push-back/start
237
     *      :end-before:  push-back/end
238
     *
239
     * @endrst
240
     */
241
    IMMER_NODISCARD flex_vector push_back(value_type value) const&
242
261k
    {
243
261k
        return impl_.push_back(std::move(value));
244
261k
    }
245
246
    IMMER_NODISCARD decltype(auto) push_back(value_type value) &&
247
34.0k
    {
248
34.0k
        return push_back_move(move_t{}, std::move(value));
249
34.0k
    }
250
251
    /*!
252
     * Returns a flex_vector with `value` inserted at the front.  It may
253
     * allocate memory and its complexity is @f$ O(log(size)) @f$.
254
     *
255
     * @rst
256
     *
257
     * **Example**
258
     *   .. literalinclude:: ../example/flex-vector/flex-vector.cpp
259
     *      :language: c++
260
     *      :dedent: 8
261
     *      :start-after: push-front/start
262
     *      :end-before:  push-front/end
263
     *
264
     * @endrst
265
     */
266
    IMMER_NODISCARD flex_vector push_front(value_type value) const
267
    {
268
        return flex_vector{}.push_back(value) + *this;
269
    }
270
271
    /*!
272
     * Returns a flex_vector containing value `value` at position `index`.
273
     * Undefined for `index >= size()`.
274
     * It may allocate memory and its complexity is
275
     * *effectively* @f$ O(1) @f$.
276
     *
277
     * @rst
278
     *
279
     * **Example**
280
     *   .. literalinclude:: ../example/flex-vector/flex-vector.cpp
281
     *      :language: c++
282
     *      :dedent: 8
283
     *      :start-after: set/start
284
     *      :end-before:  set/end
285
     *
286
     * @endrst
287
     */
288
    IMMER_NODISCARD flex_vector set(size_type index, value_type value) const&
289
    {
290
        return impl_.assoc(index, std::move(value));
291
    }
292
293
    IMMER_NODISCARD decltype(auto) set(size_type index, value_type value) &&
294
    {
295
        return set_move(move_t{}, index, std::move(value));
296
    }
297
298
    /*!
299
     * Returns a vector containing the result of the expression
300
     * `fn((*this)[idx])` at position `idx`.
301
     * Undefined for `index >= size()`.
302
     * It may allocate memory and its complexity is
303
     * *effectively* @f$ O(1) @f$.
304
     *
305
     * @rst
306
     *
307
     * **Example**
308
     *   .. literalinclude:: ../example/flex-vector/flex-vector.cpp
309
     *      :language: c++
310
     *      :dedent: 8
311
     *      :start-after: update/start
312
     *      :end-before:  update/end
313
     *
314
     * @endrst
315
316
     */
317
    template <typename FnT>
318
    IMMER_NODISCARD flex_vector update(size_type index, FnT&& fn) const&
319
273k
    {
320
273k
        return impl_.update(index, std::forward<FnT>(fn));
321
273k
    }
322
323
    template <typename FnT>
324
    IMMER_NODISCARD decltype(auto) update(size_type index, FnT&& fn) &&
325
65.4k
    {
326
65.4k
        return update_move(move_t{}, index, std::forward<FnT>(fn));
327
65.4k
    }
328
329
    /*!
330
     * Returns a vector containing only the first `min(elems, size())`
331
     * elements. It may allocate memory and its complexity is
332
     * *effectively* @f$ O(1) @f$.
333
     *
334
     * @rst
335
     *
336
     * **Example**
337
     *   .. literalinclude:: ../example/flex-vector/flex-vector.cpp
338
     *      :language: c++
339
     *      :dedent: 8
340
     *      :start-after: take/start
341
     *      :end-before:  take/end
342
     *
343
     * @endrst
344
     */
345
    IMMER_NODISCARD flex_vector take(size_type elems) const&
346
27.5k
    {
347
27.5k
        return impl_.take(elems);
348
27.5k
    }
349
350
    IMMER_NODISCARD decltype(auto) take(size_type elems) &&
351
43.6k
    {
352
43.6k
        return take_move(move_t{}, elems);
353
43.6k
    }
354
355
    /*!
356
     * Returns a vector without the first `min(elems, size())`
357
     * elements. It may allocate memory and its complexity is
358
     * *effectively* @f$ O(1) @f$.
359
     *
360
     * @rst
361
     *
362
     * **Example**
363
     *   .. literalinclude:: ../example/flex-vector/flex-vector.cpp
364
     *      :language: c++
365
     *      :dedent: 8
366
     *      :start-after: drop/start
367
     *      :end-before:  drop/end
368
     *
369
     * @endrst
370
     */
371
    IMMER_NODISCARD flex_vector drop(size_type elems) const&
372
29.6k
    {
373
29.6k
        return impl_.drop(elems);
374
29.6k
    }
375
376
    IMMER_NODISCARD decltype(auto) drop(size_type elems) &&
377
43.6k
    {
378
43.6k
        return drop_move(move_t{}, elems);
379
43.6k
    }
380
381
    /*!
382
     * Concatenation operator. Returns a flex_vector with the contents
383
     * of `l` followed by those of `r`.  It may allocate memory
384
     * and its complexity is @f$ O(log(max(size_r, size_l))) @f$
385
     *
386
     * @rst
387
     *
388
     * **Example**
389
     *   .. literalinclude:: ../example/flex-vector/flex-vector.cpp
390
     *      :language: c++
391
     *      :dedent: 8
392
     *      :start-after: concat/start
393
     *      :end-before:  concat/end
394
     *
395
     * @endrst
396
     */
397
    IMMER_NODISCARD friend flex_vector operator+(const flex_vector& l,
398
                                                 const flex_vector& r)
399
990k
    {
400
990k
        return l.impl_.concat(r.impl_);
401
990k
    }
402
403
    IMMER_NODISCARD friend decltype(auto) operator+(flex_vector&& l,
404
                                                    const flex_vector& r)
405
557k
    {
406
557k
        return concat_move(move_t{}, std::move(l), r);
407
557k
    }
408
409
    IMMER_NODISCARD friend decltype(auto) operator+(const flex_vector& l,
410
                                                    flex_vector&& r)
411
4.19k
    {
412
4.19k
        return concat_move(move_t{}, l, std::move(r));
413
4.19k
    }
414
415
    IMMER_NODISCARD friend decltype(auto) operator+(flex_vector&& l,
416
                                                    flex_vector&& r)
417
27.5k
    {
418
27.5k
        return concat_move(move_t{}, std::move(l), std::move(r));
419
27.5k
    }
420
421
    /*!
422
     * Returns a flex_vector with the `value` inserted at index
423
     * `pos`. It may allocate memory and its complexity is @f$
424
     * O(log(size)) @f$
425
     *
426
     * @rst
427
     *
428
     * **Example**
429
     *   .. literalinclude:: ../example/flex-vector/flex-vector.cpp
430
     *      :language: c++
431
     *      :dedent: 8
432
     *      :start-after: insert/start
433
     *      :end-before:  insert/end
434
     *
435
     * @endrst
436
     */
437
    IMMER_NODISCARD flex_vector insert(size_type pos, T value) const&
438
22.0k
    {
439
22.0k
        return take(pos).push_back(std::move(value)) + drop(pos);
440
22.0k
    }
441
    IMMER_NODISCARD decltype(auto) insert(size_type pos, T value) &&
442
    {
443
        using std::move;
444
        auto rs = drop(pos);
445
        return std::move(*this).take(pos).push_back(std::move(value)) +
446
               std::move(rs);
447
    }
448
449
    IMMER_NODISCARD flex_vector insert(size_type pos, flex_vector value) const&
450
    {
451
        return take(pos) + std::move(value) + drop(pos);
452
    }
453
    IMMER_NODISCARD decltype(auto) insert(size_type pos, flex_vector value) &&
454
    {
455
        using std::move;
456
        auto rs = drop(pos);
457
        return std::move(*this).take(pos) + std::move(value) + std::move(rs);
458
    }
459
460
    /*!
461
     * Returns a flex_vector without the element at index `pos`. It
462
     * may allocate memory and its complexity is @f$ O(log(size)) @f$
463
     *
464
     * @rst
465
     *
466
     * **Example**
467
     *   .. literalinclude:: ../example/flex-vector/flex-vector.cpp
468
     *      :language: c++
469
     *      :dedent: 8
470
     *      :start-after: erase/start
471
     *      :end-before:  erase/end
472
     *
473
     * @endrst
474
     */
475
    IMMER_NODISCARD flex_vector erase(size_type pos) const&
476
3.36k
    {
477
3.36k
        return take(pos) + drop(pos + 1);
478
3.36k
    }
479
    IMMER_NODISCARD decltype(auto) erase(size_type pos) &&
480
    {
481
        auto rs = drop(pos + 1);
482
        return std::move(*this).take(pos) + std::move(rs);
483
    }
484
485
    IMMER_NODISCARD flex_vector erase(size_type pos, size_type lpos) const&
486
    {
487
        return lpos > pos ? take(pos) + drop(lpos) : *this;
488
    }
489
    IMMER_NODISCARD decltype(auto) erase(size_type pos, size_type lpos) &&
490
    {
491
        if (lpos > pos) {
492
            auto rs = drop(lpos);
493
            return std::move(*this).take(pos) + std::move(rs);
494
        } else {
495
            return std::move(*this);
496
        }
497
    }
498
499
    /*!
500
     * Returns an @a transient form of this container, an
501
     * `immer::flex_vector_transient`.
502
     */
503
    IMMER_NODISCARD transient_type transient() const& { return impl_; }
504
    IMMER_NODISCARD transient_type transient() && { return std::move(impl_); }
505
506
    /*!
507
     * Returns a value that can be used as identity for the container.  If two
508
     * values have the same identity, they are guaranteed to be equal and to
509
     * contain the same objects.  However, two equal containers are not
510
     * guaranteed to have the same identity.
511
     */
512
    std::pair<void*, void*> identity() const
513
    {
514
        return {impl_.root, impl_.tail};
515
    }
516
517
    // Semi-private
518
    const impl_t& impl() const { return impl_; }
519
520
#if IMMER_DEBUG_PRINT
521
    void debug_print(std::ostream& out = std::cerr) const
522
    {
523
        impl_.debug_print(out);
524
    }
525
#endif
526
527
private:
528
    friend transient_type;
529
530
    flex_vector(impl_t impl)
531
        : impl_(std::move(impl))
532
1.58M
    {
533
#if IMMER_DEBUG_PRINT
534
        // force the compiler to generate debug_print, so we can call
535
        // it from a debugger
536
        [](volatile auto) {}(&flex_vector::debug_print);
537
#endif
538
1.58M
    }
539
540
    flex_vector&& push_back_move(std::true_type, value_type value)
541
34.0k
    {
542
34.0k
        impl_.push_back_mut({}, std::move(value));
543
34.0k
        return std::move(*this);
544
34.0k
    }
545
    flex_vector push_back_move(std::false_type, value_type value)
546
    {
547
        return impl_.push_back(std::move(value));
548
    }
549
550
    flex_vector&& set_move(std::true_type, size_type index, value_type value)
551
    {
552
        impl_.assoc_mut({}, index, std::move(value));
553
        return std::move(*this);
554
    }
555
    flex_vector set_move(std::false_type, size_type index, value_type value)
556
    {
557
        return impl_.assoc(index, std::move(value));
558
    }
559
560
    template <typename Fn>
561
    flex_vector&& update_move(std::true_type, size_type index, Fn&& fn)
562
65.4k
    {
563
65.4k
        impl_.update_mut({}, index, std::forward<Fn>(fn));
564
65.4k
        return std::move(*this);
565
65.4k
    }
566
    template <typename Fn>
567
    flex_vector update_move(std::false_type, size_type index, Fn&& fn)
568
    {
569
        return impl_.update(index, std::forward<Fn>(fn));
570
    }
571
572
    flex_vector&& take_move(std::true_type, size_type elems)
573
43.6k
    {
574
43.6k
        impl_.take_mut({}, elems);
575
43.6k
        return std::move(*this);
576
43.6k
    }
577
    flex_vector take_move(std::false_type, size_type elems)
578
    {
579
        return impl_.take(elems);
580
    }
581
582
    flex_vector&& drop_move(std::true_type, size_type elems)
583
43.6k
    {
584
43.6k
        impl_.drop_mut({}, elems);
585
43.6k
        return std::move(*this);
586
43.6k
    }
587
    flex_vector drop_move(std::false_type, size_type elems)
588
    {
589
        return impl_.drop(elems);
590
    }
591
592
    static flex_vector&&
593
    concat_move(std::true_type, flex_vector&& l, const flex_vector& r)
594
557k
    {
595
557k
        concat_mut_l(l.impl_, {}, r.impl_);
596
557k
        return std::move(l);
597
557k
    }
598
    static flex_vector&&
599
    concat_move(std::true_type, const flex_vector& l, flex_vector&& r)
600
4.19k
    {
601
4.19k
        concat_mut_r(l.impl_, r.impl_, {});
602
4.19k
        return std::move(r);
603
4.19k
    }
604
    static flex_vector&&
605
    concat_move(std::true_type, flex_vector&& l, flex_vector&& r)
606
27.5k
    {
607
27.5k
        concat_mut_lr_l(l.impl_, {}, r.impl_, {});
608
27.5k
        return std::move(l);
609
27.5k
    }
610
    static flex_vector
611
    concat_move(std::false_type, const flex_vector& l, const flex_vector& r)
612
    {
613
        return l.impl_.concat(r.impl_);
614
    }
615
616
    impl_t impl_ = {};
617
};
618
619
static_assert(std::is_nothrow_move_constructible<flex_vector<int>>::value,
620
              "flex_vector is not nothrow move constructible");
621
static_assert(std::is_nothrow_move_assignable<flex_vector<int>>::value,
622
              "flex_vector is not nothrow move assignable");
623
624
} // namespace immer