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