sparrow 0.9.0
Loading...
Searching...
No Matches
buffer.hpp
Go to the documentation of this file.
1// Copyright 2024 Man Group Operations Limited
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7// http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15#pragma once
16
17#include <algorithm>
18#include <concepts>
19#include <iterator>
20#include <memory>
21#include <ranges>
22#include <stdexcept>
23#include <type_traits>
24
29
30#if not defined(SPARROW_BUFFER_GROWTH_FACTOR)
31# define SPARROW_BUFFER_GROWTH_FACTOR 2
32#endif
33
34namespace sparrow
35{
36 template <typename T>
37 concept is_buffer_view = requires(T t) { typename T::is_buffer_view; };
38
46 template <class T>
48 {
49 protected:
50
52 using alloc_traits = std::allocator_traits<allocator_type>;
53 using pointer = typename alloc_traits::pointer;
54 using size_type = typename alloc_traits::size_type;
55
57 {
58 pointer p_begin = nullptr;
59 pointer p_end = nullptr;
61
62 buffer_data() = default;
63 constexpr buffer_data(buffer_data&&) noexcept;
64 constexpr buffer_data& operator=(buffer_data&&) noexcept;
65 };
66
67 buffer_base() = default;
68
69 template <allocator A>
70 constexpr buffer_base(const A& a) noexcept;
71
72 template <allocator A = allocator_type>
73 constexpr buffer_base(size_type n, const A& a = A());
74
75 template <allocator A = allocator_type>
76 constexpr buffer_base(pointer p, size_type n, const A& a = A());
77
79
80 buffer_base(buffer_base&&) noexcept = default;
81
82 template <allocator A>
83 constexpr buffer_base(buffer_base&& rhs, const A& a);
84
85 [[nodiscard]] constexpr allocator_type& get_allocator() noexcept;
86 [[nodiscard]] constexpr const allocator_type& get_allocator() const noexcept;
87 [[nodiscard]] constexpr buffer_data& get_data() noexcept;
88 [[nodiscard]] constexpr const buffer_data& get_data() const noexcept;
89
91 constexpr void deallocate(pointer p, size_type n);
92 constexpr void create_storage(size_type n);
93 constexpr void assign_storage(pointer p, size_type n, size_type cap);
94
95 private:
96
97 allocator_type m_alloc;
98 buffer_data m_data;
99 };
100
110 template <class T>
111 class buffer : private buffer_base<T>
112 {
113 using base_type = buffer_base<T>;
114 using alloc_traits = typename base_type::alloc_traits;
115
116 static_assert(
117 std::same_as<std::remove_cvref_t<T>, T>,
118 "buffer must have a non-const, non-volatile, non-reference value_type"
119 );
120
121 public:
122
124 using value_type = T;
127 using pointer = typename alloc_traits::pointer;
128 using const_pointer = typename alloc_traits::const_pointer;
129 using size_type = typename alloc_traits::size_type;
130 using difference_type = typename alloc_traits::difference_type;
133 using reverse_iterator = std::reverse_iterator<iterator>;
134 using const_reverse_iterator = std::reverse_iterator<const_iterator>;
135
136 buffer() = default;
137
138 template <class A>
139 requires(not std::same_as<A, buffer<T>> and allocator<A>)
140 constexpr explicit buffer(const A& a)
141 : base_type(a)
142 {
143 }
144
145 template <allocator A = allocator_type>
146 constexpr explicit buffer(size_type n, const A& a = A());
147
148 template <allocator A = allocator_type>
149 constexpr buffer(size_type n, const value_type& v, const A& a = A());
150
151 template <allocator A = allocator_type>
152 constexpr buffer(pointer p, size_type n, const A& a = A());
153
154 template <allocator A = allocator_type>
155 constexpr buffer(std::initializer_list<value_type> init, const A& a = A());
156
157 template <class It, allocator A = allocator_type>
158 constexpr buffer(It first, It last, const A& a = A());
159
160 template <std::ranges::input_range Range, allocator A = allocator_type>
161 requires(std::same_as<std::ranges::range_value_t<Range>, T> && !is_buffer_view<Range>)
162 constexpr buffer(const Range& range, const A& a = A());
163
165
166 buffer(const buffer& rhs);
167
168 template <allocator A>
169 buffer(const buffer& rhs, const A& a);
170
171 buffer(buffer&& rhs) noexcept = default;
172
173 template <allocator A>
174 buffer(buffer&& rhs, const A& a);
175
176 constexpr buffer& operator=(const buffer& rhs);
177 constexpr buffer& operator=(buffer&& rhs);
178 constexpr buffer& operator=(std::initializer_list<value_type> init);
179
180 // Element access
181
182 [[nodiscard]] constexpr reference operator[](size_type i);
183 [[nodiscard]] constexpr const_reference operator[](size_type i) const;
184
185 [[nodiscard]] constexpr reference front();
186 [[nodiscard]] constexpr const_reference front() const;
187
188 [[nodiscard]] constexpr reference back();
189 [[nodiscard]] constexpr const_reference back() const;
190
191 // TODO: make this non template and make a buffer_caster class
192 template <class U = T>
193 [[nodiscard]] constexpr U* data() noexcept;
194
195 // TODO: make this non template and make a buffer_caster class
196 template <class U = T>
197 [[nodiscard]] constexpr const U* data() const noexcept;
198
199 // Iterators
200
201 [[nodiscard]] constexpr iterator begin() noexcept;
202 [[nodiscard]] constexpr iterator end() noexcept;
203
204 [[nodiscard]] constexpr const_iterator begin() const noexcept;
205 [[nodiscard]] constexpr const_iterator end() const noexcept;
206
207 [[nodiscard]] constexpr const_iterator cbegin() const noexcept;
208 [[nodiscard]] constexpr const_iterator cend() const noexcept;
209
210 [[nodiscard]] constexpr reverse_iterator rbegin() noexcept;
211 [[nodiscard]] constexpr reverse_iterator rend() noexcept;
212
213 [[nodiscard]] constexpr const_reverse_iterator rbegin() const noexcept;
214 [[nodiscard]] constexpr const_reverse_iterator rend() const noexcept;
215
216 [[nodiscard]] constexpr const_reverse_iterator crbegin() const noexcept;
217 [[nodiscard]] constexpr const_reverse_iterator crend() const noexcept;
218 [[nodiscard]]
219 // Capacity
220
221 [[nodiscard]] constexpr bool
222 empty() const noexcept;
223 [[nodiscard]] constexpr size_type capacity() const noexcept;
224 [[nodiscard]] constexpr size_type size() const noexcept;
225 [[nodiscard]] constexpr size_type max_size() const noexcept;
226 constexpr void reserve(size_type new_cap);
227 constexpr void shrink_to_fit();
228
229 // Modifiers
230
231 constexpr void clear();
232
233 constexpr iterator insert(const_iterator pos, const T& value);
234 constexpr iterator insert(const_iterator pos, T&& value);
235 constexpr iterator insert(const_iterator pos, size_type count, const T& value);
236 template <mpl::iterator_of_type<T> InputIt>
237 constexpr iterator insert(const_iterator pos, InputIt first, InputIt last);
238 template <std::ranges::input_range R>
239 requires std::same_as<std::ranges::range_value_t<R>, T>
240 constexpr iterator insert(const_iterator pos, R&& range);
241 constexpr iterator insert(const_iterator pos, std::initializer_list<T> ilist);
242
243 template <class... Args>
244 constexpr iterator emplace(const_iterator pos, Args&&... args);
245
248
249 constexpr void push_back(const T& value);
250 constexpr void push_back(T&& value);
251
252 constexpr void pop_back();
253
254 constexpr void resize(size_type new_size);
255 constexpr void resize(size_type new_size, const value_type& value);
256 constexpr void swap(buffer& rhs) noexcept;
257
258 private:
259
260 using base_type::get_allocator;
261 using base_type::get_data;
262
263 template <class F>
264 constexpr void resize_impl(size_type new_size, F&& initializer);
265
266 template <class It>
267 constexpr void assign_range_impl(It first, It last, std::forward_iterator_tag);
268
269 constexpr void erase_at_end(pointer p);
270
271 template <class It>
272 constexpr pointer allocate_and_copy(size_type n, It first, It last);
273
274 constexpr void reserve_with_growth_factor(size_type new_cap);
275
276 // The following methods are static because:
277 // - they accept an allocator argument, and do not depend on
278 // the state of buffer
279 // - taking an allocator argument instead of relying on get_allocator
280 // will make it easier to support allocators that propagate
281 // on copy / move, as these methods will be called with allocators
282 // from different instances of buffers.
283
284 static constexpr size_type check_init_length(size_type n, const allocator_type& a);
285
286 [[nodiscard]] static constexpr size_type max_size_impl(const allocator_type& a) noexcept;
287
288 [[nodiscard]] static constexpr pointer
289 default_initialize(pointer begin, size_type n, allocator_type& a);
290
291 static constexpr pointer
292 fill_initialize(pointer begin, size_type n, const value_type& v, allocator_type& a);
293
294 template <class It>
295 static constexpr pointer copy_initialize(It first, It last, pointer begin, allocator_type& a);
296
297 static constexpr void destroy(pointer first, pointer last, allocator_type& a);
298 };
299
300 template <class T>
301 constexpr bool operator==(const buffer<T>& lhs, const buffer<T>& rhs) noexcept;
302
303 /******************************
304 * buffer_base implementation *
305 ******************************/
306
307 template <class T>
308 constexpr buffer_base<T>::buffer_data::buffer_data(buffer_data&& rhs) noexcept
309 : p_begin(rhs.p_begin)
310 , p_end(rhs.p_end)
312 {
313 rhs.p_begin = nullptr;
314 rhs.p_end = nullptr;
315 rhs.p_storage_end = nullptr;
316 }
317
318 template <class T>
320 {
321 std::swap(p_begin, rhs.p_begin);
322 std::swap(p_end, rhs.p_end);
323 std::swap(p_storage_end, rhs.p_storage_end);
324 return *this;
325 }
326
327 template <class T>
328 template <allocator A>
329 constexpr buffer_base<T>::buffer_base(const A& a) noexcept
330 : m_alloc(a)
331 {
332 }
333
334 template <class T>
335 template <allocator A>
336 constexpr buffer_base<T>::buffer_base(size_type n, const A& a)
337 : m_alloc(a)
338 {
340 }
341
342 template <class T>
343 template <allocator A>
345 : m_alloc(a)
346 {
347 SPARROW_ASSERT_TRUE((p != nullptr) || (p == nullptr && n == 0));
348 assign_storage(p, n, n);
349 }
350
351 template <class T>
353 {
354 deallocate(m_data.p_begin, static_cast<size_type>(m_data.p_storage_end - m_data.p_begin));
355 }
356
357 template <class T>
358 template <allocator A>
359 constexpr buffer_base<T>::buffer_base(buffer_base&& rhs, const A& a)
360 : m_alloc(a)
361 , m_data(std::move(rhs.m_data))
362 {
363 }
364
365 template <class T>
366 constexpr auto buffer_base<T>::get_allocator() noexcept -> allocator_type&
367 {
368 return m_alloc;
369 }
370
371 template <class T>
372 constexpr auto buffer_base<T>::get_allocator() const noexcept -> const allocator_type&
373 {
374 return m_alloc;
375 }
376
377 template <class T>
378 constexpr auto buffer_base<T>::get_data() noexcept -> buffer_data&
379 {
380 return m_data;
381 }
382
383 template <class T>
384 constexpr auto buffer_base<T>::get_data() const noexcept -> const buffer_data&
385 {
386 return m_data;
387 }
388
389 template <class T>
391 {
392 return alloc_traits::allocate(m_alloc, n);
393 }
394
395 template <class T>
397 {
398 alloc_traits::deallocate(m_alloc, p, n);
399 }
400
401 template <class T>
403 {
404 m_data.p_begin = allocate(n);
405 m_data.p_end = m_data.p_begin + n;
406 m_data.p_storage_end = m_data.p_end;
407 }
408
409 template <class T>
411 {
412 SPARROW_ASSERT_TRUE(n <= cap);
413 m_data.p_begin = p;
414 m_data.p_end = p + n;
415 m_data.p_storage_end = p + cap;
416 }
417
418 /*************************
419 * buffer implementation *
420 *************************/
421
422 template <class T>
423 template <allocator A>
424 constexpr buffer<T>::buffer(size_type n, const A& a)
425 : base_type(check_init_length(n, a), a)
426 {
427 get_data().p_end = default_initialize(get_data().p_begin, n, get_allocator());
428 }
429
430 template <class T>
431 template <allocator A>
432 constexpr buffer<T>::buffer(size_type n, const value_type& v, const A& a)
433 : base_type(check_init_length(n, a), a)
434 {
435 get_data().p_end = fill_initialize(get_data().p_begin, n, v, get_allocator());
436 }
437
438 template <class T>
439 template <allocator A>
440 constexpr buffer<T>::buffer(pointer p, size_type n, const A& a)
441 : base_type(p, check_init_length(n, a), a)
442 {
443 }
444
445 template <class T>
446 template <allocator A>
447 constexpr buffer<T>::buffer(std::initializer_list<value_type> init, const A& a)
448 : base_type(check_init_length(init.size(), a), a)
449 {
450 get_data().p_end = copy_initialize(init.begin(), init.end(), get_data().p_begin, get_allocator());
451 }
452
453 template <class T>
454 template <class It, allocator A>
455 constexpr buffer<T>::buffer(It first, It last, const A& a)
456 : base_type(check_init_length(static_cast<size_type>(std::distance(first, last)), a), a)
457 {
458 get_data().p_end = copy_initialize(first, last, get_data().p_begin, get_allocator());
459 }
460
461 template <class T>
462 template <std::ranges::input_range Range, allocator A>
463 requires(std::same_as<std::ranges::range_value_t<Range>, T> && !is_buffer_view<Range>)
464 constexpr buffer<T>::buffer(const Range& range, const A& a)
465 : base_type(check_init_length(static_cast<size_type>(std::ranges::size(range)), a), a)
466 {
467 get_data().p_end = copy_initialize(
468 std::ranges::begin(range),
469 std::ranges::end(range),
470 get_data().p_begin,
471 get_allocator()
472 );
473 }
474
475 template <class T>
477 {
478 destroy(get_data().p_begin, get_data().p_end, get_allocator());
479 }
480
481 template <class T>
483 : base_type(rhs.get_allocator())
484 {
485 if (rhs.get_data().p_begin != nullptr)
486 {
487 this->create_storage(rhs.size());
488 get_data().p_end = copy_initialize(rhs.begin(), rhs.end(), get_data().p_begin, get_allocator());
489 }
490 }
491
492 template <class T>
493 template <allocator A>
494 buffer<T>::buffer(const buffer& rhs, const A& a)
495 : base_type(a)
496 {
497 if (rhs.get_data().p_begin != nullptr)
498 {
499 this->create_storage(rhs.size());
500 get_data().p_end = copy_initialize(rhs.begin(), rhs.end(), get_data().p_begin, get_allocator());
501 }
502 }
503
504 template <class T>
505 template <allocator A>
506 buffer<T>::buffer(buffer&& rhs, const A& a)
507 : base_type(a)
508 {
509 if (rhs.get_allocator() == get_allocator())
510 {
511 get_data() = std::move(rhs.m_data);
512 }
513 else if (!rhs.empty())
514 {
515 if (rhs.get_data().p_begin != nullptr)
516 {
517 this->create_storage(rhs.size());
518 get_data().p_end = copy_initialize(rhs.begin(), rhs.end(), get_data().p_begin, get_allocator());
519 }
520 rhs.clear();
521 }
522 }
523
524 template <class T>
525 constexpr buffer<T>& buffer<T>::operator=(const buffer& rhs)
526 {
527 if (std::addressof(rhs) != this)
528 {
529 if (rhs.get_data().p_begin != nullptr)
530 {
531 // We assume that any_allocator never propagates on assign
532 assign_range_impl(rhs.get_data().p_begin, rhs.get_data().p_end, std::random_access_iterator_tag());
533 }
534 else
535 {
536 clear();
537 this->deallocate(
538 get_data().p_begin,
539 static_cast<size_type>(get_data().p_storage_end - get_data().p_begin)
540 );
541 this->assign_storage(nullptr, 0, 0);
542 }
543 }
544 return *this;
545 }
546
547 template <class T>
549 {
550 if (get_allocator() == rhs.get_allocator())
551 {
552 get_data() = std::move(rhs.get_data());
553 }
554 else
555 {
556 if (rhs.get_data().p_begin != nullptr)
557 {
558 assign_range_impl(
559 std::make_move_iterator(rhs.begin()),
560 std::make_move_iterator(rhs.end()),
561 std::random_access_iterator_tag()
562 );
563 }
564 else
565 {
566 clear();
567 }
568
569 rhs.clear();
570 }
571 return *this;
572 }
573
574 template <class T>
575 constexpr buffer<T>& buffer<T>::operator=(std::initializer_list<value_type> init)
576 {
577 assign_range_impl(
578 std::make_move_iterator(init.begin()),
579 std::make_move_iterator(init.end()),
580 std::random_access_iterator_tag()
581 );
582 return *this;
583 }
584
585 template <class T>
587 {
589 return get_data().p_begin[i];
590 }
591
592 template <class T>
594 {
596 return get_data().p_begin[i];
597 }
598
599 template <class T>
600 constexpr auto buffer<T>::front() -> reference
601 {
603 return *(get_data().p_begin);
604 }
605
606 template <class T>
607 constexpr auto buffer<T>::front() const -> const_reference
608 {
610 return *(get_data().p_begin);
611 }
612
613 template <class T>
614 constexpr auto buffer<T>::back() -> reference
615 {
617 return *(get_data().p_end - 1);
618 }
619
620 template <class T>
621 constexpr auto buffer<T>::back() const -> const_reference
622 {
624 return *(get_data().p_end - 1);
625 }
626
627 template <class T>
628 template <class U>
629 constexpr U* buffer<T>::data() noexcept
630 {
631#if defined(__GNUC__)
632# pragma GCC diagnostic push
633# pragma GCC diagnostic ignored "-Wcast-align"
634#endif
635 return reinterpret_cast<U*>(get_data().p_begin);
636#if defined(__GNUC__)
637# pragma GCC diagnostic pop
638#endif
639 }
640
641 template <class T>
642 template <class U>
643 constexpr const U* buffer<T>::data() const noexcept
644 {
645#if defined(__GNUC__)
646# pragma GCC diagnostic push
647# pragma GCC diagnostic ignored "-Wcast-align"
648#endif
649 return reinterpret_cast<U*>(get_data().p_begin);
650#if defined(__GNUC__)
651# pragma GCC diagnostic pop
652#endif
653 }
654
655 template <class T>
656 constexpr auto buffer<T>::begin() noexcept -> iterator
657 {
658 return make_pointer_iterator(get_data().p_begin);
659 }
660
661 template <class T>
662 constexpr auto buffer<T>::end() noexcept -> iterator
663 {
664 return make_pointer_iterator(get_data().p_end);
665 }
666
667 template <class T>
668 constexpr auto buffer<T>::begin() const noexcept -> const_iterator
669 {
670 return cbegin();
671 }
672
673 template <class T>
674 constexpr auto buffer<T>::end() const noexcept -> const_iterator
675 {
676 return cend();
677 }
678
679 template <class T>
680 constexpr auto buffer<T>::cbegin() const noexcept -> const_iterator
681 {
682 return make_pointer_iterator(const_pointer(get_data().p_begin));
683 }
684
685 template <class T>
686 constexpr auto buffer<T>::cend() const noexcept -> const_iterator
687 {
688 return make_pointer_iterator(const_pointer(get_data().p_end));
689 }
690
691 template <class T>
692 constexpr auto buffer<T>::rbegin() noexcept -> reverse_iterator
693 {
694 return reverse_iterator(end());
695 }
696
697 template <class T>
698 constexpr auto buffer<T>::rend() noexcept -> reverse_iterator
699 {
700 return reverse_iterator(begin());
701 }
702
703 template <class T>
704 constexpr auto buffer<T>::rbegin() const noexcept -> const_reverse_iterator
705 {
706 return crbegin();
707 }
708
709 template <class T>
710 constexpr auto buffer<T>::rend() const noexcept -> const_reverse_iterator
711 {
712 return crend();
713 }
714
715 template <class T>
716 constexpr auto buffer<T>::crbegin() const noexcept -> const_reverse_iterator
717 {
718 return const_reverse_iterator(end());
719 }
720
721 template <class T>
722 constexpr auto buffer<T>::crend() const noexcept -> const_reverse_iterator
723 {
725 }
726
727 template <class T>
728 constexpr bool buffer<T>::empty() const noexcept
729 {
730 return get_data().p_begin == get_data().p_end;
731 }
732
733 template <class T>
734 constexpr auto buffer<T>::capacity() const noexcept -> size_type
735 {
736 return static_cast<size_type>(get_data().p_storage_end - get_data().p_begin);
737 }
738
739 template <class T>
740 constexpr auto buffer<T>::size() const noexcept -> size_type
741 {
742 return static_cast<size_type>(get_data().p_end - get_data().p_begin);
743 }
744
745 template <class T>
746 constexpr auto buffer<T>::max_size() const noexcept -> size_type
747 {
748 return max_size_impl(get_allocator());
749 }
750
751 template <class T>
752 constexpr void buffer<T>::reserve(size_type new_cap)
753 {
754 if (new_cap > max_size())
755 {
756 throw std::length_error("buffer::reserve called with new_cap > max_size()");
757 }
758 if (new_cap > capacity())
759 {
760 if (data() == nullptr)
761 {
762 this->create_storage(new_cap);
763 get_data().p_end = get_data().p_begin;
764 }
765 else
766 {
767 const size_type old_size = size();
768 pointer tmp = allocate_and_copy(
769 new_cap,
770 std::make_move_iterator(get_data().p_begin),
771 std::make_move_iterator(get_data().p_end)
772 );
773 destroy(get_data().p_begin, get_data().p_end, get_allocator());
774 this->deallocate(
775 get_data().p_begin,
776 static_cast<size_type>(get_data().p_storage_end - get_data().p_begin)
777 );
778 this->assign_storage(tmp, old_size, new_cap);
779 }
780 }
781 }
782
783 template <class T>
784 constexpr void buffer<T>::reserve_with_growth_factor(size_type new_cap)
785 {
786 if (new_cap > capacity())
787 {
788 reserve(new_cap * SPARROW_BUFFER_GROWTH_FACTOR);
789 }
790 }
791
792 template <class T>
794 {
795 if (capacity() != size())
796 {
797 buffer(std::make_move_iterator(begin()), std::make_move_iterator(end()), get_allocator()).swap(*this);
798 }
799 }
800
801 template <class T>
802 constexpr void buffer<T>::clear()
803 {
804 if (get_data().p_begin != nullptr)
805 {
806 erase_at_end(get_data().p_begin);
807 }
808 }
809
810 template <class T>
811 constexpr auto buffer<T>::insert(const_iterator pos, const T& value) -> iterator
812 {
813 SPARROW_ASSERT_TRUE(cbegin() <= pos);
814 SPARROW_ASSERT_TRUE(pos <= cend());
815 return emplace(pos, value);
816 }
817
818 template <class T>
819 constexpr auto buffer<T>::insert(const_iterator pos, T&& value) -> iterator
820 {
821 SPARROW_ASSERT_TRUE(cbegin() <= pos);
822 SPARROW_ASSERT_TRUE(pos <= cend());
823 return emplace(pos, std::move(value));
824 }
825
826 template <class T>
827 constexpr auto buffer<T>::insert(const_iterator pos, size_type count, const T& value) -> iterator
828 {
829 SPARROW_ASSERT_TRUE(cbegin() <= pos);
830 SPARROW_ASSERT_TRUE(pos <= cend());
831
832 const difference_type offset = std::distance(cbegin(), pos);
833 if (count != 0)
834 {
835 reserve_with_growth_factor(size() + count);
836 const iterator it = std::next(begin(), offset);
837 std::move_backward(it, end(), std::next(end(), static_cast<difference_type>(count)));
838 std::fill_n(it, count, value);
839 get_data().p_end += count;
840 }
841 return std::next(begin(), offset);
842 }
843
844 template <typename T>
845 struct is_move_iterator : std::false_type
846 {
847 };
848
849 template <typename Iterator>
850 struct is_move_iterator<std::move_iterator<Iterator>> : std::true_type
851 {
852 };
853
854 template <typename T>
856
857 template <class T>
858 template <mpl::iterator_of_type<T> InputIt>
859 constexpr auto buffer<T>::insert(const_iterator pos, InputIt first, InputIt last) -> iterator
860 {
861 SPARROW_ASSERT_TRUE(cbegin() <= pos && pos <= cend());
862 const difference_type num_elements = std::distance(first, last);
863 const size_type new_size = size() + static_cast<size_type>(num_elements);
864 const difference_type offset = std::distance(cbegin(), pos);
865 const size_type old_size = size();
866 reserve_with_growth_factor(new_size);
867 resize(new_size);
868 const iterator new_pos = std::next(begin(), offset);
869 const iterator end_it = std::next(begin(), static_cast<difference_type>(old_size));
870 std::move_backward(new_pos, end_it, end());
871 if constexpr (is_move_iterator_v<InputIt>)
872 {
873 std::uninitialized_move(first, last, new_pos);
874 }
875 else
876 {
877 std::uninitialized_copy(first, last, new_pos);
878 }
879 return new_pos;
880 }
881
882 template <class T>
883 template <std::ranges::input_range R>
884 requires std::same_as<std::ranges::range_value_t<R>, T>
885 constexpr auto buffer<T>::insert(const_iterator pos, R&& range) -> iterator
886 {
887 SPARROW_ASSERT_TRUE(cbegin() <= pos);
888 SPARROW_ASSERT_TRUE(pos <= cend());
889 return insert(pos, std::ranges::begin(range), std::ranges::end(range));
890 }
891
892 template <class T>
893 constexpr auto buffer<T>::insert(const_iterator pos, std::initializer_list<T> ilist) -> iterator
894 {
895 SPARROW_ASSERT_TRUE(cbegin() <= pos);
896 SPARROW_ASSERT_TRUE(pos <= cend());
897 return insert(pos, ilist.begin(), ilist.end());
898 }
899
900 template <class T>
901 template <class... Args>
903 {
904 SPARROW_ASSERT_TRUE(cbegin() <= pos);
905 SPARROW_ASSERT_TRUE(pos <= cend());
906 const difference_type offset = std::distance(cbegin(), pos);
907 reserve_with_growth_factor(size() + 1);
908 pointer p = get_data().p_begin + offset;
909 if (p != get_data().p_end)
910 {
911 alloc_traits::construct(get_allocator(), get_data().p_end, std::move(*(get_data().p_end - 1)));
912 std::move_backward(p, get_data().p_end - 1, get_data().p_end);
913 alloc_traits::construct(get_allocator(), p, std::forward<Args>(args)...);
914 }
915 else
916 {
917 alloc_traits::construct(get_allocator(), get_data().p_end, std::forward<Args>(args)...);
918 }
919 ++get_data().p_end;
920 return iterator(p);
921 }
922
923 template <class T>
925 {
926 SPARROW_ASSERT_TRUE(cbegin() <= pos);
927 SPARROW_ASSERT_TRUE(pos < cend());
928 return erase(pos, pos + 1);
929 }
930
931 template <class T>
933 {
934 SPARROW_ASSERT_TRUE(first < last);
935 SPARROW_ASSERT_TRUE(cbegin() <= first);
936 SPARROW_ASSERT_TRUE(last <= cend());
937 const difference_type offset = std::distance(cbegin(), first);
938 const difference_type len = std::distance(first, last);
939 pointer p = get_data().p_begin + offset;
940 erase_at_end(std::move(p + len, get_data().p_end, p));
941 return iterator(p);
942 }
943
944 template <class T>
945 constexpr void buffer<T>::push_back(const T& value)
946 {
947 emplace(cend(), value);
948 }
949
950 template <class T>
951 constexpr void buffer<T>::push_back(T&& value)
952 {
953 emplace(cend(), std::move(value));
954 }
955
956 template <class T>
957 constexpr void buffer<T>::pop_back()
958 {
960 destroy(get_allocator(), get_data().p_end - 1);
961 --get_data().p_end;
962 }
963
964 template <class T>
965 constexpr void buffer<T>::resize(size_type new_size)
966 {
967 resize_impl(
968 new_size,
969 [this](size_type nb_init)
970 {
971 get_data().p_end = default_initialize(get_data().p_end, nb_init, get_allocator());
972 }
973 );
974 }
975
976 template <class T>
977 constexpr void buffer<T>::resize(size_type new_size, const value_type& value)
978 {
979 resize_impl(
980 new_size,
981 [this, &value](size_type nb_init)
982 {
983 get_data().p_end = fill_initialize(get_data().p_end, nb_init, value, get_allocator());
984 }
985 );
986 }
987
988 template <class T>
989 constexpr void buffer<T>::swap(buffer& rhs) noexcept
990 {
991 std::swap(this->get_data(), rhs.get_data());
992 }
993
994 template <class T>
995 template <class F>
996 constexpr void buffer<T>::resize_impl(size_type new_size, F&& initializer)
997 {
998 if (new_size > size())
999 {
1000 const std::size_t nb_init = new_size - size();
1001 if (new_size <= capacity())
1002 {
1003 initializer(nb_init);
1004 }
1005 else
1006 {
1007 reserve(new_size);
1008 initializer(nb_init);
1009 }
1010 }
1011 else if (new_size < size())
1012 {
1013 erase_at_end(get_data().p_begin + new_size);
1014 }
1015 }
1016
1017 template <class T>
1018 template <class It>
1019 constexpr void buffer<T>::assign_range_impl(It first, It last, std::forward_iterator_tag)
1020 {
1021 const size_type sz = size();
1022 const size_type len = static_cast<size_type>(std::distance(first, last));
1023 if (len > capacity())
1024 {
1025 check_init_length(len, get_allocator());
1026 pointer p = allocate_and_copy(len, first, last);
1027 destroy(get_data().p_begin, get_data().p_end, get_allocator());
1028 this->deallocate(get_data().p_begin, capacity());
1029 this->assign_storage(p, len, len);
1030 }
1031 else if (sz >= len)
1032 {
1033 pointer p = std::copy(first, last, get_data().p_begin);
1034 erase_at_end(p);
1035 }
1036 else
1037 {
1038 It mid = first;
1039 std::advance(mid, sz);
1040 std::copy(first, mid, get_data().p_begin);
1041 get_data().p_end = copy_initialize(mid, last, get_data().p_end, get_allocator());
1042 }
1043 }
1044
1045 template <class T>
1046 constexpr void buffer<T>::erase_at_end(pointer p)
1047 {
1048 destroy(p, get_data().p_end, get_allocator());
1049 get_data().p_end = p;
1050 }
1051
1052 template <class T>
1053 template <class It>
1054 constexpr auto buffer<T>::allocate_and_copy(size_type n, It first, It last) -> pointer
1055 {
1056 pointer p = this->allocate(n);
1057 try
1058 {
1059 copy_initialize(first, last, p, get_allocator());
1060 }
1061 catch (...)
1062 {
1063 this->deallocate(p, n);
1064 throw;
1065 }
1066 return p;
1067 }
1068
1069 template <class T>
1070 constexpr auto buffer<T>::check_init_length(size_type n, const allocator_type& a) -> size_type
1071 {
1072 if (n > max_size_impl(a))
1073 {
1074 throw std::length_error("cannot create buffer larger than max_size()");
1075 }
1076 return n;
1077 }
1078
1079 template <class T>
1080 constexpr auto buffer<T>::max_size_impl(const allocator_type& a) noexcept -> size_type
1081 {
1082 const size_type diff_max = static_cast<size_type>(std::numeric_limits<difference_type>::max());
1083 const size_type alloc_max = std::allocator_traits<allocator_type>::max_size(a);
1084 return (std::min) (diff_max, alloc_max);
1085 }
1086
1087 template <class T>
1088 constexpr auto buffer<T>::default_initialize(pointer begin, size_type n, allocator_type& a) -> pointer
1089 {
1090 pointer current = begin;
1091 for (; n > 0; --n, ++current)
1092 {
1093 alloc_traits::construct(a, current);
1094 }
1095 return current;
1096 }
1097
1098 template <class T>
1099 constexpr auto
1100 buffer<T>::fill_initialize(pointer begin, size_type n, const value_type& v, allocator_type& a) -> pointer
1101 {
1102 pointer current = begin;
1103 for (; n > 0; --n, ++current)
1104 {
1105 alloc_traits::construct(a, current, v);
1106 }
1107 return current;
1108 }
1109
1110 template <class T>
1111 template <class It>
1112 constexpr auto buffer<T>::copy_initialize(It first, It last, pointer begin, allocator_type& a) -> pointer
1113 {
1114 pointer current = begin;
1115 for (; first != last; ++first, ++current)
1116 {
1117 alloc_traits::construct(a, current, *first);
1118 }
1119 return current;
1120 }
1121
1122 template <class T>
1123 constexpr void buffer<T>::destroy(pointer first, pointer last, allocator_type& a)
1124 {
1125 SPARROW_ASSERT_TRUE(first <= last);
1126 for (; first != last; ++first)
1127 {
1128 alloc_traits::destroy(a, first);
1129 }
1130 }
1131
1132 template <class T>
1133 constexpr bool operator==(const buffer<T>& lhs, const buffer<T>& rhs) noexcept
1134 {
1135 return lhs.size() == rhs.size() && std::equal(lhs.begin(), lhs.end(), rhs.begin());
1136 }
1137}
#define SPARROW_BUFFER_GROWTH_FACTOR
Definition buffer.hpp:31
constexpr pointer allocate(size_type n)
Definition buffer.hpp:390
typename alloc_traits::pointer pointer
Definition buffer.hpp:53
any_allocator< T > allocator_type
Definition buffer.hpp:51
constexpr void create_storage(size_type n)
Definition buffer.hpp:402
constexpr void deallocate(pointer p, size_type n)
Definition buffer.hpp:396
constexpr buffer_data & get_data() noexcept
Definition buffer.hpp:378
constexpr void assign_storage(pointer p, size_type n, size_type cap)
Definition buffer.hpp:410
std::allocator_traits< allocator_type > alloc_traits
Definition buffer.hpp:52
typename alloc_traits::size_type size_type
Definition buffer.hpp:54
constexpr allocator_type & get_allocator() noexcept
Definition buffer.hpp:366
Object that owns a piece of contiguous memory.
Definition buffer.hpp:112
std::reverse_iterator< iterator > reverse_iterator
Definition buffer.hpp:133
std::reverse_iterator< const_iterator > const_reverse_iterator
Definition buffer.hpp:134
constexpr buffer(size_type n, const A &a=A())
Definition buffer.hpp:424
typename alloc_traits::difference_type difference_type
Definition buffer.hpp:130
typename alloc_traits::size_type size_type
Definition buffer.hpp:129
constexpr void resize(size_type new_size)
Definition buffer.hpp:965
constexpr const_reference back() const
Definition buffer.hpp:621
constexpr size_type max_size() const noexcept
Definition buffer.hpp:746
typename alloc_traits::const_pointer const_pointer
Definition buffer.hpp:128
constexpr auto allocate_and_copy(size_type n, It first, It last) -> pointer
Definition buffer.hpp:1054
constexpr iterator insert(const_iterator pos, const T &value)
Definition buffer.hpp:811
constexpr void shrink_to_fit()
Definition buffer.hpp:793
constexpr const_reverse_iterator crend() const noexcept
Definition buffer.hpp:722
constexpr U * data() noexcept
Definition buffer.hpp:629
buffer(const buffer &rhs, const A &a)
Definition buffer.hpp:494
constexpr auto copy_initialize(It first, It last, pointer begin, allocator_type &a) -> pointer
Definition buffer.hpp:1112
pointer_iterator< const_pointer > const_iterator
Definition buffer.hpp:132
constexpr iterator erase(const_iterator pos)
Definition buffer.hpp:924
constexpr reference back()
Definition buffer.hpp:614
typename base_type::allocator_type allocator_type
Definition buffer.hpp:123
constexpr const_iterator cbegin() const noexcept
Definition buffer.hpp:680
const value_type & const_reference
Definition buffer.hpp:126
constexpr buffer & operator=(std::initializer_list< value_type > init)
Definition buffer.hpp:575
buffer(const buffer &rhs)
Definition buffer.hpp:482
typename alloc_traits::pointer pointer
Definition buffer.hpp:127
constexpr buffer(const A &a)
Definition buffer.hpp:140
constexpr const_reference operator[](size_type i) const
Definition buffer.hpp:593
pointer_iterator< pointer > iterator
Definition buffer.hpp:131
constexpr buffer(size_type n, const value_type &v, const A &a=A())
Definition buffer.hpp:432
constexpr buffer(pointer p, size_type n, const A &a=A())
Definition buffer.hpp:440
constexpr const_iterator cend() const noexcept
Definition buffer.hpp:686
constexpr iterator begin() noexcept
Definition buffer.hpp:656
constexpr iterator end() noexcept
Definition buffer.hpp:662
constexpr buffer(It first, It last, const A &a=A())
Definition buffer.hpp:455
constexpr auto insert(const_iterator pos, InputIt first, InputIt last) -> iterator
Definition buffer.hpp:859
constexpr reverse_iterator rend() noexcept
Definition buffer.hpp:698
buffer(buffer &&rhs, const A &a)
Definition buffer.hpp:506
buffer()=default
constexpr size_type size() const noexcept
Definition buffer.hpp:740
constexpr reference operator[](size_type i)
Definition buffer.hpp:586
constexpr void reserve(size_type new_cap)
Definition buffer.hpp:752
value_type & reference
Definition buffer.hpp:125
buffer(buffer &&rhs) noexcept=default
constexpr buffer & operator=(const buffer &rhs)
Definition buffer.hpp:525
constexpr buffer(const Range &range, const A &a=A())
Definition buffer.hpp:464
constexpr void swap(buffer &rhs) noexcept
Definition buffer.hpp:989
constexpr void push_back(const std::uint8_t &value)
Definition buffer.hpp:945
constexpr const_reverse_iterator crbegin() const noexcept
Definition buffer.hpp:716
constexpr iterator emplace(const_iterator pos, Args &&... args)
Definition buffer.hpp:902
constexpr const_reference front() const
Definition buffer.hpp:607
constexpr buffer(std::initializer_list< value_type > init, const A &a=A())
Definition buffer.hpp:447
constexpr reference front()
Definition buffer.hpp:600
constexpr reverse_iterator rbegin() noexcept
Definition buffer.hpp:692
constexpr size_type capacity() const noexcept
Definition buffer.hpp:734
constexpr buffer & operator=(buffer &&rhs)
Definition buffer.hpp:548
constexpr bool empty() const noexcept
Definition buffer.hpp:728
#define SPARROW_ASSERT_TRUE(expr__)
#define SPARROW_ASSERT_FALSE(expr__)
constexpr std::size_t size(typelist< T... >={})
Definition mp_utils.hpp:107
pointer_iterator< T * > make_pointer_iterator(T *t)
Definition iterator.hpp:497
SPARROW_API bool operator==(const array &lhs, const array &rhs)
Compares the content of two arrays.
constexpr bool is_move_iterator_v
Definition buffer.hpp:855
constexpr buffer_data & operator=(buffer_data &&) noexcept
Definition buffer.hpp:319