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