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