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