From 3d1992ca0feaa08b95a113f46ca98f06a0706b6f Mon Sep 17 00:00:00 2001 From: James Moschou Date: Wed, 21 Oct 2026 18:30:36 +0200 Subject: [PATCH 01/14] Fix over-release of inline vector elements after growing to the heap vector declared its inline buffer as `T _inline_buffer[N]`, so the compiler default-constructed every inline element and destroyed all of them again after ~vector() had already destroyed the live ones. Once the vector grew past its inline capacity, the elements were memcpy'd to the heap but the stale copies left in the inline buffer were still destroyed. For the `cf_ptr` stack in Subgraph::update() this released up to 32 subgraphs an extra time whenever more than 32 subgraphs were pending, eventually freeing subgraphs that were still in use. Store inline elements in raw, aligned storage so their lifetime is managed solely by the vector. Co-Authored-By: Claude Opus 5.5 # Conflicts: # Sources/ComputeCxx/Vector/Vector.h --- Sources/ComputeCxx/Vector/Vector.h | 32 ++++++++++++++++++++---------- 1 file changed, 21 insertions(+), 11 deletions(-) diff --git a/Sources/ComputeCxx/Vector/Vector.h b/Sources/ComputeCxx/Vector/Vector.h index 4e8bf27..9f3c95d 100644 --- a/Sources/ComputeCxx/Vector/Vector.h +++ b/Sources/ComputeCxx/Vector/Vector.h @@ -17,6 +17,18 @@ IAG_ASSUME_NONNULL_BEGIN namespace IAG { +namespace details { + +template +struct vector_storage { + alignas(T) char _bytes[sizeof(T) * capacity]; + + T *_Nonnull data() { return reinterpret_cast(_bytes); }; + const T *_Nonnull data() const { return reinterpret_cast(_bytes); }; +}; + +} // namespace details + template requires std::unsigned_integral<_size_type> class vector { @@ -33,9 +45,7 @@ class vector { private: // Elements are constructed and destroyed explicitly. After relocation to // the heap, the inline storage must not destroy the old element copies. - union { - T _inline_buffer[_inline_capacity]; - }; + details::vector_storage _inline_buffer; T *_Nullable _buffer = nullptr; size_type _size = 0; size_type _capacity = _inline_capacity; @@ -66,8 +76,8 @@ class vector { reference back() { return *&data()[_size - 1]; }; const_reference back() const { return *&data()[_size - 1]; }; - T *_Nonnull data() { return _buffer != nullptr ? _buffer : _inline_buffer; }; - const T *_Nonnull data() const { return _buffer != nullptr ? _buffer : _inline_buffer; }; + T *_Nonnull data() { return _buffer != nullptr ? _buffer : _inline_buffer.data(); }; + const T *_Nonnull data() const { return _buffer != nullptr ? _buffer : _inline_buffer.data(); }; // Iterators @@ -175,8 +185,8 @@ vector::vector(vector &&other) noexcept _buffer = std::exchange(other._buffer, nullptr); } else { for (auto i = 0; i < _size; ++i) { - new (&_inline_buffer[i]) T(std::move(other._inline_buffer[i])); - other._inline_buffer[i].~T(); + new (&_inline_buffer.data()[i]) T(std::move(other._inline_buffer.data()[i])); + other._inline_buffer.data()[i].~T(); } _buffer = nullptr; } @@ -198,8 +208,8 @@ vector &vector:: _capacity = other._capacity; } else { for (auto i = 0; i < other._size; ++i) { - new (&_inline_buffer[i]) T(std::move(other._inline_buffer[i])); - other._inline_buffer[i].~T(); + new (&_inline_buffer.data()[i]) T(std::move(other._inline_buffer.data()[i])); + other._inline_buffer.data()[i].~T(); } _buffer = nullptr; _size = other._size; @@ -216,7 +226,7 @@ template void vector::reserve_slow(size_type new_cap) { size_type effective_new_cap = std::max(capacity() * 1.5, new_cap * 1.0); _buffer = reinterpret_cast(details::realloc_vector( - (void *)_buffer, (void *)_inline_buffer, _inline_capacity, &_capacity, effective_new_cap)); + (void *)_buffer, (void *)_inline_buffer.data(), _inline_capacity, &_capacity, effective_new_cap)); } template @@ -233,7 +243,7 @@ template void vector::shrink_to_fit() { if (capacity() > _size) { _buffer = reinterpret_cast(details::realloc_vector( - _buffer, _inline_buffer, _inline_capacity, &_capacity, _size)); + _buffer, _inline_buffer.data(), _inline_capacity, &_capacity, _size)); } } From 56a136f38ae33f753c1481de65cbfde0e1548e7b Mon Sep 17 00:00:00 2001 From: James Moschou Date: Wed, 21 Oct 2026 18:30:55 +0200 Subject: [PATCH 02/14] Fix vector::front() returning a pointer instead of a reference Also remove the redundant `*&` from the other element accessors. Co-Authored-By: Claude Opus 5.5 --- Sources/ComputeCxx/Vector/Vector.h | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/Sources/ComputeCxx/Vector/Vector.h b/Sources/ComputeCxx/Vector/Vector.h index 9f3c95d..422fac2 100644 --- a/Sources/ComputeCxx/Vector/Vector.h +++ b/Sources/ComputeCxx/Vector/Vector.h @@ -71,10 +71,10 @@ class vector { reference operator[](size_type pos) { return data()[pos]; }; const_reference operator[](size_type pos) const { return data()[pos]; }; - reference front() { return &data()[0]; }; - const_reference front() const { return *&data()[0]; }; - reference back() { return *&data()[_size - 1]; }; - const_reference back() const { return *&data()[_size - 1]; }; + reference front() { return data()[0]; }; + const_reference front() const { return data()[0]; }; + reference back() { return data()[_size - 1]; }; + const_reference back() const { return data()[_size - 1]; }; T *_Nonnull data() { return _buffer != nullptr ? _buffer : _inline_buffer.data(); }; const T *_Nonnull data() const { return _buffer != nullptr ? _buffer : _inline_buffer.data(); }; @@ -416,10 +416,10 @@ class vector { reference operator[](size_type pos) { return data()[pos]; }; const_reference operator[](size_type pos) const { return data()[pos]; }; - reference front() { return &data()[0]; }; - const_reference front() const { return *&data()[0]; }; - reference back() { return *&data()[_size - 1]; }; - const_reference back() const { return *&data()[_size - 1]; }; + reference front() { return data()[0]; }; + const_reference front() const { return data()[0]; }; + reference back() { return data()[_size - 1]; }; + const_reference back() const { return data()[_size - 1]; }; T *_Nonnull data() { return _buffer; }; const T *_Nonnull data() const { return _buffer; }; @@ -722,10 +722,10 @@ class vector, 0, _size_type> { reference operator[](size_type pos) { return data()[pos]; }; const_reference operator[](size_type pos) const { return data()[pos]; }; - reference front() { return &data()[0]; }; - const_reference front() const { return *&data()[0]; }; - reference back() { return *&data()[_size - 1]; }; - const_reference back() const { return *&data()[_size - 1]; }; + reference front() { return data()[0]; }; + const_reference front() const { return data()[0]; }; + reference back() { return data()[_size - 1]; }; + const_reference back() const { return data()[_size - 1]; }; std::unique_ptr *_Nonnull data() { return _buffer; }; const std::unique_ptr *_Nonnull data() const { return _buffer; }; From 7fb10c2fb95c8ebf4598836aa9d5c7caca0b416b Mon Sep 17 00:00:00 2001 From: James Moschou Date: Wed, 21 Oct 2026 18:31:31 +0200 Subject: [PATCH 03/14] Fix shrink_to_fit freeing the elements of a vector without inline storage shrink_to_fit() on a vector with no inline capacity always reallocated to zero elements, freeing the buffer even when it still held elements. Shrink to the current size instead. Co-Authored-By: Claude Opus 5.5 --- Sources/ComputeCxx/Vector/Vector.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Sources/ComputeCxx/Vector/Vector.h b/Sources/ComputeCxx/Vector/Vector.h index 422fac2..c5c2c7b 100644 --- a/Sources/ComputeCxx/Vector/Vector.h +++ b/Sources/ComputeCxx/Vector/Vector.h @@ -554,7 +554,7 @@ template requires std::unsigned_integral void vector::shrink_to_fit() { if (capacity() > size()) { - _buffer = reinterpret_cast(details::realloc_vector(_buffer, &_capacity, 0)); + _buffer = reinterpret_cast(details::realloc_vector(_buffer, &_capacity, _size)); } } From 8f1e0eba75adf3dc2def3621e34943c8cefd73a3 Mon Sep 17 00:00:00 2001 From: James Moschou Date: Wed, 21 Oct 2026 18:31:43 +0200 Subject: [PATCH 04/14] Fix move assignment of vector of unique_ptr calling the wrong destructor The move assignment operator called `~T()` on each unique_ptr element, where T is the pointee type, which fails to compile once instantiated. Reset the elements instead, matching the destructor. Co-Authored-By: Claude Opus 5.5 --- Sources/ComputeCxx/Vector/Vector.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Sources/ComputeCxx/Vector/Vector.h b/Sources/ComputeCxx/Vector/Vector.h index c5c2c7b..c0a61ac 100644 --- a/Sources/ComputeCxx/Vector/Vector.h +++ b/Sources/ComputeCxx/Vector/Vector.h @@ -797,7 +797,7 @@ vector, 0, size_type> & vector, 0, size_type>::operator=(vector &&other) noexcept { if (this != &other) { for (auto i = 0; i < _size; i++) { - _buffer[i].~T(); + _buffer[i].reset(); } if (_buffer) { free(_buffer); From c762fdadb510168c63e6921a0ca175fe87981747 Mon Sep 17 00:00:00 2001 From: James Moschou Date: Wed, 21 Oct 2026 18:33:15 +0200 Subject: [PATCH 05/14] Move vector elements that aren't trivially copyable instead of memcpy'ing them Growing or shrinking a vector relocated elements with memcpy/realloc regardless of their type, bypassing their move constructors and destructors. Like llvm::SmallVector, only use memcpy/realloc for trivially copyable elements, and otherwise move each element into the new buffer and destroy the original. Compare::Enum was non-movable but stored in a growable vector, so give it a move constructor that transfers ownership of its copies. Co-Authored-By: Claude Opus 5.5 --- Sources/ComputeCxx/Comparison/Compare.h | 7 +- Sources/ComputeCxx/Vector/Vector.h | 140 ++++++++++++++++++------ 2 files changed, 109 insertions(+), 38 deletions(-) diff --git a/Sources/ComputeCxx/Comparison/Compare.h b/Sources/ComputeCxx/Comparison/Compare.h index cef8810..69465f1 100644 --- a/Sources/ComputeCxx/Comparison/Compare.h +++ b/Sources/ComputeCxx/Comparison/Compare.h @@ -40,8 +40,11 @@ class Compare { Enum(const Enum &) = delete; Enum &operator=(const Enum &) = delete; - // Non-moveable - Enum(Enum &&) = delete; + // Move constructible so that vector can relocate it, the moved-from enum no longer owns anything + Enum(Enum &&other) + : type(std::exchange(other.type, nullptr)), enum_tag(other.enum_tag), lhs(other.lhs), rhs(other.rhs), + lhs_copy(other.lhs_copy), rhs_copy(other.rhs_copy), offset(other.offset), mode(other.mode), + owns_copies(std::exchange(other.owns_copies, false)) {}; Enum &operator=(Enum &&) = delete; }; diff --git a/Sources/ComputeCxx/Vector/Vector.h b/Sources/ComputeCxx/Vector/Vector.h index c0a61ac..c5b0d30 100644 --- a/Sources/ComputeCxx/Vector/Vector.h +++ b/Sources/ComputeCxx/Vector/Vector.h @@ -6,6 +6,7 @@ #include #include #include +#include #include #include @@ -129,6 +130,7 @@ static_assert(std::contiguous_iterator::const_iterator>); namespace details { +// Relocates elements with memcpy, so only valid for trivially copyable elements. template requires std::unsigned_integral void *_Nullable realloc_vector(void *_Nullable buffer, void *_Nonnull _inline_buffer, size_type inline_capacity, @@ -164,6 +166,31 @@ void *_Nullable realloc_vector(void *_Nullable buffer, void *_Nonnull _inline_bu return new_buffer; } +// Relocates elements with realloc, so only valid for trivially copyable elements, or when buffer is null. +template + requires std::unsigned_integral +void *_Nullable realloc_vector(void *_Nullable buffer, size_type *_Nonnull size, size_type preferred_new_size) { + if (preferred_new_size == 0) { + *size = 0; + free(buffer); + return nullptr; + } + + size_t new_size_bytes = malloc_good_size(preferred_new_size * element_size); + size_type new_size = (size_type)(new_size_bytes / element_size); + if (new_size == *size) { + // nothing to do + return buffer; + } + + void *new_buffer = realloc(buffer, new_size_bytes); + if (!new_buffer) { + precondition_failure("allocation failure"); + } + *size = new_size; + return new_buffer; +} + } // namespace details template @@ -225,8 +252,22 @@ template requires std::unsigned_integral void vector::reserve_slow(size_type new_cap) { size_type effective_new_cap = std::max(capacity() * 1.5, new_cap * 1.0); - _buffer = reinterpret_cast(details::realloc_vector( - (void *)_buffer, (void *)_inline_buffer.data(), _inline_capacity, &_capacity, effective_new_cap)); + + if constexpr (std::is_trivially_copyable_v) { + _buffer = reinterpret_cast(details::realloc_vector( + (void *)_buffer, (void *)_inline_buffer.data(), _inline_capacity, &_capacity, effective_new_cap)); + } else { + size_type new_capacity = 0; + T *new_buffer = reinterpret_cast( + details::realloc_vector(nullptr, &new_capacity, effective_new_cap)); + std::uninitialized_move(begin(), end(), new_buffer); + std::destroy(begin(), end()); + if (_buffer) { + free((void *)_buffer); + } + _buffer = new_buffer; + _capacity = new_capacity; + } } template @@ -241,9 +282,26 @@ void vector::reserve(size_type new_cap) { template requires std::unsigned_integral void vector::shrink_to_fit() { - if (capacity() > _size) { + if (!_buffer || capacity() == _size) { + return; + } + + if constexpr (std::is_trivially_copyable_v) { _buffer = reinterpret_cast(details::realloc_vector( _buffer, _inline_buffer.data(), _inline_capacity, &_capacity, _size)); + } else { + T *new_buffer = nullptr; + size_type new_capacity = _inline_capacity; + if (_size > _inline_capacity) { + new_capacity = 0; + new_buffer = + reinterpret_cast(details::realloc_vector(nullptr, &new_capacity, _size)); + } + std::uninitialized_move(begin(), end(), new_buffer ? new_buffer : _inline_buffer.data()); + std::destroy(begin(), end()); + free((void *)_buffer); + _buffer = new_buffer; + _capacity = new_capacity; } } @@ -466,34 +524,6 @@ class vector { void resize(size_type count, const value_type &value); }; -namespace details { - -template - requires std::unsigned_integral -void *_Nullable realloc_vector(void *_Nullable buffer, size_type *_Nonnull size, size_type preferred_new_size) { - if (preferred_new_size == 0) { - *size = 0; - free(buffer); - return nullptr; - } - - size_t new_size_bytes = malloc_good_size(preferred_new_size * element_size); - size_type new_size = (size_type)(new_size_bytes / element_size); - if (new_size == *size) { - // nothing to do - return buffer; - } - - void *new_buffer = realloc(buffer, new_size_bytes); - if (!new_buffer) { - precondition_failure("allocation failure"); - } - *size = new_size; - return new_buffer; -} - -} // namespace details - template requires std::unsigned_integral vector::~vector() { @@ -537,8 +567,22 @@ template requires std::unsigned_integral void vector::reserve_slow(size_type new_cap) { size_type effective_new_cap = std::max(capacity() * 1.5, new_cap * 1.0); - _buffer = - reinterpret_cast(details::realloc_vector(_buffer, &_capacity, effective_new_cap)); + + if constexpr (std::is_trivially_copyable_v) { + _buffer = reinterpret_cast( + details::realloc_vector((void *)_buffer, &_capacity, effective_new_cap)); + } else { + size_type new_capacity = 0; + T *new_buffer = reinterpret_cast( + details::realloc_vector(nullptr, &new_capacity, effective_new_cap)); + std::uninitialized_move(begin(), end(), new_buffer); + std::destroy(begin(), end()); + if (_buffer) { + free((void *)_buffer); + } + _buffer = new_buffer; + _capacity = new_capacity; + } } template @@ -553,8 +597,23 @@ void vector::reserve(size_type new_cap) { template requires std::unsigned_integral void vector::shrink_to_fit() { - if (capacity() > size()) { + if (capacity() == size()) { + return; + } + + if constexpr (std::is_trivially_copyable_v) { _buffer = reinterpret_cast(details::realloc_vector(_buffer, &_capacity, _size)); + } else { + size_type new_capacity = 0; + T *new_buffer = + reinterpret_cast(details::realloc_vector(nullptr, &new_capacity, _size)); + if (new_buffer) { + std::uninitialized_move(begin(), end(), new_buffer); + } + std::destroy(begin(), end()); + free((void *)_buffer); + _buffer = new_buffer; + _capacity = new_capacity; } } @@ -816,9 +875,18 @@ template requires std::unsigned_integral void vector, 0, size_type>::reserve_slow(size_type new_cap) { size_type effective_new_cap = std::max(capacity() * 1.5, new_cap * 1.0); - _buffer = reinterpret_cast *>( - details::realloc_vector)>(_buffer, &_capacity, + + size_type new_capacity = 0; + auto new_buffer = reinterpret_cast *>( + details::realloc_vector)>(nullptr, &new_capacity, effective_new_cap)); + std::uninitialized_move(begin(), end(), new_buffer); + std::destroy(begin(), end()); + if (_buffer) { + free((void *)_buffer); + } + _buffer = new_buffer; + _capacity = new_capacity; } template From 82042e8a99944cb409f40eb4cd7d76ead9e0330c Mon Sep 17 00:00:00 2001 From: James Moschou Date: Wed, 21 Oct 2026 18:33:45 +0200 Subject: [PATCH 06/14] Grow vector capacity like llvm::SmallVector Grow to at least 2 * capacity + 1 instead of 1.5 * capacity, computing the new capacity in integer arithmetic and reporting overflow of size_type instead of silently truncating. Co-Authored-By: Claude Opus 5.5 --- Sources/ComputeCxx/Vector/Vector.h | 19 ++++++++++++++++--- 1 file changed, 16 insertions(+), 3 deletions(-) diff --git a/Sources/ComputeCxx/Vector/Vector.h b/Sources/ComputeCxx/Vector/Vector.h index c5b0d30..2a238a3 100644 --- a/Sources/ComputeCxx/Vector/Vector.h +++ b/Sources/ComputeCxx/Vector/Vector.h @@ -5,6 +5,7 @@ #include #include #include +#include #include #include #include @@ -251,7 +252,11 @@ vector &vector:: template requires std::unsigned_integral void vector::reserve_slow(size_type new_cap) { - size_type effective_new_cap = std::max(capacity() * 1.5, new_cap * 1.0); + constexpr size_t max_capacity = std::numeric_limits::max(); + if (_capacity == max_capacity) { + precondition_failure("vector capacity overflow"); + } + size_type effective_new_cap = (size_type)std::clamp(2 * size_t(_capacity) + 1, size_t(new_cap), max_capacity); if constexpr (std::is_trivially_copyable_v) { _buffer = reinterpret_cast(details::realloc_vector( @@ -566,7 +571,11 @@ vector &vector::operator=(vector &&other) noex template requires std::unsigned_integral void vector::reserve_slow(size_type new_cap) { - size_type effective_new_cap = std::max(capacity() * 1.5, new_cap * 1.0); + constexpr size_t max_capacity = std::numeric_limits::max(); + if (_capacity == max_capacity) { + precondition_failure("vector capacity overflow"); + } + size_type effective_new_cap = (size_type)std::clamp(2 * size_t(_capacity) + 1, size_t(new_cap), max_capacity); if constexpr (std::is_trivially_copyable_v) { _buffer = reinterpret_cast( @@ -874,7 +883,11 @@ vector, 0, size_type>::operator=(vector &&other template requires std::unsigned_integral void vector, 0, size_type>::reserve_slow(size_type new_cap) { - size_type effective_new_cap = std::max(capacity() * 1.5, new_cap * 1.0); + constexpr size_t max_capacity = std::numeric_limits::max(); + if (_capacity == max_capacity) { + precondition_failure("vector capacity overflow"); + } + size_type effective_new_cap = (size_type)std::clamp(2 * size_t(_capacity) + 1, size_t(new_cap), max_capacity); size_type new_capacity = 0; auto new_buffer = reinterpret_cast *>( From 9e591b021d78546588338e3f2f68390d11bc7f84 Mon Sep 17 00:00:00 2001 From: James Moschou Date: Wed, 21 Oct 2026 18:34:36 +0200 Subject: [PATCH 07/14] Fix vector insert and erase operating on destroyed or uninitialized elements insert() move-assigned into the uninitialized slot past the end, then placement-constructed the value over a live, moved-from element without destroying it. erase() destroyed the erased elements and then swapped live elements with them. Like llvm::SmallVector, insert() now move-constructs the new last element, shifts the rest with move assignment and assigns the value, and erase() move-assigns the tail down before destroying the leftover elements. Inserting at the end goes through push_back(), which also avoids reading back() of an empty vector. erase() still returns the new end() rather than the iterator following the erased elements, since Graph::invalidate_subgraphs() relies on it. Co-Authored-By: Claude Opus 5.5 --- Sources/ComputeCxx/Vector/Vector.h | 82 +++++++++++++++++------------- 1 file changed, 47 insertions(+), 35 deletions(-) diff --git a/Sources/ComputeCxx/Vector/Vector.h b/Sources/ComputeCxx/Vector/Vector.h index 2a238a3..8cbdf1b 100644 --- a/Sources/ComputeCxx/Vector/Vector.h +++ b/Sources/ComputeCxx/Vector/Vector.h @@ -323,12 +323,20 @@ template requires std::unsigned_integral vector::iterator vector::insert(const_iterator pos, const T &value) { + if (pos == end()) { + push_back(value); + return end() - 1; + } + auto offset = pos - begin(); reserve(_size + 1); iterator mutable_pos = begin() + offset; - std::move_backward(mutable_pos, end(), end() + 1); - new (mutable_pos) value_type(value); + + new (end()) value_type(std::move(back())); + std::move_backward(mutable_pos, end() - 1, end()); _size += 1; + + *mutable_pos = value; return mutable_pos; } @@ -336,12 +344,20 @@ template requires std::unsigned_integral vector::iterator vector::insert(const_iterator pos, T &&value) { + if (pos == end()) { + push_back(std::move(value)); + return end() - 1; + } + auto offset = pos - begin(); reserve(_size + 1); iterator mutable_pos = begin() + offset; - std::move_backward(mutable_pos, end(), end() + 1); - new (mutable_pos) value_type(std::move(value)); + + new (end()) value_type(std::move(back())); + std::move_backward(mutable_pos, end() - 1, end()); _size += 1; + + *mutable_pos = std::move(value); return mutable_pos; } @@ -354,21 +370,17 @@ vector::iterator vector requires std::unsigned_integral vector::iterator vector::erase(iterator first, iterator last) { - auto count = last - first; - if (count == 0) { + if (first == last) { return last; } - for (auto iter = first; iter != last; iter++) { - iter->~T(); - } - for (auto iter = last, old_end = end(); iter != old_end; iter++) { - std::swap(*(iter - count), *iter); - } - _size -= count; + iterator new_end = std::move(last, end(), first); + std::destroy(new_end, end()); + _size = (size_type)(new_end - begin()); return end(); } @@ -642,12 +654,16 @@ vector::iterator vector::insert(const_iterator push_back(value); return end() - 1; } + auto offset = pos - begin(); reserve(_size + 1); iterator mutable_pos = begin() + offset; - std::move_backward(mutable_pos, end(), end() + 1); - new (mutable_pos) value_type(value); + + new (end()) value_type(std::move(back())); + std::move_backward(mutable_pos, end() - 1, end()); _size += 1; + + *mutable_pos = value; return mutable_pos; } @@ -658,12 +674,16 @@ vector::iterator vector::insert(const_iterator push_back(std::move(value)); return end() - 1; } + auto offset = pos - begin(); reserve(_size + 1); iterator mutable_pos = begin() + offset; - std::move_backward(mutable_pos, end(), end() + 1); - new (mutable_pos) value_type(std::move(value)); + + new (end()) value_type(std::move(back())); + std::move_backward(mutable_pos, end() - 1, end()); _size += 1; + + *mutable_pos = std::move(value); return mutable_pos; } @@ -676,20 +696,16 @@ vector::iterator vector::erase(iterator pos) { return erase(pos, pos + 1); } +// Unlike std::vector, returns the new end() rather than the iterator following the erased elements. template requires std::unsigned_integral vector::iterator vector::erase(iterator first, iterator last) { - auto count = last - first; - if (count == 0) { + if (first == last) { return last; } - for (auto iter = first; iter != last; iter++) { - iter->~T(); - } - for (auto iter = last, old_end = end(); iter != old_end; iter++) { - std::swap(*(iter - count), *iter); - } - _size -= count; + iterator new_end = std::move(last, end(), first); + std::destroy(new_end, end()); + _size = (size_type)(new_end - begin()); return end(); } @@ -921,21 +937,17 @@ vector, 0, size_type>::erase(iterator pos) { return erase(pos, pos + 1); } +// Unlike std::vector, returns the new end() rather than the iterator following the erased elements. template requires std::unsigned_integral vector, 0, size_type>::iterator vector, 0, size_type>::erase(iterator first, iterator last) { - auto count = last - first; - if (count == 0) { + if (first == last) { return last; } - for (auto iter = first; iter != last; iter++) { - iter->reset(); - } - for (auto iter = last, old_end = end(); iter != old_end; iter++) { - std::swap(*(iter - count), *iter); - } - _size -= count; + iterator new_end = std::move(last, end(), first); + std::destroy(new_end, end()); + _size = (size_type)(new_end - begin()); return end(); } From bb39998ac0d5c434005424f24250f38bc14f7008 Mon Sep 17 00:00:00 2001 From: James Moschou Date: Wed, 21 Oct 2026 18:36:18 +0200 Subject: [PATCH 08/14] Manage vector element lifetimes like llvm::SmallVector Use std::destroy and the uninitialized memory algorithms for element lifetimes in the destructor, move operations, clear(), pop_back() and resize(), and only reserve in resize() when growing. Like llvm::SmallVector, moving from a vector with inline elements into a vector that has a heap buffer now keeps that buffer rather than freeing it. Co-Authored-By: Claude Opus 5.5 --- Sources/ComputeCxx/Vector/Vector.h | 173 +++++++++++------------------ 1 file changed, 65 insertions(+), 108 deletions(-) diff --git a/Sources/ComputeCxx/Vector/Vector.h b/Sources/ComputeCxx/Vector/Vector.h index 8cbdf1b..8051eba 100644 --- a/Sources/ComputeCxx/Vector/Vector.h +++ b/Sources/ComputeCxx/Vector/Vector.h @@ -197,9 +197,7 @@ void *_Nullable realloc_vector(void *_Nullable buffer, size_type *_Nonnull size, template requires std::unsigned_integral vector::~vector() { - for (auto i = 0; i < _size; i++) { - data()[i].~T(); - } + std::destroy(begin(), end()); if (_buffer) { free((void *)_buffer); } @@ -207,45 +205,38 @@ vector::~vector() { template requires std::unsigned_integral -vector::vector(vector &&other) noexcept - : _size(std::exchange(other._size, 0)), _capacity(std::exchange(other._capacity, _inline_capacity)) { +vector::vector(vector &&other) noexcept { if (other._buffer) { _buffer = std::exchange(other._buffer, nullptr); + _capacity = std::exchange(other._capacity, _inline_capacity); } else { - for (auto i = 0; i < _size; ++i) { - new (&_inline_buffer.data()[i]) T(std::move(other._inline_buffer.data()[i])); - other._inline_buffer.data()[i].~T(); - } - _buffer = nullptr; + std::uninitialized_move(other.begin(), other.end(), _inline_buffer.data()); + std::destroy(other.begin(), other.end()); } + _size = std::exchange(other._size, 0); } template requires std::unsigned_integral vector &vector::operator=(vector &&other) noexcept { - if (this != &other) { - for (auto i = 0; i < _size; i++) { - data()[i].~T(); - } + if (this == &other) { + return *this; + } + + std::destroy(begin(), end()); + if (other._buffer) { + // steal the heap buffer if (_buffer) { - free(_buffer); - } - if (other._buffer) { - _buffer = std::exchange(other._buffer, nullptr); - _size = other._size; - _capacity = other._capacity; - } else { - for (auto i = 0; i < other._size; ++i) { - new (&_inline_buffer.data()[i]) T(std::move(other._inline_buffer.data()[i])); - other._inline_buffer.data()[i].~T(); - } - _buffer = nullptr; - _size = other._size; - _capacity = other._capacity; + free((void *)_buffer); } - other._size = 0; - other._capacity = _inline_capacity; + _buffer = std::exchange(other._buffer, nullptr); + _capacity = std::exchange(other._capacity, _inline_capacity); + } else { + // inline elements always fit in our current storage + std::uninitialized_move(other.begin(), other.end(), begin()); + std::destroy(other.begin(), other.end()); } + _size = std::exchange(other._size, 0); return *this; } @@ -313,9 +304,7 @@ void vector::shrink_to_fit() { template requires std::unsigned_integral void vector::clear() { - for (auto i = 0; i < _size; i++) { - data()[i].~T(); - } + std::destroy(begin(), end()); _size = 0; } @@ -414,22 +403,18 @@ template requires std::unsigned_integral void vector::pop_back() { assert(size() > 0); - data()[_size - 1].~T(); _size -= 1; + std::destroy_at(end()); } template requires std::unsigned_integral void vector::resize(size_type count) { - reserve(count); if (count < _size) { - for (auto i = count; i < _size; i++) { - data()[i].~T(); - } + std::destroy(begin() + count, end()); } else if (count > _size) { - for (auto i = _size; i < count; i++) { - new (&data()[i]) value_type(); - } + reserve(count); + std::uninitialized_value_construct(end(), begin() + count); } _size = count; } @@ -437,15 +422,11 @@ void vector::resize(size_type count) { template requires std::unsigned_integral void vector::resize(size_type count, const value_type &value) { - reserve(count); if (count < _size) { - for (auto i = count; i < _size; i++) { - data()[i].~T(); - } + std::destroy(begin() + count, end()); } else if (count > _size) { - for (auto i = _size; i < count; i++) { - new (&data()[i]) value_type(value); - } + reserve(count); + std::uninitialized_fill(end(), begin() + count, value); } _size = count; } @@ -544,39 +525,32 @@ class vector { template requires std::unsigned_integral vector::~vector() { - for (auto i = 0; i < _size; i++) { - _buffer[i].~T(); - } + std::destroy(begin(), end()); if (_buffer) { - free(_buffer); + free((void *)_buffer); } } template requires std::unsigned_integral vector::vector(vector &&other) noexcept - : _buffer(std::exchange(other._buffer, nullptr)), _size(other._size), _capacity(other._capacity) { - other._size = 0; - other._capacity = 0; -} + : _buffer(std::exchange(other._buffer, nullptr)), _size(std::exchange(other._size, 0)), + _capacity(std::exchange(other._capacity, 0)) {} template requires std::unsigned_integral vector &vector::operator=(vector &&other) noexcept { - if (this != &other) { - for (auto i = 0; i < _size; i++) { - _buffer[i].~T(); - } - if (_buffer) { - free(_buffer); - } - _buffer = other._buffer; - _size = other._size; - _capacity = other._capacity; - other._buffer = nullptr; - other._size = 0; - other._capacity = 0; + if (this == &other) { + return *this; + } + + std::destroy(begin(), end()); + if (_buffer) { + free((void *)_buffer); } + _buffer = std::exchange(other._buffer, nullptr); + _size = std::exchange(other._size, 0); + _capacity = std::exchange(other._capacity, 0); return *this; } @@ -641,9 +615,7 @@ void vector::shrink_to_fit() { template requires std::unsigned_integral void vector::clear() { - for (auto i = 0; i < _size; i++) { - data()[i].~T(); - } + std::destroy(begin(), end()); _size = 0; } @@ -729,22 +701,18 @@ template requires std::unsigned_integral void vector::pop_back() { assert(size() > 0); - data()[_size - 1].~T(); _size -= 1; + std::destroy_at(end()); } template requires std::unsigned_integral void vector::resize(size_type count) { - reserve(count); if (count < _size) { - for (auto i = count; i < _size; i++) { - data()[i].~T(); - } + std::destroy(begin() + count, end()); } else if (count > _size) { - for (auto i = _size; i < count; i++) { - new (&data()[i]) value_type(); - } + reserve(count); + std::uninitialized_value_construct(end(), begin() + count); } _size = count; } @@ -752,15 +720,11 @@ void vector::resize(size_type count) { template requires std::unsigned_integral void vector::resize(size_type count, const value_type &value) { - reserve(count); if (count < _size) { - for (auto i = count; i < _size; i++) { - data()[i].~T(); - } + std::destroy(begin() + count, end()); } else if (count > _size) { - for (auto i = _size; i < count; i++) { - new (&data()[i]) value_type(value); - } + reserve(count); + std::uninitialized_fill(end(), begin() + count, value); } _size = count; } @@ -859,40 +823,33 @@ class vector, 0, _size_type> { template requires std::unsigned_integral vector, 0, size_type>::~vector() { - for (auto i = 0; i < _size; i++) { - _buffer[i].reset(); - } + std::destroy(begin(), end()); if (_buffer) { - free(_buffer); + free((void *)_buffer); } } template requires std::unsigned_integral vector, 0, size_type>::vector(vector &&other) noexcept - : _buffer(std::exchange(other._buffer, nullptr)), _size(other._size), _capacity(other._capacity) { - other._size = 0; - other._capacity = 0; -} + : _buffer(std::exchange(other._buffer, nullptr)), _size(std::exchange(other._size, 0)), + _capacity(std::exchange(other._capacity, 0)) {} template requires std::unsigned_integral vector, 0, size_type> & vector, 0, size_type>::operator=(vector &&other) noexcept { - if (this != &other) { - for (auto i = 0; i < _size; i++) { - _buffer[i].reset(); - } - if (_buffer) { - free(_buffer); - } - _buffer = other._buffer; - _size = other._size; - _capacity = other._capacity; - other._buffer = nullptr; - other._size = 0; - other._capacity = 0; + if (this == &other) { + return *this; + } + + std::destroy(begin(), end()); + if (_buffer) { + free((void *)_buffer); } + _buffer = std::exchange(other._buffer, nullptr); + _size = std::exchange(other._size, 0); + _capacity = std::exchange(other._capacity, 0); return *this; } From b81de33b3591a1d5f59ca934a5185314890aa1fe Mon Sep 17 00:00:00 2001 From: James Moschou Date: Wed, 21 Oct 2026 18:37:03 +0200 Subject: [PATCH 09/14] Handle vector arguments that refer to elements of the same vector push_back(), insert(), resize(count, value) and emplace_back() read their argument after growing the vector, so passing an element of the same vector (e.g. `v.push_back(v[0])` when full) read from the freed buffer. insert() could also read the argument after shifting it. Like llvm::SmallVector, recompute the argument's address after growing when it refers to the vector's storage, and adjust it if insert() shifted it. emplace_back() constructs the new element before growing. Co-Authored-By: Claude Opus 5.5 --- Sources/ComputeCxx/Vector/Vector.h | 179 +++++++++++++++++++++++++---- 1 file changed, 154 insertions(+), 25 deletions(-) diff --git a/Sources/ComputeCxx/Vector/Vector.h b/Sources/ComputeCxx/Vector/Vector.h index 8051eba..42e5f67 100644 --- a/Sources/ComputeCxx/Vector/Vector.h +++ b/Sources/ComputeCxx/Vector/Vector.h @@ -4,6 +4,7 @@ #include #include #include +#include #include #include #include @@ -317,15 +318,28 @@ vector::iterator vector= _capacity) { + if (!std::less<>()(value_ptr, begin()) && std::less<>()(value_ptr, end())) { + auto value_offset = value_ptr - begin(); + reserve(_size + 1); + value_ptr = begin() + value_offset; + } else { + reserve(_size + 1); + } + } iterator mutable_pos = begin() + offset; new (end()) value_type(std::move(back())); std::move_backward(mutable_pos, end() - 1, end()); _size += 1; - *mutable_pos = value; + if (!std::less<>()(value_ptr, mutable_pos) && std::less<>()(value_ptr, end())) { + ++value_ptr; + } + *mutable_pos = *value_ptr; return mutable_pos; } @@ -338,15 +352,28 @@ vector::iterator vector= _capacity) { + if (!std::less<>()(value_ptr, begin()) && std::less<>()(value_ptr, end())) { + auto value_offset = value_ptr - begin(); + reserve(_size + 1); + value_ptr = begin() + value_offset; + } else { + reserve(_size + 1); + } + } iterator mutable_pos = begin() + offset; new (end()) value_type(std::move(back())); std::move_backward(mutable_pos, end() - 1, end()); _size += 1; - *mutable_pos = std::move(value); + if (!std::less<>()(value_ptr, mutable_pos) && std::less<>()(value_ptr, end())) { + ++value_ptr; + } + *mutable_pos = std::move(*value_ptr); return mutable_pos; } @@ -376,16 +403,36 @@ vector::iterator vector requires std::unsigned_integral void vector::push_back(const T &value) { - reserve(_size + 1); - new (&data()[_size]) value_type(value); + // value may refer to an element of this vector + const T *value_ptr = &value; + if (_size >= _capacity) { + if (!std::less<>()(value_ptr, begin()) && std::less<>()(value_ptr, end())) { + auto value_offset = value_ptr - begin(); + reserve(_size + 1); + value_ptr = begin() + value_offset; + } else { + reserve(_size + 1); + } + } + new (end()) value_type(*value_ptr); _size += 1; } template requires std::unsigned_integral void vector::push_back(T &&value) { - reserve(_size + 1); - new (&data()[_size]) value_type(std::move(value)); + // value may refer to an element of this vector + T *value_ptr = &value; + if (_size >= _capacity) { + if (!std::less<>()(value_ptr, begin()) && std::less<>()(value_ptr, end())) { + auto value_offset = value_ptr - begin(); + reserve(_size + 1); + value_ptr = begin() + value_offset; + } else { + reserve(_size + 1); + } + } + new (end()) value_type(std::move(*value_ptr)); _size += 1; } @@ -393,10 +440,16 @@ template requires std::unsigned_integral template vector::reference vector::emplace_back(Args &&...args) { - reserve(_size + 1); - new (&data()[_size]) value_type(std::forward(args)...); + if (_size >= _capacity) { + // construct before growing in case args refer to an element of this vector + value_type value(std::forward(args)...); + reserve(_size + 1); + new (end()) value_type(std::move(value)); + } else { + new (end()) value_type(std::forward(args)...); + } _size += 1; - return data()[_size - 1]; + return back(); } template @@ -425,8 +478,18 @@ void vector::resize(size_type count, const value if (count < _size) { std::destroy(begin() + count, end()); } else if (count > _size) { - reserve(count); - std::uninitialized_fill(end(), begin() + count, value); + // value may refer to an element of this vector + const T *value_ptr = &value; + if (count > _capacity) { + if (!std::less<>()(value_ptr, begin()) && std::less<>()(value_ptr, end())) { + auto value_offset = value_ptr - begin(); + reserve(count); + value_ptr = begin() + value_offset; + } else { + reserve(count); + } + } + std::uninitialized_fill(end(), begin() + count, *value_ptr); } _size = count; } @@ -627,15 +690,28 @@ vector::iterator vector::insert(const_iterator return end() - 1; } + // value may refer to an element of this vector auto offset = pos - begin(); - reserve(_size + 1); + const T *value_ptr = &value; + if (_size >= _capacity) { + if (!std::less<>()(value_ptr, begin()) && std::less<>()(value_ptr, end())) { + auto value_offset = value_ptr - begin(); + reserve(_size + 1); + value_ptr = begin() + value_offset; + } else { + reserve(_size + 1); + } + } iterator mutable_pos = begin() + offset; new (end()) value_type(std::move(back())); std::move_backward(mutable_pos, end() - 1, end()); _size += 1; - *mutable_pos = value; + if (!std::less<>()(value_ptr, mutable_pos) && std::less<>()(value_ptr, end())) { + ++value_ptr; + } + *mutable_pos = *value_ptr; return mutable_pos; } @@ -647,15 +723,28 @@ vector::iterator vector::insert(const_iterator return end() - 1; } + // value may refer to an element of this vector auto offset = pos - begin(); - reserve(_size + 1); + T *value_ptr = &value; + if (_size >= _capacity) { + if (!std::less<>()(value_ptr, begin()) && std::less<>()(value_ptr, end())) { + auto value_offset = value_ptr - begin(); + reserve(_size + 1); + value_ptr = begin() + value_offset; + } else { + reserve(_size + 1); + } + } iterator mutable_pos = begin() + offset; new (end()) value_type(std::move(back())); std::move_backward(mutable_pos, end() - 1, end()); _size += 1; - *mutable_pos = std::move(value); + if (!std::less<>()(value_ptr, mutable_pos) && std::less<>()(value_ptr, end())) { + ++value_ptr; + } + *mutable_pos = std::move(*value_ptr); return mutable_pos; } @@ -684,16 +773,36 @@ vector::iterator vector::erase(iterator first, template requires std::unsigned_integral void vector::push_back(const T &value) { - reserve(_size + 1); - new (&_buffer[_size]) value_type(value); + // value may refer to an element of this vector + const T *value_ptr = &value; + if (_size >= _capacity) { + if (!std::less<>()(value_ptr, begin()) && std::less<>()(value_ptr, end())) { + auto value_offset = value_ptr - begin(); + reserve(_size + 1); + value_ptr = begin() + value_offset; + } else { + reserve(_size + 1); + } + } + new (end()) value_type(*value_ptr); _size += 1; } template requires std::unsigned_integral void vector::push_back(T &&value) { - reserve(_size + 1); - new (&_buffer[_size]) value_type(std::move(value)); + // value may refer to an element of this vector + T *value_ptr = &value; + if (_size >= _capacity) { + if (!std::less<>()(value_ptr, begin()) && std::less<>()(value_ptr, end())) { + auto value_offset = value_ptr - begin(); + reserve(_size + 1); + value_ptr = begin() + value_offset; + } else { + reserve(_size + 1); + } + } + new (end()) value_type(std::move(*value_ptr)); _size += 1; } @@ -723,8 +832,18 @@ void vector::resize(size_type count, const value_type &value) { if (count < _size) { std::destroy(begin() + count, end()); } else if (count > _size) { - reserve(count); - std::uninitialized_fill(end(), begin() + count, value); + // value may refer to an element of this vector + const T *value_ptr = &value; + if (count > _capacity) { + if (!std::less<>()(value_ptr, begin()) && std::less<>()(value_ptr, end())) { + auto value_offset = value_ptr - begin(); + reserve(count); + value_ptr = begin() + value_offset; + } else { + reserve(count); + } + } + std::uninitialized_fill(end(), begin() + count, *value_ptr); } _size = count; } @@ -911,8 +1030,18 @@ vector, 0, size_type>::erase(iterator first, it template requires std::unsigned_integral void vector, 0, size_type>::push_back(std::unique_ptr &&value) { - reserve(_size + 1); - new (&_buffer[_size]) value_type(std::move(value)); + // value may refer to an element of this vector + auto value_ptr = &value; + if (_size >= _capacity) { + if (!std::less<>()(value_ptr, begin()) && std::less<>()(value_ptr, end())) { + auto value_offset = value_ptr - begin(); + reserve(_size + 1); + value_ptr = begin() + value_offset; + } else { + reserve(_size + 1); + } + } + new (end()) value_type(std::move(*value_ptr)); _size += 1; } From 7bcc00161805d841993fd6ec05519bfe3896a7f9 Mon Sep 17 00:00:00 2001 From: James Moschou Date: Wed, 21 Oct 2026 18:38:25 +0200 Subject: [PATCH 10/14] Implement clear() and pop_back() for vector of unique_ptr Both were declared by the unique_ptr specialization but never defined. Co-Authored-By: Claude Opus 5.5 --- Sources/ComputeCxx/Vector/Vector.h | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/Sources/ComputeCxx/Vector/Vector.h b/Sources/ComputeCxx/Vector/Vector.h index 42e5f67..1207326 100644 --- a/Sources/ComputeCxx/Vector/Vector.h +++ b/Sources/ComputeCxx/Vector/Vector.h @@ -1003,6 +1003,13 @@ void vector, 0, size_type>::reserve(size_type n reserve_slow(new_cap); } +template + requires std::unsigned_integral +void vector, 0, size_type>::clear() { + std::destroy(begin(), end()); + _size = 0; +} + template requires std::unsigned_integral vector, 0, size_type>::iterator @@ -1045,6 +1052,14 @@ void vector, 0, size_type>::push_back(std::uniq _size += 1; } +template + requires std::unsigned_integral +void vector, 0, size_type>::pop_back() { + assert(size() > 0); + _size -= 1; + std::destroy_at(end()); +} + } // namespace IAG IAG_ASSUME_NONNULL_END From c7e8a042774c2f075b09ed2aa7a09e1b3cdd638c Mon Sep 17 00:00:00 2001 From: James Moschou Date: Wed, 21 Oct 2026 18:38:53 +0200 Subject: [PATCH 11/14] Implement insert() of an rvalue for vector of unique_ptr It was declared by the unique_ptr specialization but never defined. Implemented like the other specializations, following llvm::SmallVector. Co-Authored-By: Claude Opus 5.5 --- Sources/ComputeCxx/Vector/Vector.h | 35 ++++++++++++++++++++++++++++++ 1 file changed, 35 insertions(+) diff --git a/Sources/ComputeCxx/Vector/Vector.h b/Sources/ComputeCxx/Vector/Vector.h index 1207326..20b8ce2 100644 --- a/Sources/ComputeCxx/Vector/Vector.h +++ b/Sources/ComputeCxx/Vector/Vector.h @@ -1010,6 +1010,41 @@ void vector, 0, size_type>::clear() { _size = 0; } +template + requires std::unsigned_integral +vector, 0, size_type>::iterator +vector, 0, size_type>::insert(const_iterator pos, + std::unique_ptr &&value) { + if (pos == end()) { + push_back(std::move(value)); + return end() - 1; + } + + // value may refer to an element of this vector + auto offset = pos - begin(); + auto value_ptr = &value; + if (_size >= _capacity) { + if (!std::less<>()(value_ptr, begin()) && std::less<>()(value_ptr, end())) { + auto value_offset = value_ptr - begin(); + reserve(_size + 1); + value_ptr = begin() + value_offset; + } else { + reserve(_size + 1); + } + } + iterator mutable_pos = begin() + offset; + + new (end()) value_type(std::move(back())); + std::move_backward(mutable_pos, end() - 1, end()); + _size += 1; + + if (!std::less<>()(value_ptr, mutable_pos) && std::less<>()(value_ptr, end())) { + ++value_ptr; + } + *mutable_pos = std::move(*value_ptr); + return mutable_pos; +} + template requires std::unsigned_integral vector, 0, size_type>::iterator From f915e3c0f3a9de1aac0fa48bac6faa3185cd511b Mon Sep 17 00:00:00 2001 From: James Moschou Date: Wed, 21 Oct 2026 18:39:18 +0200 Subject: [PATCH 12/14] Implement resize() for vector of unique_ptr It was declared by the unique_ptr specialization but never defined. New elements are value-initialized to nullptr. Co-Authored-By: Claude Opus 5.5 --- Sources/ComputeCxx/Vector/Vector.h | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/Sources/ComputeCxx/Vector/Vector.h b/Sources/ComputeCxx/Vector/Vector.h index 20b8ce2..8607dc3 100644 --- a/Sources/ComputeCxx/Vector/Vector.h +++ b/Sources/ComputeCxx/Vector/Vector.h @@ -1095,6 +1095,18 @@ void vector, 0, size_type>::pop_back() { std::destroy_at(end()); } +template + requires std::unsigned_integral +void vector, 0, size_type>::resize(size_type count) { + if (count < _size) { + std::destroy(begin() + count, end()); + } else if (count > _size) { + reserve(count); + std::uninitialized_value_construct(end(), begin() + count); + } + _size = count; +} + } // namespace IAG IAG_ASSUME_NONNULL_END From e0a243ded4ad8f6e5d8d08a98693e5120f4ac494 Mon Sep 17 00:00:00 2001 From: James Moschou Date: Wed, 21 Oct 2026 18:39:45 +0200 Subject: [PATCH 13/14] Implement shrink_to_fit() for vector of unique_ptr It was declared by the unique_ptr specialization but never defined. Co-Authored-By: Claude Opus 5.5 --- Sources/ComputeCxx/Vector/Vector.h | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/Sources/ComputeCxx/Vector/Vector.h b/Sources/ComputeCxx/Vector/Vector.h index 8607dc3..4244e29 100644 --- a/Sources/ComputeCxx/Vector/Vector.h +++ b/Sources/ComputeCxx/Vector/Vector.h @@ -1003,6 +1003,25 @@ void vector, 0, size_type>::reserve(size_type n reserve_slow(new_cap); } +template + requires std::unsigned_integral +void vector, 0, size_type>::shrink_to_fit() { + if (capacity() == size()) { + return; + } + + size_type new_capacity = 0; + auto new_buffer = reinterpret_cast *>( + details::realloc_vector)>(nullptr, &new_capacity, _size)); + if (new_buffer) { + std::uninitialized_move(begin(), end(), new_buffer); + } + std::destroy(begin(), end()); + free((void *)_buffer); + _buffer = new_buffer; + _capacity = new_capacity; +} + template requires std::unsigned_integral void vector, 0, size_type>::clear() { From d26179c4a57068dc1bde33e412f934df877883a3 Mon Sep 17 00:00:00 2001 From: James Moschou Date: Wed, 21 Oct 2026 18:40:20 +0200 Subject: [PATCH 14/14] Delete vector of unique_ptr methods that would copy an element insert() and resize() taking a `const std::unique_ptr &` were declared but never defined, and can't be, since unique_ptr is not copyable. Mark them deleted like push_back(const std::unique_ptr &), so that misuse is a compile error rather than a link error. Co-Authored-By: Claude Opus 5.5 --- Sources/ComputeCxx/Vector/Vector.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Sources/ComputeCxx/Vector/Vector.h b/Sources/ComputeCxx/Vector/Vector.h index 4244e29..de8b47c 100644 --- a/Sources/ComputeCxx/Vector/Vector.h +++ b/Sources/ComputeCxx/Vector/Vector.h @@ -925,7 +925,7 @@ class vector, 0, _size_type> { void clear(); - iterator insert(const_iterator pos, const std::unique_ptr &value); + iterator insert(const_iterator pos, const std::unique_ptr &value) = delete; iterator insert(const_iterator pos, std::unique_ptr &&value); iterator erase(iterator pos); @@ -936,7 +936,7 @@ class vector, 0, _size_type> { void pop_back(); void resize(size_type count); - void resize(size_type count, const value_type &value); + void resize(size_type count, const value_type &value) = delete; }; template