diff --git a/cetlvast/suites/docs/examples/example_08_variable_length_array_vs_vector.cpp b/cetlvast/suites/docs/examples/example_08_variable_length_array_vs_vector.cpp index 4e1090e3..5ef78f93 100644 --- a/cetlvast/suites/docs/examples/example_08_variable_length_array_vs_vector.cpp +++ b/cetlvast/suites/docs/examples/example_08_variable_length_array_vs_vector.cpp @@ -83,8 +83,10 @@ TEST(example_08_variable_length_array_vs_vector, example_tight_fit_1) cetl::pf17::pmr::UnsynchronizedArrayMemoryResource<56> array_storage_1{}; cetl::pf17::pmr::UnsynchronizedArrayMemoryResource<56> array_storage_0{&array_storage_1, array_storage_1.max_size()}; - cetl::VariableLengthArray> tight_fit{{&array_storage_0}, - array_storage_0.size()}; + cetl::VariableLengthArray> tight_fit{ + array_storage_0.size(), + {&array_storage_0}, + }; for (std::size_t i = 0; i < 56; ++i) { std::cout << i << ", "; @@ -102,8 +104,10 @@ TEST(example_08_variable_length_array_vs_vector, example_exact_fit) /// resource. cetl::pf17::pmr::UnsynchronizedArrayMemoryResource<56> array_storage_0{}; - cetl::VariableLengthArray> exact_fit{{&array_storage_0}, - array_storage_0.size()}; + cetl::VariableLengthArray> exact_fit{ + array_storage_0.size(), + {&array_storage_0}, + }; exact_fit.reserve(56); for (std::size_t i = 0; i < 56; ++i) { diff --git a/cetlvast/suites/unittest/test_variable_length_array_allocator_awareness.cpp b/cetlvast/suites/unittest/test_variable_length_array_allocator_awareness.cpp deleted file mode 100644 index 809cc2ba..00000000 --- a/cetlvast/suites/unittest/test_variable_length_array_allocator_awareness.cpp +++ /dev/null @@ -1,42 +0,0 @@ -/// @file -/// Covers edge cases not covered by other tests for allocator-aware features -/// of cetl::VariableLengthArray. -/// -/// @copyright -/// Copyright (C) OpenCyphal Development Team -/// Copyright Amazon.com Inc. or its affiliates. -/// SPDX-License-Identifier: MIT -/// - -#include "cetl/variable_length_array.hpp" -#include "cetl/pf17/sys/memory_resource.hpp" - -#include "gtest/gtest.h" -#include "gmock/gmock.h" - -#include -#include - -// +---------------------------------------------------------------------------+ -// | TEST FIXTURES -// +---------------------------------------------------------------------------+ - -TEST(VLAAllocatorAwarenessTest, UsesPMAForItems) -{ - std::string::value_type buffer[100]; - cetl::pf17::pmr::monotonic_buffer_resource resource{buffer, sizeof(buffer)}; - cetl::VariableLengthArray> vla{&resource}; - vla.reserve(3); - vla.emplace_back("Hello"); - vla.emplace_back(" "); - vla.emplace_back("World"); - - ASSERT_EQ(3, vla.size()); - - // Verify that the strings created within the array are using the buffer. - for(const std::string& item : vla) - { - ASSERT_GE(item.c_str(), buffer); - ASSERT_LT(item.c_str(), buffer + sizeof(buffer)); - } -} diff --git a/cetlvast/suites/unittest/test_variable_length_array_bool.cpp b/cetlvast/suites/unittest/test_variable_length_array_bool.cpp index 384900fb..a2d1b43d 100644 --- a/cetlvast/suites/unittest/test_variable_length_array_bool.cpp +++ b/cetlvast/suites/unittest/test_variable_length_array_bool.cpp @@ -340,3 +340,27 @@ TYPED_TEST(VLABoolTests, TestBoolBack) const_cast::type>::type>(&array)->back(); ASSERT_TRUE(value); } + +TYPED_TEST(VLABoolTests, TestAssignCountAndValue) +{ + auto array = TypeParam::make_bool_container(); + array.assign(0, true); + ASSERT_EQ(0, array.size()); + array.assign(1, false); + ASSERT_EQ(1, array.size()); + ASSERT_FALSE(array[0]); + array.resize(9, false); + ASSERT_EQ(9, array.size()); + array.assign(3, false); + ASSERT_EQ(3, array.size()); + for(auto i = array.begin(), e = array.end(); i != e; ++i) + { + ASSERT_FALSE(*i); + } + array.assign(17, true); + ASSERT_EQ(17, array.size()); + for(auto i = array.begin(), e = array.end(); i != e; ++i) + { + ASSERT_TRUE(*i); + } +} diff --git a/cetlvast/suites/unittest/test_variable_length_array_compat.cpp b/cetlvast/suites/unittest/test_variable_length_array_compat.cpp index 45695f67..c09919be 100644 --- a/cetlvast/suites/unittest/test_variable_length_array_compat.cpp +++ b/cetlvast/suites/unittest/test_variable_length_array_compat.cpp @@ -29,806 +29,22 @@ #endif // +-------------------------------------------------------------------------------------------------------------------+ -// | standard allocator with max_size defined. +// | INTEGER LIKE PRIMITIVE TYPES // +-------------------------------------------------------------------------------------------------------------------+ -template -struct MaxAllocator : public std::allocator -{ - template - struct rebind - { - typedef MaxAllocator other; - }; - - typename std::allocator::size_type max_size() const noexcept - { - return max_size_v; - } -}; - -// +-------------------------------------------------------------------------------------------------------------------+ -// | memory_resource FACTORIES -// +-------------------------------------------------------------------------------------------------------------------+ - -/// Provides null_memory_resource -struct NullResourceFactory -{ - template - struct Bind - {}; - - template - struct Bind::type>::value>::type> - { - static constexpr std::size_t expected_max_size() noexcept - { - return 0; - } - - static constexpr typename std::add_pointer::type resource( - NullResourceFactory& resource_factory) - { - (void) resource_factory; - return cetl::pf17::pmr::null_memory_resource(); - } - - static constexpr typename std::add_pointer::type resource( - NullResourceFactory& resource_factory, - NullResourceFactory& upstream) - { - (void) upstream; - return resource(resource_factory); - } - - static constexpr Allocator make_allocator(NullResourceFactory& resource_factory, NullResourceFactory& upstream) - { - return Allocator{resource(resource_factory, upstream)}; - } - }; - - template - struct Bind, Allocator>::value>::type> - { - static constexpr Allocator make_allocator(NullResourceFactory& resource_factory, NullResourceFactory& upstream) - { - (void) resource_factory; - (void) upstream; - return Allocator{}; - } - }; -}; - -// +-------------------------------------------------------------------------------------------------------------------+ - -// Creates a standard allocator that implements max_size() and returns the given MaxSizeValue. -template -struct MaxSizeResourceFactory -{ - template - struct Bind - {}; - - template - struct Bind::type>::value>::type> - { - static constexpr std::size_t expected_max_size() noexcept - { - return MaxSizeValue; - } - - static constexpr typename std::add_pointer::type resource( - MaxSizeResourceFactory& resource_factory) - { - (void) resource_factory; - return cetl::pf17::pmr::null_memory_resource(); - } - - static constexpr typename std::add_pointer::type resource( - MaxSizeResourceFactory& resource_factory, - NullResourceFactory& upstream) - { - (void) upstream; - return resource(resource_factory); - } - - static constexpr Allocator make_allocator(MaxSizeResourceFactory& resource_factory, - UpstreamResourceFactoryType& upstream) - { - return Allocator{resource(resource_factory, upstream)}; - } - }; - - template - struct Bind, Allocator>::value>::type> - { - static constexpr std::size_t expected_max_size() noexcept - { - return MaxSizeValue; - } - - static constexpr Allocator make_allocator(MaxSizeResourceFactory& resource_factory, - NullResourceFactory& upstream) - { - (void) resource_factory; - (void) upstream; - return Allocator{}; - } - }; -}; - -// +-------------------------------------------------------------------------------------------------------------------+ - -/// Creates a polymorphic allocator that uses the new_delete_resource. -struct CetlNewDeleteResourceFactory -{ - template - struct Bind - {}; - - template - struct Bind - { - static constexpr std::size_t expected_max_size() noexcept - { - return std::numeric_limits::max() / sizeof(typename Allocator::value_type); - } - - static constexpr typename std::add_pointer::type resource( - CetlNewDeleteResourceFactory& resource_factory, - NullResourceFactory& upstream) - { - (void) upstream; - (void) resource_factory; - return cetl::pf17::pmr::new_delete_resource(); - } - - static constexpr Allocator make_allocator(CetlNewDeleteResourceFactory& resource_factory, - NullResourceFactory& upstream) - { - return Allocator{resource(resource_factory, upstream)}; - } - }; -}; - -// +-------------------------------------------------------------------------------------------------------------------+ - -/// Creates a polymorphic allocator that uses a new_delete_resource that does not implement realloc. -struct CetlNoReallocNewDeleteResourceFactory -{ - template - struct Bind - {}; - - template - struct Bind - { - static constexpr std::size_t expected_max_size() noexcept - { - return std::numeric_limits::max() / sizeof(typename Allocator::value_type); - } - - static constexpr typename std::add_pointer::type resource( - CetlNoReallocNewDeleteResourceFactory& resource_factory, - NullResourceFactory& upstream) - { - (void) upstream; - (void) resource_factory; - return &resource_factory.memory_resoure_; - } - - static constexpr Allocator make_allocator(CetlNoReallocNewDeleteResourceFactory& resource_factory, - NullResourceFactory& upstream) - { - return Allocator{resource(resource_factory, upstream)}; - } - }; - -private: - cetlvast::MaxAlignNewDeleteResourceWithoutRealloc memory_resoure_{}; -}; - -// +-------------------------------------------------------------------------------------------------------------------+ - -/// Creates a polymorphic allocator that uses an UnsynchronizedBufferMemoryResourceDelegate-based memory resource. -template -class CetlUnsynchronizedArrayMemoryResourceFactory -{ -private: - template - class ResourceImpl : public cetl::pf17::pmr::memory_resource - { - public: - explicit ResourceImpl(cetl::pf17::pmr::memory_resource* upstream) - : memory_{} - , delegate_{memory_.data(), - memory_.size(), - upstream, - cetl::pf17::pmr::deviant::memory_resource_traits::max_size( - *upstream)} - { - } - - private: - void* do_allocate(std::size_t bytes, std::size_t alignment) override - { - return delegate_.allocate(bytes, alignment); - } - - void do_deallocate(void* p, std::size_t bytes, std::size_t alignment) override - { - delegate_.deallocate(p, bytes, alignment); - } - - bool do_is_equal(const cetl::pf17::pmr::memory_resource& other) const noexcept override - { - return (this == &other); - } - - std::size_t do_max_size() const noexcept override - { - return delegate_.max_size(); - } - - void* do_reallocate(void* p, - std::size_t old_size_bytes, - std::size_t new_size_bytes, - std::size_t alignment) override - { - return delegate_.reallocate(p, old_size_bytes, new_size_bytes, alignment); - } - - std::array memory_; - cetl::pmr::UnsynchronizedBufferMemoryResourceDelegate delegate_; - }; - -public: - template - struct Bind - { - static constexpr std::size_t expected_max_size() noexcept - { - return ArraySizeBytes / sizeof(typename Allocator::value_type); - } - - static constexpr typename std::add_pointer::type resource( - CetlUnsynchronizedArrayMemoryResourceFactory& resource_factory, - UpstreamResourceFactoryType& upstream) - { - auto resource = std::make_unique>( - UpstreamResourceFactoryType::template Bind::resource(upstream)); - resource_factory.resources_.push_back(std::move(resource)); - return resource_factory.resources_.back().get(); - } - static constexpr Allocator make_allocator(CetlUnsynchronizedArrayMemoryResourceFactory& resource_factory, - UpstreamResourceFactoryType& upstream) - { - return Allocator{resource(resource_factory, upstream)}; - } - }; - -private: - std::vector>> resources_; -}; - -// +-------------------------------------------------------------------------------------------------------------------+ - -#if (__cplusplus >= CETL_CPP_STANDARD_17) - -/// Creates a polymorphic allocator that uses std::pmr::new_delete_resource. -struct StdNewDeleteResourceFactory -{ - template - struct Bind - {}; - - template - struct Bind - { - static constexpr std::size_t expected_max_size() noexcept - { - return std::numeric_limits::max() / sizeof(typename Allocator::value_type); - } - - static constexpr std::add_pointer::type resource( - StdNewDeleteResourceFactory& resource_factory, - NullResourceFactory& upstream) - { - (void) upstream; - (void) resource_factory; - return std::pmr::new_delete_resource(); - } - - static constexpr Allocator make_allocator(StdNewDeleteResourceFactory& resource_factory, - NullResourceFactory& upstream) - { - return Allocator{resource(resource_factory, upstream)}; - } - }; -}; -#endif - -// +-------------------------------------------------------------------------------------------------------------------+ -// | TEST VALUE TYPES -// +-------------------------------------------------------------------------------------------------------------------+ - -/// A type that is not trivially constructable for use in test subject containers. -class NotTriviallyConstructable -{ -public: - NotTriviallyConstructable() - : data_{} - { - } - - NotTriviallyConstructable(std::size_t value) - : data_{std::to_string(value)} - { - } - - virtual ~NotTriviallyConstructable() = default; - - NotTriviallyConstructable(const NotTriviallyConstructable& rhs) - : data_{rhs.data_} - { - } - - NotTriviallyConstructable(NotTriviallyConstructable&& rhs) - : data_{std::move(rhs.data_)} - { - } - - NotTriviallyConstructable& operator=(const NotTriviallyConstructable& rhs) - { - data_ = rhs.data_; - return *this; - } - - NotTriviallyConstructable& operator=(NotTriviallyConstructable&& rhs) - { - data_ = std::move(rhs.data_); - return *this; - } - - virtual const char* what() const noexcept - { - return data_.c_str(); - } - - operator std::size_t() const noexcept - { - if (data_.empty()) - { - return 0; - } - else - { - return std::stoul(data_); - } - } - - NotTriviallyConstructable& operator=(std::size_t value) - { - data_ = std::to_string(value); - return *this; - } - -private: - std::string data_; -}; - -static_assert(!std::is_trivially_constructible::value, - "NotTriviallyConstructable must not be trivially constructable"); - -// +-------------------------------------------------------------------------------------------------------------------+ -// | TYPED TEST PROTOCOL -// +-------------------------------------------------------------------------------------------------------------------+ - -/// The primary test protocol. Each test case will have a single VLA (the subject) with the given value_type and -/// allocator_type. The allocator_type will be constructed with the given memory_resource_factory_type and that -/// memory resource will be given the providing memory_resource_upstream_factory_type. -template -struct TestAllocatorType -{ - using container_type = ContainerType; - using allocator_type = Allocator; - using value_type = typename Allocator::value_type; - using memory_resource_factory_type = MemoryResourceFactoryType; - using memory_resource_upstream_factory_type = UpstreamMemoryResourceFactoryType; -}; - -// +-------------------------------------------------------------------------------------------------------------------+ -// | TEST SUITE -// +-------------------------------------------------------------------------------------------------------------------+ - -/// -/// Test suite for running multiple allocators against the variable length array type. -/// -template -class VLATestsGeneric : public ::testing::Test -{ -public: - // Some tests will run std::min(maximumMaxSize, get_expected_max_size()) to avoid - // trying to run std::numeric_limits::max() loops or allocate that much memory. - constexpr static std::size_t maximumMaxSize = 1024; - using MemoryResourceFactoryType = typename T::memory_resource_factory_type; - using MemoryResourceFactoryTypePtr = typename std::add_pointer::type; - using MemoryResourceUpstreamFactoryType = typename T::memory_resource_upstream_factory_type; - using MemoryResourceUpstreamFactoryTypePtr = typename std::add_pointer::type; - using Allocator = typename T::allocator_type; - using Value = typename T::value_type; - using SubjectType = typename std::conditional::value, - cetl::VariableLengthArray, - std::vector>::type; - - static void SetUpTestSuite() - { -#ifdef CETLVAST_RTTI_ENABLED - ::testing::Test::RecordProperty("TestAllocatorType", typeid(T).name()); -#else - ::testing::Test::RecordProperty("TestAllocatorType", "(RTTI disabled)"); -#endif - } - void SetUp() override - { - memoryResourceUpstreamFactory_ = std::make_unique(); - memoryResourceFactory_ = std::make_unique(); - cetlvast::InstrumentedAllocatorStatistics::reset(); - } - - // Tears down the test fixture. - void TearDown() override - { - memoryResourceFactory_.reset(); - memoryResourceUpstreamFactory_.reset(); - } - - // Get the configured maximum number of objects for the allocator. - std::size_t get_expected_max_size() const noexcept - { - // Interestingly enough, GCC and Clang disagree on the default maximum size - // for std::vector. Clang says std::numeric_limits::max() and - // GCC says std::numeric_limits::max() / sizeof(value_type). - // We'll expect the larger of the two but tests should always evaluate max_size - // as being <= the expected_max_size() to be portable. - const std::size_t clamp = std::numeric_limits::max(); - return std::min(clamp, - MemoryResourceFactoryType::template Bind:: - expected_max_size()); - } - - Allocator make_allocator() - { - return MemoryResourceFactoryType::template Bind:: - make_allocator(*memoryResourceFactory_, *memoryResourceUpstreamFactory_); - } - -private: - std::unique_ptr memoryResourceFactory_; - std::unique_ptr memoryResourceUpstreamFactory_; -}; - -template -const std::size_t VLATestsGeneric::maximumMaxSize; - -// +-------------------------------------------------------------------------------------------------------------------+ -// | TYPED TEST, TYPES -// | See comments on TestAllocatorType for the "protocol" in use here. -// +-------------------------------------------------------------------------------------------------------------------+ -// clang-format off - -using MyTypes = ::testing::Types< -/* container type tag | allocator type | primary memory resource factory */ -/* 0 */ TestAllocatorType, CetlUnsynchronizedArrayMemoryResourceFactory<24>> -/* 1 */ , TestAllocatorType, CetlUnsynchronizedArrayMemoryResourceFactory<24>> -/* 2 */ , TestAllocatorType, CetlUnsynchronizedArrayMemoryResourceFactory> -/* 3 */ , TestAllocatorType, CetlUnsynchronizedArrayMemoryResourceFactory> -/* 4 */ , TestAllocatorType, MaxSizeResourceFactory<24>> -/* 5 */ , TestAllocatorType, CetlNewDeleteResourceFactory> -/* 6 */ , TestAllocatorType, CetlNewDeleteResourceFactory> -/* 7 */ , TestAllocatorType, CetlNoReallocNewDeleteResourceFactory> - -#if (__cplusplus >= CETL_CPP_STANDARD_17) -/* 8 */ , TestAllocatorType, StdNewDeleteResourceFactory> -/* 9 */ , TestAllocatorType, StdNewDeleteResourceFactory> -#endif ->; -// clang-format on - -TYPED_TEST_SUITE(VLATestsGeneric, MyTypes, ); - -// +-------------------------------------------------------------------------------------------------------------------+ -// | TESTS -// +-------------------------------------------------------------------------------------------------------------------+ - -TYPED_TEST(VLATestsGeneric, TestReserve) -{ - typename TestFixture::SubjectType subject{TestFixture::make_allocator()}; - - ASSERT_EQ(0U, subject.capacity()); - ASSERT_EQ(0U, subject.size()); - ASSERT_GE(this->get_expected_max_size(), subject.max_size()); - subject.reserve(1); - ASSERT_LE(1U, subject.capacity()); - ASSERT_EQ(0U, subject.size()); - ASSERT_GE(this->get_expected_max_size(), subject.max_size()); -} - -// +-------------------------------------------------------------------------------------------------------------------+ - -TYPED_TEST(VLATestsGeneric, TestPush) -{ - typename TestFixture::SubjectType subject{TestFixture::make_allocator()}; - ASSERT_EQ(0U, subject.size()); - - typename decltype(subject)::value_type x = 0; - - const std::size_t clamped_max = std::min(this->get_expected_max_size(), this->maximumMaxSize); - subject.reserve(clamped_max); - for (std::size_t i = 0; i < clamped_max; ++i) - { - subject.push_back(x); - - ASSERT_EQ(i + 1, subject.size()); - ASSERT_LE(subject.size(), subject.capacity()); - - ASSERT_EQ(x, subject[i]); - x = x + 1; - } -} - -// +-------------------------------------------------------------------------------------------------------------------+ - -TYPED_TEST(VLATestsGeneric, TestPop) -{ - typename TestFixture::SubjectType subject{TestFixture::make_allocator()}; - - std::size_t clamped_max = std::min(this->get_expected_max_size(), 10UL); - ASSERT_LE(1U, clamped_max) << "This test requires a max_size of at least 1."; - subject.reserve(clamped_max); - const std::size_t reserved = subject.capacity(); - ASSERT_LE(clamped_max, subject.capacity()); - subject.push_back(1); - ASSERT_EQ(1U, subject.size()); - ASSERT_EQ(1, subject[0]); - ASSERT_EQ(1U, subject.size()); - subject.pop_back(); - ASSERT_EQ(0U, subject.size()); - ASSERT_EQ(reserved, subject.capacity()); -} - -// +-------------------------------------------------------------------------------------------------------------------+ - -TYPED_TEST(VLATestsGeneric, TestShrink) -{ - typename TestFixture::SubjectType subject{TestFixture::make_allocator()}; - - std::size_t clamped_max = std::min(this->get_expected_max_size(), 10UL); - ASSERT_LE(1U, clamped_max) << "This test requires a max_size of at least 1."; - - subject.reserve(clamped_max); - const auto reserved = subject.capacity(); - ASSERT_LE(clamped_max, reserved); - subject.push_back(1); - ASSERT_EQ(1U, subject.size()); - ASSERT_EQ(1, subject[0]); - ASSERT_EQ(1U, subject.size()); - ASSERT_EQ(reserved, subject.capacity()); - subject.shrink_to_fit(); - // shrink_to_fit implementations are not required to exactly match the size of the container, but they can't grow - // the size. - ASSERT_LE(subject.capacity(), clamped_max); -} - -// +-------------------------------------------------------------------------------------------------------------------+ - -TYPED_TEST(VLATestsGeneric, TestShrinkToSameSize) -{ - typename TestFixture::SubjectType subject{TestFixture::make_allocator()}; - - subject.reserve(1); - ASSERT_LE(1U, subject.capacity()); - subject.push_back(1); - ASSERT_EQ(1U, subject.size()); - subject.shrink_to_fit(); - ASSERT_EQ(1U, subject.size()); - ASSERT_EQ(1U, subject.capacity()); -} - -// +-------------------------------------------------------------------------------------------------------------------+ - -TYPED_TEST(VLATestsGeneric, TestCopyAssignment) -{ - if (std::is_same::value) - { - GTEST_SKIP() << "Skipping test that requires CETL reallocation support."; - } - typename TestFixture::SubjectType subject0{TestFixture::make_allocator()}; - typename TestFixture::SubjectType subject1{TestFixture::make_allocator()}; - - subject0.push_back(1); - subject0.push_back(2); - subject0.push_back(3); - - subject1 = subject0; - - ASSERT_EQ(subject0.size(), subject1.size()); - ASSERT_GE(subject0.capacity(), subject1.capacity()); - ASSERT_EQ(subject0.max_size(), subject1.max_size()); - ASSERT_EQ(subject0[0], subject1[0]); - ASSERT_EQ(subject0[1], subject1[1]); - ASSERT_EQ(subject0[2], subject1[2]); -} - -// +-------------------------------------------------------------------------------------------------------------------+ - -TYPED_TEST(VLATestsGeneric, TestOverMaxSize) -{ - typename TestFixture::SubjectType subject{TestFixture::make_allocator()}; - const std::size_t MaxSize = subject.max_size(); - if (MaxSize > TestFixture::maximumMaxSize) - { - GTEST_SKIP() << "The allocator under test has a max_size that is too large for this test."; - return; - } - if (MaxSize == 0U) - { - GTEST_SKIP() << "The allocator under test does not have a maximum size."; - return; - } - - subject.reserve(MaxSize); - - for (std::size_t i = 1; i <= MaxSize; ++i) - { - subject.push_back(static_cast(i)); - ASSERT_EQ(i, subject.size()); - ASSERT_EQ(static_cast(i), subject[i - 1]); - } - - ASSERT_EQ(MaxSize, subject.capacity()); -#if defined(__cpp_exceptions) - ASSERT_THROW(subject.reserve(MaxSize + 1), std::length_error); - ASSERT_EQ(MaxSize, subject.capacity()); -#endif - - ASSERT_EQ(MaxSize, subject.size()); - -#if defined(__cpp_exceptions) - ASSERT_THROW(subject.push_back(0), std::length_error); -#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); - } -} - -// +-------------------------------------------------------------------------------------------------------------------+ - -TYPED_TEST(VLATestsGeneric, TestFrontAndBack) -{ - using const_ref_value_type = - typename std::add_lvalue_reference::type>::type; - typename TestFixture::SubjectType subject{TestFixture::make_allocator()}; - subject.reserve(2); - subject.push_back(static_cast(1)); - subject.push_back(static_cast(2)); - ASSERT_EQ(static_cast(1), subject.front()); - typename TestFixture::SubjectType::const_reference const_front_ref = - const_cast::type>::type>( - &subject) - ->front(); - ASSERT_EQ(static_cast(1), const_front_ref); - - typename TestFixture::SubjectType::const_reference const_back_ref = - const_cast::type>::type>( - &subject) - ->back(); - ASSERT_EQ(static_cast(2), subject.back()); - ASSERT_EQ(static_cast(2), const_back_ref); -} - -// +----------------------------------------------------------------------+ - -#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. + * Test suite to ensure primitive type compatibility. */ template -class VLATestsNonTrivialCommon : public ::testing::Test +class VLATestsCompatPrimitiveTypes : public ::testing::Test {}; -using VLATestsNonTrivialCommonTypes = ::testing::Types; -TYPED_TEST_SUITE(VLATestsNonTrivialCommon, VLATestsNonTrivialCommonTypes, ); +using VLATestsCompatPrimitiveTypesTypes = ::testing::Types; +TYPED_TEST_SUITE(VLATestsCompatPrimitiveTypes, VLATestsCompatPrimitiveTypesTypes, ); -TYPED_TEST(VLATestsNonTrivialCommon, TestMoveToVector) +TYPED_TEST(VLATestsCompatPrimitiveTypes, TestMoveToVector) { cetl::VariableLengthArray> - subject{cetl::pf17::pmr::polymorphic_allocator(cetl::pf17::pmr::new_delete_resource()), 10U}; + subject{10u, cetl::pf17::pmr::polymorphic_allocator(cetl::pf17::pmr::new_delete_resource())}; subject.reserve(subject.max_size()); ASSERT_EQ(subject.capacity(), subject.max_size()); for (std::size_t i = 0; i < subject.max_size(); ++i) @@ -843,7 +59,7 @@ TYPED_TEST(VLATestsNonTrivialCommon, TestMoveToVector) } } -TYPED_TEST(VLATestsNonTrivialCommon, TestPushBackGrowsCapacity) +TYPED_TEST(VLATestsCompatPrimitiveTypes, TestPushBackGrowsCapacity) { static constexpr std::size_t PushBackItems = 9; cetl::VariableLengthArray> subject{ @@ -863,11 +79,11 @@ TYPED_TEST(VLATestsNonTrivialCommon, TestPushBackGrowsCapacity) ASSERT_LE(PushBackItems, subject.capacity()); } -TYPED_TEST(VLATestsNonTrivialCommon, TestForEachConstIterators) +TYPED_TEST(VLATestsCompatPrimitiveTypes, TestForEachConstIterators) { static constexpr std::size_t MaxSize = 9; cetl::VariableLengthArray> - subject{cetl::pf17::pmr::polymorphic_allocator(cetl::pf17::pmr::new_delete_resource()), MaxSize}; + subject{MaxSize, cetl::pf17::pmr::polymorphic_allocator(cetl::pf17::pmr::new_delete_resource())}; const auto& const_subject = subject; ASSERT_EQ(0U, const_subject.size()); ASSERT_EQ(0U, const_subject.capacity()); @@ -902,7 +118,7 @@ TYPED_TEST(VLATestsNonTrivialCommon, TestForEachConstIterators) # pragma clang diagnostic ignored "-Wself-assign-overloaded" #endif -TYPED_TEST(VLATestsNonTrivialCommon, SelfAssignment) +TYPED_TEST(VLATestsCompatPrimitiveTypes, SelfAssignment) { auto allocator = cetl::pf17::pmr::polymorphic_allocator(cetl::pf17::pmr::new_delete_resource()); cetl::VariableLengthArray> subject{allocator}; @@ -919,7 +135,32 @@ TYPED_TEST(VLATestsNonTrivialCommon, SelfAssignment) # pragma clang diagnostic pop #endif -TEST(VLATestsNonTrivialSpecific, TestDeallocSizeNonBool) +TYPED_TEST(VLATestsCompatPrimitiveTypes, TestAssignCountItems) +{ + std::allocator allocator{}; + cetl::VariableLengthArray> subject{allocator}; + const TypeParam value0 = std::numeric_limits::max(); + const TypeParam value1 = std::numeric_limits::min(); + subject.assign(16, value0); + ASSERT_EQ(16, subject.size()); + for (auto i = subject.begin(), e = subject.end(); i != e; ++i) + { + ASSERT_EQ(*i, value0); + } + subject.assign(32, value1); + ASSERT_EQ(32, subject.size()); + for (auto i = subject.begin(), e = subject.end(); i != e; ++i) + { + ASSERT_EQ(*i, value1); + } +} + +// +-------------------------------------------------------------------------------------------------------------------+ +// | ANY TYPE +// | These are just the rest of the tests. All ad-hoc and simple. +// +-------------------------------------------------------------------------------------------------------------------+ + +TEST(VLATestsCompatAnyType, TestDeallocSizeNonBool) { cetlvast::InstrumentedAllocatorStatistics& stats = cetlvast::InstrumentedAllocatorStatistics::get(); cetlvast::InstrumentedNewDeleteAllocator allocator; @@ -935,7 +176,7 @@ TEST(VLATestsNonTrivialSpecific, TestDeallocSizeNonBool) ASSERT_EQ(10U * sizeof(int), stats.last_deallocation_size_bytes); } -TEST(VLATestsNonTrivialSpecific, TestPush) +TEST(VLATestsCompatAnyType, TestPush) { cetlvast::InstrumentedAllocatorStatistics& stats = cetlvast::InstrumentedAllocatorStatistics::get(); cetlvast::InstrumentedNewDeleteAllocator allocator; @@ -996,7 +237,7 @@ class Doomed bool moved_; }; -TEST(VLATestsNonTrivialSpecific, TestDestroy) +TEST(VLATestsCompatAnyType, TestDestroy) { int dtor_called = 0; @@ -1014,7 +255,7 @@ TEST(VLATestsNonTrivialSpecific, TestDestroy) ASSERT_EQ(2, dtor_called); } -TEST(VLATestsNonTrivialSpecific, TestNonFundamental) +TEST(VLATestsCompatAnyType, TestNonFundamental) { int dtor_called = 0; @@ -1028,7 +269,7 @@ TEST(VLATestsNonTrivialSpecific, TestNonFundamental) ASSERT_EQ(1, dtor_called); } -TEST(VLATestsNonTrivialSpecific, TestNotMovable) +TEST(VLATestsCompatAnyType, TestNotMovable) { class NotMovable { @@ -1049,7 +290,7 @@ TEST(VLATestsNonTrivialSpecific, TestNotMovable) ASSERT_EQ(1U, subject.size()); } -TEST(VLATestsNonTrivialSpecific, TestMovable) +TEST(VLATestsCompatAnyType, TestMovable) { class Movable { @@ -1082,7 +323,7 @@ TEST(VLATestsNonTrivialSpecific, TestMovable) ASSERT_EQ(1, pushed->get_data()); } -TEST(VLATestsNonTrivialSpecific, TestInitializerArray) +TEST(VLATestsCompatAnyType, TestInitializerArray) { cetl::VariableLengthArray> subject{{10, 9, 8, 7, 6, 5, 4, 3, 2, 1}, std::allocator{}}; @@ -1093,7 +334,7 @@ TEST(VLATestsNonTrivialSpecific, TestInitializerArray) } } -TEST(VLATestsNonTrivialSpecific, TestCopyConstructor) +TEST(VLATestsCompatAnyType, TestCopyConstructor) { cetl::VariableLengthArray> fixture{{10, 9, 8, 7, 6, 5, 4, 3, 2, 1}, std::allocator{}}; @@ -1106,7 +347,7 @@ TEST(VLATestsNonTrivialSpecific, TestCopyConstructor) } } -TEST(VLATestsNonTrivialSpecific, TestMoveConstructor) +TEST(VLATestsCompatAnyType, TestMoveConstructor) { cetl::VariableLengthArray> fixture{{10, 9, 8, 7, 6, 5, 4, 3, 2, 1}, std::allocator{}}; @@ -1121,7 +362,7 @@ TEST(VLATestsNonTrivialSpecific, TestMoveConstructor) ASSERT_EQ(0U, fixture.capacity()); } -TEST(VLATestsNonTrivialSpecific, TestCompare) +TEST(VLATestsCompatAnyType, TestCompare) { std::allocator allocator{}; cetl::VariableLengthArray> one{{10, 9, 8, 7, 6, 5, 4, 3, 2, 1}, allocator}; @@ -1132,7 +373,7 @@ TEST(VLATestsNonTrivialSpecific, TestCompare) ASSERT_NE(one, three); } -TEST(VLATestsNonTrivialSpecific, TestFPCompare) +TEST(VLATestsCompatAnyType, TestFPCompare) { std::allocator allocator{}; cetl::VariableLengthArray> one{{1.00, 2.00}, allocator}; @@ -1145,7 +386,7 @@ TEST(VLATestsNonTrivialSpecific, TestFPCompare) ASSERT_NE(one, three); } -TEST(VLATestsNonTrivialSpecific, TestCompareBool) +TEST(VLATestsCompatAnyType, TestCompareBool) { std::allocator allocator{}; cetl::VariableLengthArray> one{{true, false, true}, allocator}; @@ -1156,7 +397,7 @@ TEST(VLATestsNonTrivialSpecific, TestCompareBool) ASSERT_NE(one, three); } -TEST(VLATestsNonTrivialSpecific, TestCopyAssignment) +TEST(VLATestsCompatAnyType, TestCopyAssignment) { std::allocator allocator{}; cetl::VariableLengthArray> lhs{{1.00}, allocator}; @@ -1170,7 +411,7 @@ TEST(VLATestsNonTrivialSpecific, TestCopyAssignment) ASSERT_EQ(lhs, rhs); } -TEST(VLATestsNonTrivialSpecific, TestMoveAssignment) +TEST(VLATestsCompatAnyType, TestMoveAssignment) { std::allocator allocator{}; cetl::VariableLengthArray> lhs{{std::string("one"), std::string("two")}, @@ -1210,7 +451,7 @@ struct NoDefault int data_; }; -TEST(VLATestsNonTrivialSpecific, TestResizeWithNoDefaultCtorData) +TEST(VLATestsCompatAnyType, TestResizeWithNoDefaultCtorData) { std::allocator allocator{}; cetl::VariableLengthArray> subject{{NoDefault{1}}, allocator}; @@ -1253,7 +494,7 @@ struct Grenade int value_; }; -TYPED_TEST(VLATestsGeneric, TestResizeExceptionFromCtorOnResize) +TEST(VLATestsCompatAnyType, TestResizeExceptionFromCtorOnResize) { std::allocator allocator{}; cetl::VariableLengthArray> subject{{Grenade{1}}, allocator}; @@ -1262,3 +503,22 @@ TYPED_TEST(VLATestsGeneric, TestResizeExceptionFromCtorOnResize) } #endif // __cpp_exceptions + +TEST(VLATestsCompatAnyType, TestAssignCountItems) +{ + std::allocator allocator{}; + cetl::VariableLengthArray> subject{allocator}; + subject.assign(25, "Hi müm"); + ASSERT_EQ(25, subject.size()); + for (auto i = subject.begin(), e = subject.end(); i != e; ++i) + { + ASSERT_EQ(*i, "Hi müm"); + } + subject.assign(7, "ciao"); + ASSERT_GT(subject.capacity(), 7) << "Assign should not shrink capacity."; + ASSERT_EQ(7, subject.size()); + for (auto i = subject.begin(), e = subject.end(); i != e; ++i) + { + ASSERT_EQ(*i, "ciao"); + } +} diff --git a/cetlvast/suites/unittest/test_variable_length_array_copy_and_move.cpp b/cetlvast/suites/unittest/test_variable_length_array_copy_and_move.cpp index 01a6b1a2..337b36d9 100644 --- a/cetlvast/suites/unittest/test_variable_length_array_copy_and_move.cpp +++ b/cetlvast/suites/unittest/test_variable_length_array_copy_and_move.cpp @@ -27,12 +27,12 @@ /// just an int. struct BoxedInt { - BoxedInt() noexcept = default; - BoxedInt(const BoxedInt&) noexcept = default; - BoxedInt(BoxedInt&&) noexcept = default; + BoxedInt() noexcept = default; + BoxedInt(const BoxedInt&) noexcept = default; + BoxedInt(BoxedInt&&) noexcept = default; BoxedInt& operator=(const BoxedInt&) noexcept = default; - BoxedInt& operator=(BoxedInt&&) noexcept = default; - ~BoxedInt() noexcept = default; + BoxedInt& operator=(BoxedInt&&) noexcept = default; + ~BoxedInt() noexcept = default; BoxedInt(int value) noexcept : value_{value} @@ -138,10 +138,10 @@ struct TypeParamDef TypeParamDef() = delete; using subject = cetlvast::AllocatorTypeParamDef; - using source = cetlvast::AllocatorTypeParamDef; + using source = cetlvast::AllocatorTypeParamDef; using subject_vla_type = cetl::VariableLengthArray; - using source_vla_type = cetl::VariableLengthArray; + using source_vla_type = cetl::VariableLengthArray; static constexpr typename subject::allocator_type make_subject_allocator() { @@ -180,17 +180,20 @@ namespace cetlvast using MyTypes = ::testing::Types< /* source value type | allocator factory | subject val. type | subject allocator factory */ /* 0 */ TypeParamDef -/* 1 */, TypeParamDef -/* 2 */, TypeParamDef -/* 3 */, TypeParamDef -/* 4 */, TypeParamDef, int, PolymorphicAllocatorNewDeleteFactory> -/* 5 */, TypeParamDef, bool, PolymorphicAllocatorNewDeleteFactory> -/* 6 */, TypeParamDef, BoxedInt, PolymorphicAllocatorNewDeleteFactory> -/* 7 */, TypeParamDef, NonTrivialBoxedInt, PolymorphicAllocatorNewDeleteFactory> -/* 8 */, TypeParamDef -/* 9 */, TypeParamDef -/* 10 */, TypeParamDef -/* 11 */, TypeParamDef +/* 1 */, TypeParamDef +/* 2 */, TypeParamDef +/* 3 */, TypeParamDef +/* 4 */, TypeParamDef +/* 5 */, TypeParamDef, int, PolymorphicAllocatorNewDeleteFactory> +/* 6 */, TypeParamDef, char, PolymorphicAllocatorNewDeleteFactory> +/* 7 */, TypeParamDef, bool, PolymorphicAllocatorNewDeleteFactory> +/* 8 */, TypeParamDef, BoxedInt, PolymorphicAllocatorNewDeleteFactory> +/* 9 */, TypeParamDef, NonTrivialBoxedInt, PolymorphicAllocatorNewDeleteFactory> +/* 10 */, TypeParamDef +/* 11 */, TypeParamDef +/* 12 */, TypeParamDef +/* 13 */, TypeParamDef +/* 14 */, TypeParamDef >; } // namespace cetlvast @@ -285,6 +288,18 @@ TYPED_TEST(VLACopyMoveTests, CopyAssignReplaceWithMoreWithAdequateCapacity) EXPECT_EQ(subject, source); } +// +---------------------------------------------------------------------------+ + +TYPED_TEST(VLACopyMoveTests, CopyAssignValueMethod) +{ + typename TypeParam::subject_vla_type subject{{0, 1, 0, 1}, TypeParam::make_subject_allocator()}; + typename TypeParam::source_vla_type::value_type source{1}; + subject.assign(6, source); + std::for_each(subject.cbegin(), subject.cend(), [](const typename TypeParam::source_vla_type::value_type& value) { + ASSERT_EQ(value, 1); + }); +} + // +---------------------------------------------------------------------------+ // | TEST CASES :: Move Construction // +---------------------------------------------------------------------------+ @@ -346,7 +361,7 @@ TYPED_TEST(VLACopyMoveTests, MoveAssignWithAdequateCapacity) TYPED_TEST(VLACopyMoveTests, MoveAssignSelf) { - typename TypeParam::source_vla_type subject{{0, 1, 0, 1, 0, 1, 0, 1, 0}, TypeParam::make_source_allocator()}; + typename TypeParam::source_vla_type subject{{0, 1, 0, 1, 0, 1, 0, 1, 0}, TypeParam::make_source_allocator()}; EXPECT_EQ(subject.size(), 9); subject = std::move(subject); EXPECT_EQ(subject.size(), 9); diff --git a/cetlvast/suites/unittest/test_variable_length_array_general_allocation.cpp b/cetlvast/suites/unittest/test_variable_length_array_general_allocation.cpp new file mode 100644 index 00000000..aa928916 --- /dev/null +++ b/cetlvast/suites/unittest/test_variable_length_array_general_allocation.cpp @@ -0,0 +1,860 @@ +/// @file +/// Unit tests that confirm cetl::VariableLengthArray behaviour versus std::vector. +/// +/// @copyright +/// Copyright (C) OpenCyphal Development Team +/// Copyright Amazon.com Inc. or its affiliates. +/// SPDX-License-Identifier: MIT +/// +// cSpell: words wself + +#include "cetl/variable_length_array.hpp" + +#include "cetl/pf17/sys/memory_resource.hpp" +#include "cetl/pmr/buffer_memory_resource_delegate.hpp" +#include "cetl/pf17/byte.hpp" + +#include "cetlvast/helpers_gtest.hpp" +#include "cetlvast/helpers_gtest_memory_resource.hpp" + +#include +#include +#include +#include +#include +#include +#include +#if (__cplusplus >= CETL_CPP_STANDARD_17) +# include +#endif + +// +-------------------------------------------------------------------------------------------------------------------+ +// | standard allocator with max_size defined. +// +-------------------------------------------------------------------------------------------------------------------+ + +template +struct MaxAllocator : public std::allocator +{ + template + struct rebind + { + typedef MaxAllocator other; + }; + + typename std::allocator::size_type max_size() const noexcept + { + return max_size_v; + } +}; + +// +-------------------------------------------------------------------------------------------------------------------+ +// | memory_resource FACTORIES +// +-------------------------------------------------------------------------------------------------------------------+ + +/// Provides null_memory_resource +struct NullResourceFactory +{ + template + struct Bind + {}; + + template + struct Bind::type>::value>::type> + { + static constexpr std::size_t expected_max_size() noexcept + { + return 0; + } + + static constexpr typename std::add_pointer::type resource( + NullResourceFactory& resource_factory) + { + (void) resource_factory; + return cetl::pf17::pmr::null_memory_resource(); + } + + static constexpr typename std::add_pointer::type resource( + NullResourceFactory& resource_factory, + NullResourceFactory& upstream) + { + (void) upstream; + return resource(resource_factory); + } + + static constexpr Allocator make_allocator(NullResourceFactory& resource_factory, NullResourceFactory& upstream) + { + return Allocator{resource(resource_factory, upstream)}; + } + }; + + template + struct Bind, Allocator>::value>::type> + { + static constexpr Allocator make_allocator(NullResourceFactory& resource_factory, NullResourceFactory& upstream) + { + (void) resource_factory; + (void) upstream; + return Allocator{}; + } + }; +}; + +// +-------------------------------------------------------------------------------------------------------------------+ + +// Creates a standard allocator that implements max_size() and returns the given MaxSizeValue. +template +struct MaxSizeResourceFactory +{ + template + struct Bind + {}; + + template + struct Bind::type>::value>::type> + { + static constexpr std::size_t expected_max_size() noexcept + { + return MaxSizeValue; + } + + static constexpr typename std::add_pointer::type resource( + MaxSizeResourceFactory& resource_factory) + { + (void) resource_factory; + return cetl::pf17::pmr::null_memory_resource(); + } + + static constexpr typename std::add_pointer::type resource( + MaxSizeResourceFactory& resource_factory, + NullResourceFactory& upstream) + { + (void) upstream; + return resource(resource_factory); + } + + static constexpr Allocator make_allocator(MaxSizeResourceFactory& resource_factory, + UpstreamResourceFactoryType& upstream) + { + return Allocator{resource(resource_factory, upstream)}; + } + }; + + template + struct Bind, Allocator>::value>::type> + { + static constexpr std::size_t expected_max_size() noexcept + { + return MaxSizeValue; + } + + static constexpr Allocator make_allocator(MaxSizeResourceFactory& resource_factory, + NullResourceFactory& upstream) + { + (void) resource_factory; + (void) upstream; + return Allocator{}; + } + }; +}; + +// +-------------------------------------------------------------------------------------------------------------------+ + +/// Creates a polymorphic allocator that uses the new_delete_resource. +struct CetlNewDeleteResourceFactory +{ + template + struct Bind + {}; + + template + struct Bind + { + static constexpr std::size_t expected_max_size() noexcept + { + return std::numeric_limits::max() / sizeof(typename Allocator::value_type); + } + + static constexpr typename std::add_pointer::type resource( + CetlNewDeleteResourceFactory& resource_factory, + NullResourceFactory& upstream) + { + (void) upstream; + (void) resource_factory; + return cetl::pf17::pmr::new_delete_resource(); + } + + static constexpr Allocator make_allocator(CetlNewDeleteResourceFactory& resource_factory, + NullResourceFactory& upstream) + { + return Allocator{resource(resource_factory, upstream)}; + } + }; +}; + +// +-------------------------------------------------------------------------------------------------------------------+ + +/// Creates a polymorphic allocator that uses a new_delete_resource that does not implement realloc. +struct CetlNoReallocNewDeleteResourceFactory +{ + template + struct Bind + {}; + + template + struct Bind + { + static constexpr std::size_t expected_max_size() noexcept + { + return std::numeric_limits::max() / sizeof(typename Allocator::value_type); + } + + static constexpr typename std::add_pointer::type resource( + CetlNoReallocNewDeleteResourceFactory& resource_factory, + NullResourceFactory& upstream) + { + (void) upstream; + (void) resource_factory; + return &resource_factory.memory_resoure_; + } + + static constexpr Allocator make_allocator(CetlNoReallocNewDeleteResourceFactory& resource_factory, + NullResourceFactory& upstream) + { + return Allocator{resource(resource_factory, upstream)}; + } + }; + +private: + cetlvast::MaxAlignNewDeleteResourceWithoutRealloc memory_resoure_{}; +}; + +// +-------------------------------------------------------------------------------------------------------------------+ + +/// Creates a polymorphic allocator that uses an UnsynchronizedBufferMemoryResourceDelegate-based memory resource. +template +class CetlUnsynchronizedArrayMemoryResourceFactory +{ +private: + template + class ResourceImpl : public cetl::pf17::pmr::memory_resource + { + public: + explicit ResourceImpl(cetl::pf17::pmr::memory_resource* upstream) + : memory_{} + , delegate_{memory_.data(), + memory_.size(), + upstream, + cetl::pf17::pmr::deviant::memory_resource_traits::max_size( + *upstream)} + { + } + + private: + void* do_allocate(std::size_t bytes, std::size_t alignment) override + { + return delegate_.allocate(bytes, alignment); + } + + void do_deallocate(void* p, std::size_t bytes, std::size_t alignment) override + { + delegate_.deallocate(p, bytes, alignment); + } + + bool do_is_equal(const cetl::pf17::pmr::memory_resource& other) const noexcept override + { + return (this == &other); + } + + std::size_t do_max_size() const noexcept override + { + return delegate_.max_size(); + } + + void* do_reallocate(void* p, + std::size_t old_size_bytes, + std::size_t new_size_bytes, + std::size_t alignment) override + { + return delegate_.reallocate(p, old_size_bytes, new_size_bytes, alignment); + } + + std::array memory_; + cetl::pmr::UnsynchronizedBufferMemoryResourceDelegate delegate_; + }; + +public: + template + struct Bind + { + static constexpr std::size_t expected_max_size() noexcept + { + return ArraySizeBytes / sizeof(typename Allocator::value_type); + } + + static constexpr typename std::add_pointer::type resource( + CetlUnsynchronizedArrayMemoryResourceFactory& resource_factory, + UpstreamResourceFactoryType& upstream) + { + auto resource = std::make_unique>( + UpstreamResourceFactoryType::template Bind::resource(upstream)); + resource_factory.resources_.push_back(std::move(resource)); + return resource_factory.resources_.back().get(); + } + static constexpr Allocator make_allocator(CetlUnsynchronizedArrayMemoryResourceFactory& resource_factory, + UpstreamResourceFactoryType& upstream) + { + return Allocator{resource(resource_factory, upstream)}; + } + }; + +private: + std::vector>> resources_; +}; + +// +-------------------------------------------------------------------------------------------------------------------+ + +#if (__cplusplus >= CETL_CPP_STANDARD_17) + +/// Creates a polymorphic allocator that uses std::pmr::new_delete_resource. +struct StdNewDeleteResourceFactory +{ + template + struct Bind + {}; + + template + struct Bind + { + static constexpr std::size_t expected_max_size() noexcept + { + return std::numeric_limits::max() / sizeof(typename Allocator::value_type); + } + + static constexpr std::add_pointer::type resource( + StdNewDeleteResourceFactory& resource_factory, + NullResourceFactory& upstream) + { + (void) upstream; + (void) resource_factory; + return std::pmr::new_delete_resource(); + } + + static constexpr Allocator make_allocator(StdNewDeleteResourceFactory& resource_factory, + NullResourceFactory& upstream) + { + return Allocator{resource(resource_factory, upstream)}; + } + }; +}; +#endif + +// +-------------------------------------------------------------------------------------------------------------------+ +// | TEST VALUE TYPES +// +-------------------------------------------------------------------------------------------------------------------+ + +/// A type that is not trivially constructable for use in test subject containers. +class NotTriviallyConstructable +{ +public: + NotTriviallyConstructable() + : data_{} + { + } + + NotTriviallyConstructable(std::size_t value) + : data_{std::to_string(value)} + { + } + + virtual ~NotTriviallyConstructable() = default; + + NotTriviallyConstructable(const NotTriviallyConstructable& rhs) + : data_{rhs.data_} + { + } + + NotTriviallyConstructable(NotTriviallyConstructable&& rhs) + : data_{std::move(rhs.data_)} + { + } + + NotTriviallyConstructable& operator=(const NotTriviallyConstructable& rhs) + { + data_ = rhs.data_; + return *this; + } + + NotTriviallyConstructable& operator=(NotTriviallyConstructable&& rhs) + { + data_ = std::move(rhs.data_); + return *this; + } + + virtual const char* what() const noexcept + { + return data_.c_str(); + } + + operator std::size_t() const noexcept + { + if (data_.empty()) + { + return 0; + } + else + { + return std::stoul(data_); + } + } + + NotTriviallyConstructable& operator=(std::size_t value) + { + data_ = std::to_string(value); + return *this; + } + +private: + std::string data_; +}; + +static_assert(!std::is_trivially_constructible::value, + "NotTriviallyConstructable must not be trivially constructable"); + +// +-------------------------------------------------------------------------------------------------------------------+ +// | TYPED TEST PROTOCOL +// +-------------------------------------------------------------------------------------------------------------------+ + +/// The primary test protocol. Each test case will have a single VLA (the subject) with the given value_type and +/// allocator_type. The allocator_type will be constructed with the given memory_resource_factory_type and that +/// memory resource will be given the providing memory_resource_upstream_factory_type. +template +struct TestAllocatorType +{ + using container_type = ContainerType; + using allocator_type = Allocator; + using value_type = typename Allocator::value_type; + using memory_resource_factory_type = MemoryResourceFactoryType; + using memory_resource_upstream_factory_type = UpstreamMemoryResourceFactoryType; +}; + +// +-------------------------------------------------------------------------------------------------------------------+ +// | TEST SUITE +// +-------------------------------------------------------------------------------------------------------------------+ + +/// +/// Test suite for running multiple allocators against the variable length array type. +/// +template +class VLATestsGeneralAllocation : public ::testing::Test +{ +public: + // Default for clamped_max_size. + constexpr static std::size_t maximumMaxSize = 1024; + using MemoryResourceFactoryType = typename T::memory_resource_factory_type; + using MemoryResourceFactoryTypePtr = typename std::add_pointer::type; + using MemoryResourceUpstreamFactoryType = typename T::memory_resource_upstream_factory_type; + using MemoryResourceUpstreamFactoryTypePtr = typename std::add_pointer::type; + using Allocator = typename T::allocator_type; + using Value = typename T::value_type; + using SubjectType = typename std::conditional::value, + cetl::VariableLengthArray, + std::vector>::type; + + static void SetUpTestSuite() + { +#ifdef CETLVAST_RTTI_ENABLED + ::testing::Test::RecordProperty("TestAllocatorType", typeid(T).name()); +#else + ::testing::Test::RecordProperty("TestAllocatorType", "(RTTI disabled)"); +#endif + } + void SetUp() override + { + memoryResourceUpstreamFactory_ = std::make_unique(); + memoryResourceFactory_ = std::make_unique(); + cetlvast::InstrumentedAllocatorStatistics::reset(); + } + + // Tears down the test fixture. + void TearDown() override + { + memoryResourceFactory_.reset(); + memoryResourceUpstreamFactory_.reset(); + } + + // Get the configured maximum number of objects for the allocator. + std::size_t get_expected_max_size() const noexcept + { + // Interestingly enough, GCC and Clang disagree on the default maximum size + // for std::vector. Clang says std::numeric_limits::max() and + // GCC says std::numeric_limits::max() / sizeof(value_type). + // We'll expect the larger of the two but tests should always evaluate max_size + // as being <= the expected_max_size() to be portable. + const std::size_t clamp = std::numeric_limits::max(); + return std::min(clamp, + MemoryResourceFactoryType::template Bind:: + expected_max_size()); + } + + // Get a large size that is not larger than the maximum size for the test container. + std::size_t clamped_max_size(std::size_t max_max = maximumMaxSize) { + return std::min(get_expected_max_size(), max_max); + } + + Allocator make_allocator() + { + return MemoryResourceFactoryType::template Bind:: + make_allocator(*memoryResourceFactory_, *memoryResourceUpstreamFactory_); + } + +private: + std::unique_ptr memoryResourceFactory_; + std::unique_ptr memoryResourceUpstreamFactory_; +}; + +template +const std::size_t VLATestsGeneralAllocation::maximumMaxSize; + +// +-------------------------------------------------------------------------------------------------------------------+ +// | TYPED TEST, TYPES +// | See comments on TestAllocatorType for the "protocol" in use here. +// +-------------------------------------------------------------------------------------------------------------------+ +// clang-format off + +using MyTypes = ::testing::Types< +/* container type tag | allocator type | primary memory resource factory */ +/* 0 */ TestAllocatorType, CetlUnsynchronizedArrayMemoryResourceFactory<24>> +/* 1 */ , TestAllocatorType, CetlUnsynchronizedArrayMemoryResourceFactory<24>> +/* 2 */ , TestAllocatorType, CetlUnsynchronizedArrayMemoryResourceFactory> +/* 3 */ , TestAllocatorType, CetlUnsynchronizedArrayMemoryResourceFactory> +/* 4 */ , TestAllocatorType, MaxSizeResourceFactory<24>> +/* 5 */ , TestAllocatorType, CetlNewDeleteResourceFactory> +/* 6 */ , TestAllocatorType, CetlNewDeleteResourceFactory> +/* 7 */ , TestAllocatorType, CetlNoReallocNewDeleteResourceFactory> + +#if (__cplusplus >= CETL_CPP_STANDARD_17) +/* 8 */ , TestAllocatorType, StdNewDeleteResourceFactory> +/* 9 */ , TestAllocatorType, StdNewDeleteResourceFactory> +#endif +>; +// clang-format on + +TYPED_TEST_SUITE(VLATestsGeneralAllocation, MyTypes, ); + +// +-------------------------------------------------------------------------------------------------------------------+ +// | TESTS +// +-------------------------------------------------------------------------------------------------------------------+ + +TYPED_TEST(VLATestsGeneralAllocation, TestReserve) +{ + typename TestFixture::SubjectType subject{TestFixture::make_allocator()}; + + ASSERT_EQ(0U, subject.capacity()); + ASSERT_EQ(0U, subject.size()); + ASSERT_GE(this->get_expected_max_size(), subject.max_size()); + subject.reserve(1); + ASSERT_LE(1U, subject.capacity()); + ASSERT_EQ(0U, subject.size()); + ASSERT_GE(this->get_expected_max_size(), subject.max_size()); +} + +// +-------------------------------------------------------------------------------------------------------------------+ + +TYPED_TEST(VLATestsGeneralAllocation, TestPush) +{ + typename TestFixture::SubjectType subject{TestFixture::make_allocator()}; + ASSERT_EQ(0U, subject.size()); + + typename decltype(subject)::value_type x = 0; + + const std::size_t clamped_max = this->clamped_max_size(); + subject.reserve(clamped_max); + for (std::size_t i = 0; i < clamped_max; ++i) + { + subject.push_back(x); + + ASSERT_EQ(i + 1, subject.size()); + ASSERT_LE(subject.size(), subject.capacity()); + + ASSERT_EQ(x, subject[i]); + x = x + 1; + } +} + +// +-------------------------------------------------------------------------------------------------------------------+ + +TYPED_TEST(VLATestsGeneralAllocation, TestPop) +{ + typename TestFixture::SubjectType subject{TestFixture::make_allocator()}; + + std::size_t clamped_max = this->clamped_max_size(10ul); + ASSERT_LE(1U, clamped_max) << "This test requires a max_size of at least 1."; + subject.reserve(clamped_max); + const std::size_t reserved = subject.capacity(); + ASSERT_LE(clamped_max, subject.capacity()); + subject.push_back(1); + ASSERT_EQ(1U, subject.size()); + ASSERT_EQ(1, subject[0]); + ASSERT_EQ(1U, subject.size()); + subject.pop_back(); + ASSERT_EQ(0U, subject.size()); + ASSERT_EQ(reserved, subject.capacity()); +} + +// +-------------------------------------------------------------------------------------------------------------------+ + +TYPED_TEST(VLATestsGeneralAllocation, TestShrink) +{ + typename TestFixture::SubjectType subject{TestFixture::make_allocator()}; + + std::size_t clamped_max = this->clamped_max_size(10ul); + ASSERT_LE(1U, clamped_max) << "This test requires a max_size of at least 1."; + + subject.reserve(clamped_max); + const auto reserved = subject.capacity(); + ASSERT_LE(clamped_max, reserved); + subject.push_back(1); + ASSERT_EQ(1U, subject.size()); + ASSERT_EQ(1, subject[0]); + ASSERT_EQ(1U, subject.size()); + ASSERT_EQ(reserved, subject.capacity()); + subject.shrink_to_fit(); + // shrink_to_fit implementations are not required to exactly match the size of the container, but they can't grow + // the size. + ASSERT_LE(subject.capacity(), clamped_max); +} + +// +-------------------------------------------------------------------------------------------------------------------+ + +TYPED_TEST(VLATestsGeneralAllocation, TestShrinkToSameSize) +{ + typename TestFixture::SubjectType subject{TestFixture::make_allocator()}; + + subject.reserve(1); + ASSERT_LE(1U, subject.capacity()); + subject.push_back(1); + ASSERT_EQ(1U, subject.size()); + subject.shrink_to_fit(); + ASSERT_EQ(1U, subject.size()); + ASSERT_EQ(1U, subject.capacity()); +} + +// +-------------------------------------------------------------------------------------------------------------------+ + +TYPED_TEST(VLATestsGeneralAllocation, TestCopyAssignment) +{ + if (std::is_same::value) + { + GTEST_SKIP() << "Skipping test that requires CETL reallocation support."; + } + typename TestFixture::SubjectType subject0{TestFixture::make_allocator()}; + typename TestFixture::SubjectType subject1{TestFixture::make_allocator()}; + + subject0.push_back(1); + subject0.push_back(2); + subject0.push_back(3); + + subject1 = subject0; + + ASSERT_EQ(subject0.size(), subject1.size()); + ASSERT_GE(subject0.capacity(), subject1.capacity()); + ASSERT_EQ(subject0.max_size(), subject1.max_size()); + ASSERT_EQ(subject0[0], subject1[0]); + ASSERT_EQ(subject0[1], subject1[1]); + ASSERT_EQ(subject0[2], subject1[2]); +} + +// +-------------------------------------------------------------------------------------------------------------------+ + +TYPED_TEST(VLATestsGeneralAllocation, TestOverMaxSize) +{ + typename TestFixture::SubjectType subject{TestFixture::make_allocator()}; + const std::size_t MaxSize = subject.max_size(); + if (MaxSize > TestFixture::maximumMaxSize) + { + GTEST_SKIP() << "The allocator under test has a max_size that is too large for this test."; + return; + } + if (MaxSize == 0U) + { + GTEST_SKIP() << "The allocator under test does not have a maximum size."; + return; + } + + subject.reserve(MaxSize); + + for (std::size_t i = 1; i <= MaxSize; ++i) + { + subject.push_back(static_cast(i)); + ASSERT_EQ(i, subject.size()); + ASSERT_EQ(static_cast(i), subject[i - 1]); + } + + ASSERT_EQ(MaxSize, subject.capacity()); +#if defined(__cpp_exceptions) + ASSERT_THROW(subject.reserve(MaxSize + 1), std::length_error); + ASSERT_EQ(MaxSize, subject.capacity()); +#endif + + ASSERT_EQ(MaxSize, subject.size()); + +#if defined(__cpp_exceptions) + ASSERT_THROW(subject.push_back(0), std::length_error); +#endif +} + +// +-------------------------------------------------------------------------------------------------------------------+ + +TYPED_TEST(VLATestsGeneralAllocation, TestResize) +{ + typename TestFixture::SubjectType subject{TestFixture::make_allocator()}; + + std::size_t clamped_max = this->clamped_max_size(10ul); + ASSERT_GT(clamped_max, 0) << "This test is only valid if 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(VLATestsGeneralAllocation, TestResizeToZero) +{ + typename TestFixture::SubjectType subject{TestFixture::make_allocator()}; + + std::size_t clamped_max = this->clamped_max_size(10UL); + ASSERT_GT(clamped_max, 0) << "This test is only valid if 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(VLATestsGeneralAllocation, 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 = this->clamped_max_size(10UL); + ASSERT_GT(clamped_max, 1) << "This test is only valid if 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); + } +} + +// +-------------------------------------------------------------------------------------------------------------------+ + +TYPED_TEST(VLATestsGeneralAllocation, TestFrontAndBack) +{ + using const_ref_value_type = + typename std::add_lvalue_reference::type>::type; + typename TestFixture::SubjectType subject{TestFixture::make_allocator()}; + subject.reserve(2); + subject.push_back(static_cast(1)); + subject.push_back(static_cast(2)); + ASSERT_EQ(static_cast(1), subject.front()); + typename TestFixture::SubjectType::const_reference const_front_ref = + const_cast::type>::type>( + &subject) + ->front(); + ASSERT_EQ(static_cast(1), const_front_ref); + + typename TestFixture::SubjectType::const_reference const_back_ref = + const_cast::type>::type>( + &subject) + ->back(); + ASSERT_EQ(static_cast(2), subject.back()); + ASSERT_EQ(static_cast(2), const_back_ref); +} + +// +-------------------------------------------------------------------------------------------------------------------+ + +#ifdef __cpp_exceptions + +TYPED_TEST(VLATestsGeneralAllocation, TestResizeExceptionLengthError) +{ + typename TestFixture::SubjectType subject{TestFixture::make_allocator()}; + ASSERT_THROW(subject.resize(subject.max_size() + 1), std::length_error); +} + +#endif // __cpp_exceptions + +// +-------------------------------------------------------------------------------------------------------------------+ + +TYPED_TEST(VLATestsGeneralAllocation, TestAssignValue) +{ + typename TestFixture::SubjectType subject{TestFixture::make_allocator()}; + + std::size_t clamped_max = this->clamped_max_size(); + subject.assign(clamped_max, 1); + ASSERT_EQ(clamped_max, subject.size()); + std::for_each(subject.cbegin(), subject.cend(), [&](const typename TypeParam::value_type& value) { + ASSERT_EQ(1, value); + }); +} + +// +-------------------------------------------------------------------------------------------------------------------+ +// | AD-HOC TEST +// | Additional tests of allocation without parameterization. +// +-------------------------------------------------------------------------------------------------------------------+ + +TEST(VLATestsAdHocAllocation, UsesPMAForItems) +{ + std::string::value_type buffer[100]; + cetl::pf17::pmr::monotonic_buffer_resource resource{buffer, sizeof(buffer)}; + cetl::VariableLengthArray> vla{ + &resource}; + vla.reserve(3); + vla.emplace_back("Hello"); + vla.emplace_back(" "); + vla.emplace_back("World"); + + ASSERT_EQ(3, vla.size()); + + // Verify that the strings created within the array are using the buffer. + for (const std::string& item : vla) + { + ASSERT_GE(item.c_str(), buffer); + ASSERT_LT(item.c_str(), buffer + sizeof(buffer)); + } +} diff --git a/include/cetl/variable_length_array.hpp b/include/cetl/variable_length_array.hpp index 126fe34f..17b0a736 100644 --- a/include/cetl/variable_length_array.hpp +++ b/include/cetl/variable_length_array.hpp @@ -81,11 +81,16 @@ class VariableLengthArrayBase using is_detected = typename detector::value_t; // +----------------------------------------------------------------------+ - /// Check if one type can be trivially coped from another. + /// Check if an array of one type can be trivially coped to another. This + /// is different from std::is_trivially_assignable in that it ensures a + /// memcpy of arrays of the types will line up based on the object representation + /// and alignment of the types involved. template - struct is_trivially_copyable_from + struct is_array_of_type_trivially_copyable : std::integral_constant::value && std::is_same::value> + std::is_trivially_copyable::value && + std::is_trivially_assignable::value && + (sizeof(DstType) == sizeof(SrcType)) && (alignof(DstType) == alignof(SrcType))> {}; // +----------------------------------------------------------------------+ @@ -256,22 +261,16 @@ class VariableLengthArrayBase /// template static constexpr size_type fast_copy_assign( - DstType* const dst, - size_type dst_capacity_count, - const SrcType* const src, - size_type src_len_count, - typename std::enable_if_t::value>* = nullptr) noexcept + DstType* const dst, + size_type dst_capacity_count, + const SrcType& src) noexcept(noexcept(std::is_nothrow_assignable::value)) { - if (nullptr == dst || nullptr == src) + if (nullptr == dst) { return 0; } - const size_type max_copy_size = std::min(dst_capacity_count, src_len_count); - if (max_copy_size > 0) - { - (void) std::memcpy(dst, src, max_copy_size * sizeof(DstType)); - } - return max_copy_size; + (void) std::fill_n(dst, dst_capacity_count, src); + return dst_capacity_count; } /// @@ -283,22 +282,14 @@ class VariableLengthArrayBase DstType* const dst, size_type dst_capacity_count, const SrcType* const src, - size_type src_len_count, - typename std::enable_if_t::value>* = - nullptr) noexcept(noexcept(std::allocator_traits:: - construct(std::declval>(), - std::declval>(), - std::declval>()))) + size_type src_len_count) noexcept(noexcept(std::is_nothrow_assignable::value)) { if (nullptr == dst || nullptr == src) { return 0; } const size_type max_copy_size = std::min(dst_capacity_count, src_len_count); - for (size_type i = 0; i < max_copy_size; ++i) - { - dst[i] = src[i]; - } + (void) std::copy_n(src, max_copy_size, dst); return max_copy_size; } @@ -317,7 +308,7 @@ class VariableLengthArrayBase const SrcType* const src, size_type src_len_count, allocator_type& alloc, - typename std::enable_if_t::value>* = nullptr) noexcept + typename std::enable_if_t::value>* = nullptr) noexcept { (void) alloc; // for trivial copyable assignment is the same as construction: @@ -335,7 +326,7 @@ class VariableLengthArrayBase const SrcType* const src, size_type src_len_count, allocator_type& alloc, - typename std::enable_if_t::value>* = + typename std::enable_if_t::value>* = nullptr) noexcept(noexcept(std::allocator_traits:: construct(std::declval>(), std::declval>(), @@ -363,7 +354,7 @@ class VariableLengthArrayBase SrcType* const src, size_type src_len_count, allocator_type& alloc, - typename std::enable_if_t::value>* = nullptr) + typename std::enable_if_t::value>* = nullptr) { (void) alloc; // for trivial copyable assignment move is the same as copy: @@ -377,7 +368,7 @@ class VariableLengthArrayBase SrcType* const src, size_type src_len_count, allocator_type& alloc, - typename std::enable_if_t::value>* = nullptr) + typename std::enable_if_t::value>* = nullptr) { if (nullptr == dst || nullptr == src) { @@ -400,7 +391,7 @@ class VariableLengthArrayBase size_type dst_capacity_count, SrcType* const src, size_type src_len_count, - typename std::enable_if_t::value>* = nullptr) + typename std::enable_if_t::value>* = nullptr) { // for trivial copyable move and copy is the same. return fast_copy_assign(dst, dst_capacity_count, src, src_len_count); @@ -412,7 +403,7 @@ class VariableLengthArrayBase size_type dst_capacity_count, SrcType* const src, size_type src_len_count, - typename std::enable_if_t::value>* = nullptr) + typename std::enable_if_t::value>* = nullptr) { if (nullptr == dst || nullptr == src) { @@ -943,6 +934,9 @@ class VariableLengthArray : protected VariableLengthArrayBase /// using const_reference = typename std::add_const_t>; + // +----------------------------------------------------------------------+ + // | CONSTRUCTORS + // +----------------------------------------------------------------------+ /// Required constructor for std::uses_allocator protocol. /// @param alloc Allocator to use for all allocations. explicit VariableLengthArray(const allocator_type& alloc) noexcept @@ -951,43 +945,46 @@ class VariableLengthArray : protected VariableLengthArrayBase } /// Required constructor for std::uses_allocator protocol. - /// @param alloc Allocator to use for all allocations. /// @param max_size_max Clamping value for the maximum size of this array. That is, - /// cetl::VariableLengthArray::max_size() will return - /// `std::min(max_size_max, std::allocator_traits::max_size(alloc))` - explicit VariableLengthArray(const allocator_type& alloc, size_type max_size_max) noexcept + /// cetl::VariableLengthArray::max_size() will return + /// `std::min(max_size_max, std::allocator_traits::max_size(alloc))` + /// @param alloc Allocator to use for all allocations. + explicit VariableLengthArray(size_type max_size_max, const allocator_type& alloc) noexcept : Base(alloc, nullptr, 0, 0, max_size_max) { } - /// Initializer syntax constructor. + /// Initializer syntax constructor with maximum max size. /// @param l Initializer list of elements to copy into the array. - /// @param alloc Allocator to use for all allocations. /// @param max_size_max Clamping value for the maximum size of this array. That is, - /// cetl::VariableLengthArray::max_size() will return - /// `std::min(max_size_max, std::allocator_traits::max_size(alloc))` - VariableLengthArray(std::initializer_list l, - const allocator_type& alloc, - size_type max_size_max = std::numeric_limits::max()) + /// cetl::VariableLengthArray::max_size() will return + /// `std::min(max_size_max, std::allocator_traits::max_size(alloc))` + /// @param alloc Allocator to use for all allocations. + VariableLengthArray(std::initializer_list l, size_type max_size_max, const allocator_type& alloc) : Base(alloc, nullptr, 0, 0, max_size_max) { reserve(l.size()); size_ = Base::fast_copy_construct(data_, capacity_, l.begin(), l.size(), alloc_); } - /// Range constructor. + /// Initializer syntax constructor. + /// @param l Initializer list of elements to copy into the array. + /// @param alloc Allocator to use for all allocations. + VariableLengthArray(std::initializer_list l, const allocator_type& alloc) + : VariableLengthArray(l, std::numeric_limits::max(), alloc) + { + } + + /// Range constructor with maximum max size. /// @tparam InputIt The type of the range's iterators. /// @param first The beginning of the range. /// @param last The end of the range. - /// @param alloc Allocator to use for all allocations. /// @param max_size_max Clamping value for the maximum size of this array. That is, - /// cetl::VariableLengthArray::max_size() will return `std::min(max_size_max, - /// std::allocator_traits::max_size(alloc))` + /// cetl::VariableLengthArray::max_size() will return `std::min(max_size_max, + /// std::allocator_traits::max_size(alloc))` + /// @param alloc Allocator to use for all allocations. template - VariableLengthArray(InputIt first, - InputIt last, - const allocator_type& alloc, - size_type max_size_max = std::numeric_limits::max()) + VariableLengthArray(InputIt first, InputIt last, size_type max_size_max, const allocator_type& alloc) : Base(alloc, nullptr, 0, 0, max_size_max) { if (last >= first) @@ -1001,9 +998,20 @@ class VariableLengthArray : protected VariableLengthArrayBase } } - // - // Rule of Five. - // + /// Range constructor. + /// @tparam InputIt The type of the range's iterators. + /// @param first The beginning of the range. + /// @param last The end of the range. + /// @param alloc Allocator to use for all allocations. + template + VariableLengthArray(InputIt first, InputIt last, const allocator_type& alloc) + : VariableLengthArray(first, last, std::numeric_limits::max(), alloc) + { + } + + // +----------------------------------------------------------------------+ + // | RULE OF FIVE + // +----------------------------------------------------------------------+ VariableLengthArray(const VariableLengthArray& rhs, const allocator_type& alloc) : Base(rhs, alloc) { @@ -1050,6 +1058,9 @@ class VariableLengthArray : protected VariableLengthArrayBase } } + // +----------------------------------------------------------------------+ + // | COMPARATORS + // +----------------------------------------------------------------------+ constexpr bool operator==(const VariableLengthArray& rhs) const noexcept { if (data_ == rhs.data_) @@ -1490,6 +1501,20 @@ class VariableLengthArray : protected VariableLengthArrayBase Base::resize(count, max_size(), value); } + /// Set count elements to the given value. Grow the array if needed else + /// set the size to count without reducing capacity. + /// @param count Number of elements, starting from 0, to set. + /// @param value The value to set. + /// @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 assign(size_type count, const value_type& value) + { + const size_type size_before = size_; + resize(count, value); + const size_type back_fill_count = (size_ > size_before) ? size_before : size_; + Base::fast_copy_assign(data_, back_fill_count, value); + } + private: // +----------------------------------------------------------------------+ @@ -1773,34 +1798,35 @@ class VariableLengthArray : protected VariableLengthArrayBase; using const_iterator = IteratorImpl; - /// Required constructor for std::uses_allocator protocol. + // +----------------------------------------------------------------------+ + // | CONSTRUCTORS + // +----------------------------------------------------------------------+ + + /// Required constructor for std::uses_allocator protocol with maximum max size. + /// @param max_size_max Clamping value for the maximum size of this array. That is, + /// `std::min(max_size_max, std::allocator_traits::max_size(alloc))` + /// cetl::VariableLengthArray::max_size() will return /// @param alloc Allocator to use for all allocations. - explicit constexpr VariableLengthArray(const allocator_type& alloc) noexcept - : Base(alloc, nullptr, 0, 0, std::numeric_limits::max()) + explicit constexpr VariableLengthArray(size_type max_size_max, const allocator_type& alloc) noexcept + : Base(alloc, nullptr, 0, 0, max_size_max) , last_byte_bit_fill_{0} { } /// Required constructor for std::uses_allocator protocol. /// @param alloc Allocator to use for all allocations. - /// @param max_size_max Clamping value for the maximum size of this array. That is, - /// cetl::VariableLengthArray::max_size() will return - /// `std::min(max_size_max, std::allocator_traits::max_size(alloc))` - explicit constexpr VariableLengthArray(const allocator_type& alloc, size_type max_size_max) noexcept - : Base(alloc, nullptr, 0, 0, max_size_max) - , last_byte_bit_fill_{0} + explicit constexpr VariableLengthArray(const allocator_type& alloc) noexcept + : VariableLengthArray(std::numeric_limits::max(), alloc) { } - /// Initializer syntax constructor. + /// Initializer syntax constructor with maximum max size. /// @param l Initializer list of elements to copy into the array. - /// @param alloc Allocator to use for all allocations. /// @param max_size_max Clamping value for the maximum size of this array. That is, - /// cetl::VariableLengthArray::max_size() will return - /// `std::min(max_size_max, std::allocator_traits::max_size(alloc))` - VariableLengthArray(std::initializer_list l, - const allocator_type& alloc, - size_type max_size_max = std::numeric_limits::max()) + /// cetl::VariableLengthArray::max_size() will return + /// `std::min(max_size_max, std::allocator_traits::max_size(alloc))` + /// @param alloc Allocator to use for all allocations. + VariableLengthArray(std::initializer_list l, size_type max_size_max, const allocator_type& alloc) : Base(alloc, nullptr, 0, 0, max_size_max) , last_byte_bit_fill_{0} { @@ -1811,21 +1837,29 @@ class VariableLengthArray : protected VariableLengthArrayBase l, const allocator_type& alloc) + : VariableLengthArray(l, std::numeric_limits::max(), alloc) + { + } + + /// Range constructor with maximum max size. /// @tparam InputIt The type of the range's iterators. /// @param first The beginning of the range. /// @param last The end of the range. /// @param length The number of elements to copy from the range. - /// @param alloc Allocator to use for all allocations. /// @param max_size_max Clamping value for the maximum size of this array. That is, - /// cetl::VariableLengthArray::max_size() will return `std::min(max_size_max, - /// std::allocator_traits::max_size(alloc))` + /// cetl::VariableLengthArray::max_size() will return `std::min(max_size_max, + /// std::allocator_traits::max_size(alloc))` + /// @param alloc Allocator to use for all allocations. template VariableLengthArray(InputIt first, InputIt last, const size_type length, - const allocator_type& alloc = std::declval(), - size_type max_size_max = std::numeric_limits::max()) + size_type max_size_max, + const allocator_type& alloc) : Base(alloc, nullptr, 0, 0, max_size_max) , last_byte_bit_fill_{0} { @@ -1839,9 +1873,21 @@ class VariableLengthArray : protected VariableLengthArrayBase + VariableLengthArray(InputIt first, InputIt last, const size_type length, const allocator_type& alloc) + : VariableLengthArray(first, last, length, std::numeric_limits::max(), alloc) + { + } + + // +----------------------------------------------------------------------+ + // | RULE OF FIVE + // +----------------------------------------------------------------------+ VariableLengthArray(const VariableLengthArray& rhs, const allocator_type& alloc) : Base(rhs, alloc) , last_byte_bit_fill_{rhs.last_byte_bit_fill_} @@ -1895,6 +1941,9 @@ class VariableLengthArray : protected VariableLengthArrayBase : protected VariableLengthArrayBase size_before) ? size_before : size_); + } + private: constexpr bool ensure_size_plus_one() {