From 5c6ccd5394c051dbb9f7bc29b5a1342f79d429e4 Mon Sep 17 00:00:00 2001 From: Scott Dixon Date: Tue, 15 Aug 2023 15:19:50 -0700 Subject: [PATCH] \#55 adding assign to VLA --- .vscode/settings.json | 3 +- .../test_variable_length_array_bool.cpp | 59 +++++++ .../test_variable_length_array_compat.cpp | 142 ++++++++++++++++- include/cetl/variable_length_array.hpp | 147 ++++++++++++++---- 4 files changed, 322 insertions(+), 29 deletions(-) diff --git a/.vscode/settings.json b/.vscode/settings.json index 677f32d1..dff984b3 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -179,6 +179,7 @@ "strstream": "cpp", "typeindex": "cpp", "charconv": "cpp", - "csignal": "cpp" + "csignal": "cpp", + "format": "cpp" } } diff --git a/cetlvast/suites/unittest/test_variable_length_array_bool.cpp b/cetlvast/suites/unittest/test_variable_length_array_bool.cpp index 9e57d746..56a87e20 100644 --- a/cetlvast/suites/unittest/test_variable_length_array_bool.cpp +++ b/cetlvast/suites/unittest/test_variable_length_array_bool.cpp @@ -232,3 +232,62 @@ TYPED_TEST(VLABoolTests, TestBoolIterator) ASSERT_EQ(true, *(foo.cend() - 1)); ASSERT_EQ(true, foo.cbegin()[2]); } + +TYPED_TEST(VLABoolTests, TestBoolPopBack) +{ + auto test_subject = TypeParam::make_bool_container( + std::initializer_list{true, true, true, true, true, true, true, true, true}); + std::size_t starting_size = test_subject.size(); + ASSERT_EQ(9, starting_size); + while(starting_size > 0) + { + test_subject.pop_back(); + --starting_size; + ASSERT_EQ(starting_size, test_subject.size()); + } +} + +TYPED_TEST(VLABoolTests, TestBoolResize) +{ + auto array = TypeParam::make_bool_container(); + for(std::size_t i = 1; i <= 64; ++i) + { + array.resize(i); + ASSERT_EQ(i, array.size()); + ASSERT_EQ(false, array[i]); + } +} + +TYPED_TEST(VLABoolTests, TestBoolResizeWithDefault) +{ + auto array = TypeParam::make_bool_container(std::initializer_list{false}); + array.resize(22, true); + ASSERT_EQ(22, array.size()); + ASSERT_EQ(false, array[0]); + for(std::size_t i = 1; i < array.size(); ++i) + { + ASSERT_EQ(true, array[i]); + } +} + +TYPED_TEST(VLABoolTests, TestBoolResizeOneBit) +{ + auto array = TypeParam::make_bool_container(std::initializer_list{true, false, true}); + ASSERT_EQ(3, array.size()); + ASSERT_EQ(1, array[0]); + ASSERT_EQ(0, array[1]); + ASSERT_EQ(1, array[2]); + array.resize(4, 1); + ASSERT_EQ(4, array.size()); + ASSERT_EQ(1, array[0]); + ASSERT_EQ(0, array[1]); + ASSERT_EQ(1, array[2]); + ASSERT_EQ(1, array[3]); + array.resize(5); + ASSERT_EQ(5, array.size()); + ASSERT_EQ(1, array[0]); + ASSERT_EQ(0, array[1]); + ASSERT_EQ(1, array[2]); + ASSERT_EQ(1, array[3]); + ASSERT_EQ(0, array[4]); +} diff --git a/cetlvast/suites/unittest/test_variable_length_array_compat.cpp b/cetlvast/suites/unittest/test_variable_length_array_compat.cpp index 6f9719b8..be92731a 100644 --- a/cetlvast/suites/unittest/test_variable_length_array_compat.cpp +++ b/cetlvast/suites/unittest/test_variable_length_array_compat.cpp @@ -255,7 +255,7 @@ class CetlUnsynchronizedArrayMemoryResourceFactory return delegate_.reallocate(p, old_size_bytes, new_size_bytes, alignment); } - std::array memory_; + std::array memory_; cetl::pmr::UnsynchronizedBufferMemoryResourceDelegate delegate_; }; @@ -662,6 +662,80 @@ TYPED_TEST(VLATestsGeneric, TestOverMaxSize) #endif } +// +-------------------------------------------------------------------------------------------------------------------+ + +TYPED_TEST(VLATestsGeneric, TestResize) +{ + typename TestFixture::SubjectType subject{TestFixture::make_allocator()}; + + std::size_t clamped_max = std::min(this->get_expected_max_size(), 10UL); + ASSERT_GT(this->get_expected_max_size(), 0) << "This test is only valid if get_expected_max_size() > 0"; + ASSERT_GT(clamped_max, subject.size()); + subject.resize(clamped_max); + ASSERT_EQ(clamped_max, subject.size()); + + typename TestFixture::SubjectType::value_type default_constructed_value{}; + ASSERT_EQ(subject[subject.size() - 1], default_constructed_value); +} + +// +-------------------------------------------------------------------------------------------------------------------+ + +TYPED_TEST(VLATestsGeneric, TestResizeToZero) +{ + typename TestFixture::SubjectType subject{TestFixture::make_allocator()}; + + std::size_t clamped_max = std::min(this->get_expected_max_size(), 10UL); + ASSERT_GT(this->get_expected_max_size(), 0) << "This test is only valid if get_expected_max_size() > 0"; + ASSERT_GT(clamped_max, subject.size()); + subject.resize(clamped_max); + ASSERT_EQ(clamped_max, subject.size()); + std::size_t capacity_before = subject.capacity(); + + subject.resize(0); + ASSERT_EQ(0, subject.size()); + ASSERT_EQ(capacity_before, subject.capacity()); +} + +// +-------------------------------------------------------------------------------------------------------------------+ + +TYPED_TEST(VLATestsGeneric, TestResizeWithCopy) +{ + if (std::is_same::value) + { + GTEST_SKIP() << "Skipping test that requires CETL reallocation support."; + } + + typename TestFixture::SubjectType subject{TestFixture::make_allocator()}; + + std::size_t clamped_max = std::min(this->get_expected_max_size(), 10UL); + ASSERT_GT(this->get_expected_max_size(), 1) << "This test is only valid if get_expected_max_size() > 1"; + ASSERT_GT(clamped_max, subject.size()); + + subject.push_back(1); + + const typename TestFixture::SubjectType::value_type copy_from_value{2}; + subject.resize(clamped_max, copy_from_value); + ASSERT_EQ(clamped_max, subject.size()); + ASSERT_EQ(1, subject[0]); + + for (std::size_t i = 1; i < subject.size(); ++i) + { + ASSERT_EQ(subject[i], copy_from_value); + } +} + +// +----------------------------------------------------------------------+ + +#ifdef __cpp_exceptions + +TYPED_TEST(VLATestsGeneric, TestResizeExceptionLengthError) +{ + typename TestFixture::SubjectType subject{TestFixture::make_allocator()}; + ASSERT_THROW(subject.resize(subject.max_size() + 1), std::length_error); +} + +#endif // __cpp_exceptions + // +----------------------------------------------------------------------+ /** * Test suite to ensure non-trivial objects are properly handled. This one is both for bool and non-bool spec. @@ -1036,3 +1110,69 @@ TEST(VLATestsNonTrivialSpecific, TestMoveAssignment) ASSERT_NE(lhs, rhs); ASSERT_EQ(std::string("three"), lhs[0]); } + +struct NoDefault +{ + ~NoDefault() = default; + NoDefault() = delete; + NoDefault(int value) + : data_{value} {}; + NoDefault(const NoDefault&) = default; + NoDefault(NoDefault&&) noexcept = default; + NoDefault& operator=(const NoDefault&) = default; + NoDefault& operator=(NoDefault&&) noexcept = default; + + int data() const noexcept + { + return data_; + } + +private: + int data_; +}; + +TEST(VLATestsNonTrivialSpecific, TestResizeWithNoDefaultCtorData) +{ + std::allocator allocator{}; + cetl::VariableLengthArray> subject{{NoDefault{1}}, allocator}; + ASSERT_EQ(1, subject.size()); + subject.resize(10, NoDefault{2}); + ASSERT_EQ(10, subject.size()); + ASSERT_EQ(1, subject[0].data()); + for (std::size_t i = 1; i < subject.size(); ++i) + { + ASSERT_EQ(2, subject[i].data()); + } +} + +#ifdef __cpp_exceptions + +struct GrenadeError : public std::runtime_error +{ + GrenadeError(const char* message) + : std::runtime_error(message) + { + + } +}; + +struct Grenade +{ + Grenade(int value) + { + if (value == 2) + { + throw GrenadeError(""); + } + } +}; + +TYPED_TEST(VLATestsGeneric, TestResizeExceptionFromCtorOnResize) +{ + std::allocator allocator{}; + cetl::VariableLengthArray> subject{{Grenade{1}}, allocator}; + ASSERT_EQ(1, subject.size()); + ASSERT_THROW(subject.resize(2, Grenade{2}), GrenadeError); +} + +#endif // __cpp_exceptions diff --git a/include/cetl/variable_length_array.hpp b/include/cetl/variable_length_array.hpp index 77fcece8..27421d46 100644 --- a/include/cetl/variable_length_array.hpp +++ b/include/cetl/variable_length_array.hpp @@ -6,7 +6,7 @@ /// Copyright Amazon.com Inc. or its affiliates. /// SPDX-License-Identifier: MIT /// -/// cSpell:ignore cend cbegin rnext rend lnext lbegin pocca pocma +/// cSpell:ignore cend cbegin rnext rend lnext lbegin pocca pocma urvo #ifndef CETL_VARIABLE_LENGTH_ARRAY_HPP_INCLUDED #define CETL_VARIABLE_LENGTH_ARRAY_HPP_INCLUDED @@ -675,7 +675,7 @@ class VariableLengthArrayBase } for (std::size_t i = size_; i < new_size; ++i) { - alloc_.construct(&data_[i], std::forward(args)...); + std::allocator_traits::construct(alloc_, &data_[i], std::forward(args)...); } } else @@ -1394,8 +1394,6 @@ class VariableLengthArray : protected VariableLengthArrayBase data_[--size_].~value_type(); } } - - /// /// Like push_back but constructs the object directly in uninitialized memory. /// @throw throw std::length_error if there was not enough storage for an additional element. /// If exceptions are disabled then the caller must check the array size before and @@ -1415,6 +1413,35 @@ class VariableLengthArray : protected VariableLengthArrayBase size_++; } + /// Resizes internal storage to count elements default initializing any added elements over the + /// current size() and deleting any elements under the current size(). If size() == `count` then + /// this method has no effect. + /// + /// @param count The new size to set for this container. + /// @throw * If exceptions are enabled then any exceptions that `value_type` constructors or destructors + /// throw will escape this call + /// @throw std::length_error if the size requested is greater than `max_size()`. + /// @throw std::bad_alloc if the container cannot obtain enough memory to size up to `count`. + constexpr void resize(size_type count) + { + Base::resize(count, max_size()); + } + + /// Resizes internal storage to count elements copy-initializing any added elements over the + /// current size() and deleting any elements under the current size(). If size() == `count` then + /// this method has no effect. + /// + /// @param count The new size to set for this container. + /// @param value The value to copy into any new elements created by the operation. + /// @throw * If exceptions are enabled then any exceptions that `value_type` constructors or destructors + /// throw will escape this call + /// @throw std::length_error if the size requested is greater than `max_size()`. + /// @throw std::bad_alloc if the container cannot obtain enough memory to size up to `count`. + constexpr void resize(size_type count, const value_type& value) + { + Base::resize(count, max_size(), value); + } + private: // +----------------------------------------------------------------------+ @@ -1838,7 +1865,7 @@ class VariableLengthArray : protected VariableLengthArrayBase((1U << last_byte_bit_fill_) - 1U); + const Storage last_byte_mask = static_cast((1U << (last_byte_bit_fill_ + 1)) - 1U); return (data_[size_ - 1] & last_byte_mask) == (rhs.data_[size_ - 1] & last_byte_mask); } @@ -1952,7 +1979,7 @@ class VariableLengthArray : protected VariableLengthArrayBase : protected VariableLengthArrayBase 0) { - last_byte_bit_fill_--; + --last_byte_bit_fill_; } - if (last_byte_bit_fill_ == 0 && size_ > 1) + else if (size_ > 1) { --size_; - last_byte_bit_fill_ = 8; + last_byte_bit_fill_ = 7; + } + else + { + size_ = 0; + last_byte_bit_fill_ = 0; } } @@ -2117,10 +2149,78 @@ class VariableLengthArray : protected VariableLengthArrayBase capacity_) + { + Base::reserve(byte_sized, max_size()); + } + if (byte_sized == 0) + { + size_ = 0; + last_byte_bit_fill_ = 0; + } + else + { + Storage bit_sized = (count - 1) % 8U; + if (byte_sized > size_) + { + // Go ahead and just set all bits in the last bytes since we use masks + // when we do comparisons. + (void) memset(&data_[size_], (value) ? 0xFF : 0, size_ - (byte_sized - 1)); + } + if (size_ > 0) + { + const Storage existing_byte = data_[size_ - 1]; + const std::size_t bit_size_delta = 8U - (last_byte_bit_fill_ + 1); + const Storage existing_bits_mask = static_cast((1U << (last_byte_bit_fill_ + 1)) - 1U); + if (value) + { + const Storage new_bits = + static_cast(((1U << bit_size_delta) - 1U) << (last_byte_bit_fill_ + 1)); + data_[size_ - 1] = (existing_byte & existing_bits_mask) | new_bits; + } + else + { + data_[size_ - 1] = existing_byte & existing_bits_mask; + } + } + size_ = byte_sized; + last_byte_bit_fill_ = bit_sized; + } + (void) value; + } + // else no change + } + private: constexpr bool ensure_size_plus_one() { - if (capacity_ > 0 && (last_byte_bit_fill_ < 8 || size_ < capacity_)) + if (capacity_ > 0 && (last_byte_bit_fill_ < 7 || size_ < capacity_)) { // we have at least one byte of capacity (first allocation) // and we have room in the last byte or we have room for another byte @@ -2133,7 +2233,7 @@ class VariableLengthArray : protected VariableLengthArrayBase= capacity_) @@ -2141,18 +2241,8 @@ class VariableLengthArray : protected VariableLengthArrayBase 0 as long as there is at least one bit in the array. That is, - // it will cycle from 1 to 8 and back to 1 as our byte count, size_, - // increases. - - // zero the next byte we're about to start using so comparisons - // don't use uninitialized bits. - data_[size_] = 0; ++size_; - last_byte_bit_fill_ = 1; + last_byte_bit_fill_ = 0; } else { @@ -2173,11 +2263,11 @@ class VariableLengthArray : protected VariableLengthArrayBase 0, - "CDE_vla_003: last_byte_bit_fill_ cannot be zero unless size_ is."); - return (size_ == 0) ? 0 : ((size_ - 1) * 8U) + last_byte_bit_fill_; + CETL_DEBUG_ASSERT(size_ != 0 || last_byte_bit_fill_ == 0, + "CDE_vla_003: last_byte_bit_fill_ should always be zero when size_ is."); + return (size_ == 0) ? 0 : ((size_ - 1) * 8U) + (last_byte_bit_fill_ + 1); } constexpr size_type capacity_bits() const noexcept @@ -2185,7 +2275,10 @@ class VariableLengthArray : protected VariableLengthArrayBase