From be22c215e274ea71a3ff716cf19574b75944918c Mon Sep 17 00:00:00 2001 From: Arthur O'Dwyer Date: Wed, 21 Aug 2024 11:07:32 -0400 Subject: [PATCH] Allocator-aware inplace_vector This header is C++20-only, because it relies on `[[no_unique_address]]` to store the allocator without trickery, and it relies on `requires` to make the special members conditionally trivial. The entire `inplace_vector` test suite is included as a subset of the `aa_inplace_vector` test suite, showing that the AA version is indeed a drop-in replacement for the non-AA version. AA `inplace_vector` has an important precondition: `operator=`, as well as `swap`, has the precondition that if the allocator propagates horizontally, then the two vectors' allocators must compare equal. (Otherwise, following an assignment `OLD = NEW`, we'd end up destroying with allocator `NEW` some elements that had originally been constructed with allocator `OLD`.) We introduce a new macro `SG14_INPLACE_VECTOR_ASSERT_PRECONDITION` for this purpose. --- README.md | 29 +- include/sg14/aa_inplace_vector.h | 1039 ++++++++++++ test/CMakeLists.txt | 5 + test/aa_inplace_vector_pmr_test.cpp | 1519 +++++++++++++++++ test/aa_inplace_vector_smallsize_test.cpp | 82 + test/aa_inplace_vector_stdallocator_test.cpp | 9 + test/inplace_vector_common_tests.cpp | 1541 +++++++++++++++++ test/inplace_vector_test.cpp | 1547 +----------------- 8 files changed, 4227 insertions(+), 1544 deletions(-) create mode 100644 include/sg14/aa_inplace_vector.h create mode 100644 test/aa_inplace_vector_pmr_test.cpp create mode 100644 test/aa_inplace_vector_smallsize_test.cpp create mode 100644 test/aa_inplace_vector_stdallocator_test.cpp create mode 100644 test/inplace_vector_common_tests.cpp diff --git a/README.md b/README.md index a656ade..5211211 100644 --- a/README.md +++ b/README.md @@ -96,7 +96,7 @@ C++23 also provides `flat_multimap` and `flat_multiset`, which we don't provide. Boost also provides all four adaptors; see [`boost::container::flat_set`](https://www.boost.org/doc/libs/1_83_0/doc/html/container/non_standard_containers.html#container.non_standard_containers.flat_xxx). -### In-place vector (future > C++17) +### In-place vector (C++26 > C++17) ``` #include @@ -109,7 +109,7 @@ class sg14::inplace_vector; but under the hood it stores its elements directly in-line, like a `std::array`, instead of using the heap. This container is proposed in [P0843](https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2023/p0843r9.html) -and will likely be in C++26. The `sg14` version is portable back to C++17. +and has been adopted into the C++26 draft. The `sg14` version is portable back to C++17. Boost provides this container under the name [`boost::container::static_vector`](https://www.boost.org/doc/libs/1_83_0/doc/html/container/non_standard_containers.html#container.non_standard_containers.static_vector). @@ -121,6 +121,31 @@ you had a `std::vector` or a `sg14::inplace_vector` in order to use it; or else use an ADL call, which isn't how `std::erase` is designed to be used. The erase-remove idiom works fine for `inplace_vector`. +#### Allocator-aware in-place vector (future > C++20) + +``` +#include + +template> +class sg14::inplace_vector; +``` + +`sg14::inplace_vector>` is a drop-in replacement for +the C++17 allocator-unaware `sg14::inplace_vector`, but having allocator-awareness +allows you to do three things you can't do without it: + +- `sg14::pmr::inplace_vector` can directly `emplace_back("abc")` + with the correct allocator, just like `std::pmr::vector`. + Without allocator-awareness, you would have to write `emplace_back("abc", &mr)` + and keep track of your allocator separately alongside the data structure. + +- `sg14::inplace_vector` can use a type smaller than + `size_t` to store the size of the vector, which means a smaller memory footprint. + +- `sg14::inplace_vector` can hold Boost.Interprocess types. In this case, + `inplace_vector::iterator` will be `boost::offset_ptr` instead of `T*`. + + ### In-place type-erased types (future > C++14) ``` diff --git a/include/sg14/aa_inplace_vector.h b/include/sg14/aa_inplace_vector.h new file mode 100644 index 0000000..4156088 --- /dev/null +++ b/include/sg14/aa_inplace_vector.h @@ -0,0 +1,1039 @@ +/* + * Boost Software License - Version 1.0 - August 17th, 2003 + * + * Permission is hereby granted, free of charge, to any person or organization + * obtaining a copy of the software and accompanying documentation covered by + * this license (the "Software") to use, reproduce, display, distribute, + * execute, and transmit the Software, and to prepare derivative works of the + * Software, and to permit third-parties to whom the Software is furnished to + * do so, all subject to the following: + * + * The copyright notices in the Software and this entire statement, including + * the above license grant, this restriction and the following disclaimer, + * must be included in all copies of the Software, in whole or in part, and + * all derivative works of the Software, unless such copies or derivative + * works are solely in the form of machine-executable object code generated by + * a source language processor. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT + * SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE + * FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE, + * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ + +#pragma once + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#ifndef SG14_INPLACE_VECTOR_ASSERT_PRECONDITION +#include +#define SG14_INPLACE_VECTOR_ASSERT_PRECONDITION(x, msg) assert((x) && (msg)) +#endif + +#ifndef SG14_INPLACE_VECTOR_THROW +#include +#define SG14_INPLACE_VECTOR_THROW(x) throw (x) +#endif + +#ifndef SG14_INPLACE_VECTOR_TRIVIALLY_RELOCATABLE_IF +#if defined(__cpp_impl_trivially_relocatable) && defined(__cpp_lib_trivially_relocatable) +#define SG14_INPLACE_VECTOR_TRIVIALLY_RELOCATABLE_IF(x) [[trivially_relocatable(x)]] +#else +#define SG14_INPLACE_VECTOR_TRIVIALLY_RELOCATABLE_IF(x) +#endif // __cpp_impl_trivially_relocatable +#endif // SG14_INPLACE_VECTOR_TRIVIALLY_RELOCATABLE_IF + +namespace sg14::aaipv { + +template struct has_trivial_construct : std::true_type {}; +template requires requires (Alloc& a, T *p) { a.construct(p, std::declval()...); } + struct has_trivial_construct : std::false_type {}; +template struct has_trivial_construct, T, Us...> : + std::bool_constant>> {}; + +template struct has_trivial_destroy : std::true_type {}; +template requires requires (Alloc& a, T *p) { a.destroy(p); } + struct has_trivial_destroy : std::false_type {}; +template struct has_trivial_destroy, T> : std::true_type {}; + +template struct propagate_on_container_copy_construction : std::true_type {}; +template struct propagate_on_container_copy_construction().select_on_container_copy_construction(), void()))> : std::allocator_traits::is_always_equal {}; + +#if defined(__cpp_lib_trivially_relocatable) +template +struct be_trivially_relocatable : std::bool_constant< + std::is_trivially_relocatable_v && + std::is_trivially_relocatable_v && + ((sg14::aaipv::propagate_on_container_copy_construction::value && + std::allocator_traits::propagate_on_container_copy_assignment::value && + std::allocator_traits::propagate_on_container_move_assignment::value && + std::allocator_traits::propagate_on_container_swap::value) || + std::allocator_traits::is_always_equal::value) && + sg14::aaipv::has_trivial_construct::value && + sg14::aaipv::has_trivial_construct::value && + sg14::aaipv::has_trivial_destroy::value +> {}; + +template +struct relocate_in_insert_and_erase : std::bool_constant< + std::is_trivially_relocatable_v && + sg14::aaipv::has_trivial_construct::value && + sg14::aaipv::has_trivial_construct::value && + sg14::aaipv::has_trivial_destroy::value +> {}; +#endif // __cpp_lib_trivially_relocatable + +template +void destroy_a(Alloc alloc, It first, It last) { + using value_type = typename std::allocator_traits::value_type; + if constexpr (sg14::aaipv::has_trivial_destroy::value) { + std::destroy(first, last); + } else { + for (; first != last; ++first) { + std::allocator_traits::destroy(alloc, std::addressof(*first)); + } + } +} + +template +void uninitialized_value_construct_a(Alloc alloc, T *dfirst, T *dlast) { + using value_type = typename std::allocator_traits::value_type; + if constexpr (sg14::aaipv::has_trivial_construct::value) { + std::uninitialized_value_construct(dfirst, dlast); + } else { + T *orig_dfirst = dfirst; + while (dfirst != dlast) { + try { + std::allocator_traits::construct(alloc, dfirst); + } catch (...) { + sg14::aaipv::destroy_a(alloc, orig_dfirst, dfirst); + throw; + } + ++dfirst; + } + } +} + +template +void uninitialized_fill_a(Alloc alloc, T *dfirst, T *dlast, const T& value) { + using value_type = typename std::allocator_traits::value_type; + if constexpr (sg14::aaipv::has_trivial_construct::value) { + std::uninitialized_fill(dfirst, dlast, value); + } else { + T *orig_dfirst = dfirst; + while (dfirst != dlast) { + try { + std::allocator_traits::construct(alloc, dfirst, value); + } catch (...) { + sg14::aaipv::destroy_a(alloc, orig_dfirst, dfirst); + throw; + } + ++dfirst; + } + } +} + +template +void uninitialized_copy_a(Alloc alloc, It first, It last, Jt dfirst) { + using value_type = typename std::allocator_traits::value_type; + if constexpr (sg14::aaipv::has_trivial_construct::value) { + std::uninitialized_copy(first, last, dfirst); + } else { + Jt orig_dfirst = dfirst; + while (first != last) { + try { + std::allocator_traits::construct(alloc, std::addressof(*dfirst), *first); + } catch (...) { + sg14::aaipv::destroy_a(alloc, orig_dfirst, dfirst); + throw; + } + ++first; + ++dfirst; + } + } +} + +template +void uninitialized_copy_n_a(Alloc alloc, It first, Size n, Jt dfirst) { + using value_type = typename std::allocator_traits::value_type; + if constexpr (sg14::aaipv::has_trivial_construct::value) { + std::uninitialized_copy_n(first, n, dfirst); + } else { + Jt orig_dfirst = dfirst; + while (n) { + try { + std::allocator_traits::construct(alloc, std::addressof(*dfirst), *first); + } catch (...) { + sg14::aaipv::destroy_a(alloc, orig_dfirst, dfirst); + throw; + } + ++first; + ++dfirst; + --n; + } + } +} + +template +void ranges_uninitialized_copy_n_a(Alloc alloc, It first, Size n, Jt dfirst, std::unreachable_sentinel_t dlast) { + using value_type = typename std::allocator_traits::value_type; + if constexpr (sg14::aaipv::has_trivial_construct::value) { + std::ranges::uninitialized_copy_n(std::move(first), n, std::move(dfirst), dlast); + } else { + sg14::aaipv::uninitialized_copy_n_a(alloc, std::move(first), n, std::move(dfirst)); + } +} + +template +std::ranges::uninitialized_copy_result +ranges_uninitialized_copy_a(Alloc alloc, It first, Sent last, Jt dfirst, Sent2 dlast) { + using value_type = typename std::allocator_traits::value_type; + if constexpr (sg14::aaipv::has_trivial_construct::value) { + return std::ranges::uninitialized_copy(std::move(first), std::move(last), std::move(dfirst), std::move(dlast)); + } else { + auto orig_dfirst = dfirst; + while (first != last && dfirst != dlast) { + try { + std::allocator_traits::construct(alloc, std::addressof(*dfirst), *first); + } catch (...) { + sg14::aaipv::destroy_a(alloc, orig_dfirst, dfirst); + throw; + } + ++first; + ++dfirst; + } + return { std::move(first), std::move(dfirst) }; + } +} + +template +void uninitialized_move_a(Alloc alloc, It first, It last, Jt dfirst) { + using value_type = typename std::allocator_traits::value_type; + if constexpr (sg14::aaipv::has_trivial_construct::value) { + std::uninitialized_move(first, last, dfirst); + } else { + Jt orig_dfirst = dfirst; + while (first != last) { + try { + std::allocator_traits::construct(alloc, std::addressof(*dfirst), std::move(*first)); + } catch (...) { + sg14::aaipv::destroy_a(alloc, orig_dfirst, dfirst); + throw; + } + ++first; + ++dfirst; + } + } +} + +#if defined(__cpp_lib_trivially_relocatable) +template +void uninitialized_relocate_a(Alloc alloc, It first, It last, It dfirst) { + using value_type = typename std::allocator_traits::value_type; + if constexpr (sg14::aaipv::has_trivial_construct::value && sg14::aaipv::has_trivial_destroy::value) { + std::uninitialized_relocate(first, last, dfirst); + } else { + try { + sg14::aaipv::uninitialized_move_a(alloc, first, last, dfirst); + } catch (...) { + sg14::aaipv::destroy_a(alloc, first, last); + throw; + } + sg14::aaipv::destroy_a(alloc, first, last); + } +} +#endif // __cpp_lib_trivially_relocatable + +} // namespace sg14::aaipv + +namespace sg14 { + +template && std::is_copy_assignable_v), + bool = (std::is_move_constructible_v && std::is_move_assignable_v)> +struct ipvbase_assignable { + // Base for copyable types +}; +template +struct ipvbase_assignable { + // Base for immobile types like std::mutex + explicit ipvbase_assignable() = default; + ipvbase_assignable(ipvbase_assignable&&) = delete; + ipvbase_assignable(const ipvbase_assignable&) = delete; + void operator=(ipvbase_assignable&&) = delete; + void operator=(const ipvbase_assignable&) = delete; + ~ipvbase_assignable() = default; +}; +template +struct ipvbase_assignable { + explicit ipvbase_assignable() = default; + ipvbase_assignable(const ipvbase_assignable&) = delete; + ipvbase_assignable(ipvbase_assignable&&) = default; + void operator=(const ipvbase_assignable&) = delete; + ipvbase_assignable& operator=(ipvbase_assignable&&) = default; + ~ipvbase_assignable() = default; +}; + +template +struct ipv_alloc_holder { +#if __has_cpp_attribute(msvc::no_unique_address) + [[msvc::no_unique_address]] Alloc alloc_; +#else + [[no_unique_address]] Alloc alloc_; +#endif + explicit ipv_alloc_holder() = default; + constexpr explicit ipv_alloc_holder(const Alloc& alloc) : alloc_(alloc) {} + constexpr const Alloc& get_allocator_() const { return alloc_; } +}; + +template +struct ipv_alloc_holder> { + // libstdc++'s std::allocator is not trivially copyable. Fix that. + using Alloc = std::allocator; + explicit ipv_alloc_holder() = default; + constexpr explicit ipv_alloc_holder(const Alloc&) {} + static constexpr Alloc get_allocator_() { return Alloc(); } +}; + +template +struct ipv_data_holder { + size_type size_ = 0; + union { + char dummy_; + T data_[N]; + }; + constexpr void set_size_(size_t n) { size_ = static_cast(n); } + constexpr void swap_sizes_(ipv_data_holder& rhs) { std::swap(size_, rhs.size_); } + + // Our job here is to make the SMFs of `ipv_data_holder` all no-ops, and trivial when possible. + // Their actual semantics will be taken care of in the `ipvbase` class itself. + // + constexpr explicit ipv_data_holder() noexcept {} + constexpr ipv_data_holder(const ipv_data_holder&) noexcept {} + constexpr ipv_data_holder(ipv_data_holder&&) noexcept {} + constexpr void operator=(const ipv_data_holder&) noexcept {} + constexpr void operator=(ipv_data_holder&&) noexcept {} + constexpr ~ipv_data_holder() {} + + explicit ipv_data_holder() requires std::is_trivially_constructible_v = default; + ipv_data_holder(const ipv_data_holder&) requires std::is_trivially_copy_constructible_v = default; + ipv_data_holder(ipv_data_holder&&) requires std::is_trivially_move_constructible_v = default; + ipv_data_holder& operator=(const ipv_data_holder&) requires std::is_trivially_copy_assignable_v = default; + ipv_data_holder& operator=(ipv_data_holder&&) requires std::is_trivially_move_assignable_v = default; + ~ipv_data_holder() requires std::is_trivially_destructible_v = default; +}; + +template +struct ipv_data_holder { + static constexpr size_type size_ = 0; + static constexpr T *data_ = nullptr; + static constexpr void set_size_(size_t) {} + static constexpr void swap_sizes_(ipv_data_holder&) {} +}; + +template +struct SG14_INPLACE_VECTOR_TRIVIALLY_RELOCATABLE_IF((sg14::aaipv::be_trivially_relocatable::value)) +#if defined(_MSC_VER) + __declspec(empty_bases) +#endif + ipvbase : ipv_alloc_holder, ipv_data_holder::size_type> +{ + using size_type = typename std::allocator_traits::size_type; + using Alloc_ = ipv_alloc_holder; + using Data_ = ipv_data_holder::size_type>; + using ipv_data_holder::size_type>::data_; + using ipv_data_holder::size_type>::size_; + using ipv_data_holder::size_type>::set_size_; + using ipv_data_holder::size_type>::swap_sizes_; + using ipv_alloc_holder::get_allocator_; + + // There is a feature-test macro for "conditionally trivial SMFs," namely + // (__cpp_concepts >= 202002L); but in fact neither GCC 11.4 nor AppleClang 15 + // set that macro, despite supporting the feature (at least well enough to + // compile this code). We could check (__cplusplus >= 202002L) instead, + // except that MSVC doesn't always set *that*! So check nothing, at least for now. + // + static_assert(true, "Allocator-aware inplace_vector requires that the compiler support conditionally trivial SMFs"); + + static constexpr bool CopyCtorIsDefaultable = + ((std::is_trivially_copy_constructible_v && + sg14::aaipv::has_trivial_construct::value) || (N == 0)) && + sg14::aaipv::propagate_on_container_copy_construction::value; + + static constexpr bool MoveCtorIsDefaultable = + ((std::is_trivially_move_constructible_v && + sg14::aaipv::has_trivial_construct::value) || (N == 0)); + + static constexpr bool CopyAssignIsDefaultable = + ((std::is_trivially_copy_constructible_v && + std::is_trivially_copy_assignable_v && + std::is_trivially_destructible_v && + sg14::aaipv::has_trivial_construct::value && + sg14::aaipv::has_trivial_destroy::value) || (N == 0)) && + (std::allocator_traits::propagate_on_container_copy_assignment::value || std::allocator_traits::is_always_equal::value); + + static constexpr bool MoveAssignIsDefaultable = + ((std::is_trivially_move_constructible_v && + std::is_trivially_move_assignable_v && + std::is_trivially_destructible_v && + sg14::aaipv::has_trivial_construct::value && + sg14::aaipv::has_trivial_destroy::value) || (N == 0)) && + (std::allocator_traits::propagate_on_container_move_assignment::value || std::allocator_traits::is_always_equal::value); + + static constexpr bool DtorIsDefaultable = + ((std::is_trivially_destructible_v && + sg14::aaipv::has_trivial_destroy::value) || (N == 0)); + + static constexpr bool CopyCtorIsNoexcept = + ((std::is_nothrow_copy_constructible_v && + sg14::aaipv::has_trivial_construct::value) || (N == 0)) && + sg14::aaipv::propagate_on_container_copy_construction::value && + std::is_nothrow_copy_constructible_v>; + + static constexpr bool MoveCtorIsNoexcept = + ((std::is_nothrow_move_constructible_v && + sg14::aaipv::has_trivial_construct::value) || (N == 0)) && + std::is_nothrow_move_constructible_v>; + + // Copy-assignment follows the Lakos Rule: if the precondition might be violated, then it's not noexcept. + // + static constexpr bool CopyAssignIsNoexcept = + ((std::is_nothrow_copy_constructible_v && + std::is_nothrow_copy_assignable_v && + std::is_nothrow_destructible_v && + sg14::aaipv::has_trivial_construct::value && + sg14::aaipv::has_trivial_destroy::value) || (N == 0)) && + (std::allocator_traits::is_always_equal::value || !std::allocator_traits::propagate_on_container_copy_assignment::value); + + // Move-assignment follows the Lakos Rule: if the precondition might be violated, then it's not noexcept. + // + static constexpr bool MoveAssignIsNoexcept = + ((std::is_nothrow_move_constructible_v && + std::is_nothrow_move_assignable_v && + std::is_nothrow_destructible_v && + sg14::aaipv::has_trivial_construct::value && + sg14::aaipv::has_trivial_destroy::value) || (N == 0)) && + (std::allocator_traits::is_always_equal::value || !std::allocator_traits::propagate_on_container_move_assignment::value); + + // Swap follows the Lakos Rule: if the precondition might be violated, then it's not noexcept. + // + static constexpr bool SwapIsNoexcept = + ((std::is_nothrow_swappable_v && + std::is_nothrow_move_constructible_v && + sg14::aaipv::has_trivial_construct::value && + sg14::aaipv::has_trivial_destroy::value) || (N == 0)) && + (std::allocator_traits::is_always_equal::value || !std::allocator_traits::propagate_on_container_swap::value); + + explicit ipvbase() = default; + constexpr explicit ipvbase(const Alloc& alloc) : Alloc_(alloc) {} + constexpr ipvbase(const ipvbase& rhs) noexcept(CopyCtorIsNoexcept) + : Alloc_(std::allocator_traits::select_on_container_copy_construction(rhs.get_allocator_())), + Data_() // unneeded, but suppresses a GCC 11.4 warning about "should be explicitly initialized" + { + sg14::aaipv::uninitialized_copy_a(get_allocator_(), rhs.data_, rhs.data_ + rhs.size_, data_); + set_size_(rhs.size_); + } + constexpr ipvbase(ipvbase&& rhs) noexcept(MoveCtorIsNoexcept) + : Alloc_(std::move(rhs.get_allocator_())) + { +#if defined(__cpp_lib_trivially_relocatable) + if constexpr (std::is_trivially_relocatable_v) { + sg14::aaipv::uninitialized_relocate_a(get_allocator_(), rhs.data_, rhs.data_ + rhs.size_, data_); + set_size_(rhs.size_); + rhs.set_size_(0); + } else +#endif // __cpp_lib_trivially_relocatable + { + sg14::aaipv::uninitialized_move_a(get_allocator_(), rhs.data_, rhs.data_ + rhs.size_, data_); + set_size_(rhs.size_); + } + } + constexpr void operator=(const ipvbase& rhs) noexcept(CopyAssignIsNoexcept) + { + if constexpr (std::allocator_traits::propagate_on_container_copy_assignment::value && !std::allocator_traits::is_always_equal::value) { + SG14_INPLACE_VECTOR_ASSERT_PRECONDITION(this->get_allocator_() == rhs.get_allocator_(), "operator= tried to propagate an unequal allocator; this is UB"); + } + if (this == std::addressof(rhs)) { + // do nothing + } else if (rhs.size_ <= size_) { + std::copy(rhs.data_, rhs.data_ + rhs.size_, data_); + sg14::aaipv::destroy_a(get_allocator_(), data_ + rhs.size_, data_ + size_); + set_size_(rhs.size_); + } else { + std::copy(rhs.data_, rhs.data_ + size_, data_); + sg14::aaipv::uninitialized_copy_a(get_allocator_(), rhs.data_ + size_, rhs.data_ + rhs.size_, data_ + size_); + set_size_(rhs.size_); + } + } + constexpr void operator=(ipvbase&& rhs) noexcept(MoveAssignIsNoexcept) + { + if constexpr (std::allocator_traits::propagate_on_container_move_assignment::value && !std::allocator_traits::is_always_equal::value) { + SG14_INPLACE_VECTOR_ASSERT_PRECONDITION(this->get_allocator_() == rhs.get_allocator_(), "operator= tried to propagate an unequal allocator; this is UB"); + } + if (this == std::addressof(rhs)) { + // do nothing + } else if (rhs.size_ <= size_) { + std::move(rhs.data_, rhs.data_ + rhs.size_, data_); + sg14::aaipv::destroy_a(get_allocator_(), data_ + rhs.size_, data_ + size_); + set_size_(rhs.size_); + } else { + std::move(rhs.data_, rhs.data_ + size_, data_); + if constexpr (std::is_trivially_copyable_v && + sg14::aaipv::has_trivial_construct::value && + sg14::aaipv::has_trivial_destroy::value) { + std::uninitialized_move(rhs.data_ + size_, rhs.data_ + rhs.size_, data_ + size_); + set_size_(rhs.size_); + return; + } +#if defined(__cpp_lib_trivially_relocatable) + if constexpr (std::is_trivially_relocatable_v && + sg14::aaipv::has_trivial_construct::value && + sg14::aaipv::has_trivial_destroy::value) { + std::uninitialized_relocate(rhs.data_ + size_, rhs.data_ + rhs.size_, data_ + size_); + swap_sizes_(rhs); + return; + } +#endif // __cpp_lib_trivially_relocatable + sg14::aaipv::uninitialized_move_a(get_allocator_(), rhs.data_ + size_, rhs.data_ + rhs.size_, data_ + size_); + set_size_(rhs.size_); + } + } + constexpr ~ipvbase() { + sg14::aaipv::destroy_a(get_allocator_(), data_, data_ + size_); + } + + ipvbase(const ipvbase&) requires CopyCtorIsDefaultable = default; + ipvbase(ipvbase&&) requires MoveCtorIsDefaultable = default; + ipvbase& operator=(const ipvbase&) requires CopyAssignIsDefaultable = default; + ipvbase& operator=(ipvbase&&) requires MoveAssignIsDefaultable = default; + ~ipvbase() requires DtorIsDefaultable = default; + + static constexpr void swap_(ipvbase& a, ipvbase& b) noexcept(SwapIsNoexcept) + { + if constexpr (std::allocator_traits::propagate_on_container_swap::value && !std::allocator_traits::is_always_equal::value) { + SG14_INPLACE_VECTOR_ASSERT_PRECONDITION(a.get_allocator_() == b.get_allocator_(), "swap tried to swap unequal allocators; this is UB"); + } + if (a.size_ < b.size_) { + swap_(b, a); + } else { + std::swap_ranges(a.data_, a.data_ + b.size_, b.data_); +#if defined(__cpp_lib_trivially_relocatable) + size_t n = a.size_; + a.set_size_(b.size_); + sg14::aaipv::uninitialized_relocate_a(get_allocator_(), a.data_ + b.size_, a.data_ + n, b.data_ + b.size_); + b.set_size_(n); +#else + sg14::aaipv::uninitialized_move_a(get_allocator_(), a.data_ + b.size_, a.data_ + a.size_, b.data_ + b.size_); + sg14::aaipv::destroy_a(get_allocator_(), a.data_ + b.size_, a.data_ + a.size_); + a.swap_sizes_(b); +#endif + } + } +}; + +template> +class inplace_vector : ipvbase_assignable, ipvbase { + using ipvbase_t = ipvbase; + using ipvbase_t::data_; + using ipvbase_t::size_; + using ipvbase_t::set_size_; + using ipvbase_t::swap_sizes_; + using ipvbase_t::get_allocator_; +public: + static_assert(std::is_same_v::value_type>); + + using value_type = T; + using allocator_type = Alloc; + using pointer = typename std::allocator_traits::pointer; + using const_pointer = typename std::allocator_traits::const_pointer; + using reference = T&; + using const_reference = const T&; + using size_type = typename std::allocator_traits::size_type; + using difference_type = typename std::allocator_traits::difference_type; + using iterator = pointer; + using const_iterator = const_pointer; + using reverse_iterator = std::reverse_iterator; + using const_reverse_iterator = std::reverse_iterator; + + // [inplace.vector.cons] + + inplace_vector() = default; + inplace_vector(inplace_vector&&) = default; + inplace_vector(const inplace_vector&) = default; + inplace_vector& operator=(inplace_vector&&) = default; + inplace_vector& operator=(const inplace_vector&) = default; + inplace_vector& operator=(std::initializer_list il) { assign(il.begin(), il.end()); return *this; } + + constexpr inplace_vector(std::initializer_list il) : inplace_vector(il.begin(), il.end()) {} + constexpr explicit inplace_vector(size_type n) { + if (n > N) { + SG14_INPLACE_VECTOR_THROW(std::bad_alloc()); + } + sg14::aaipv::uninitialized_value_construct_a(get_allocator(), data_, data_ + n); + set_size_(n); + } + constexpr inplace_vector(size_type n, const value_type& value) { assign(n, value); } + + template::iterator_category>, int> = 0> + constexpr inplace_vector(It first, It last) { + if constexpr (std::is_base_of_v::iterator_category>) { + size_t n = last - first; + if (n > N) { + SG14_INPLACE_VECTOR_THROW(std::bad_alloc()); + } + sg14::aaipv::uninitialized_copy_n_a(get_allocator(), first, n, data_); + set_size_(n); + } else { + for (; first != last; ++first) { + emplace_back(*first); + } + } + } + + // [inplace.vector.cons.alloc] + + explicit inplace_vector(const allocator_type& alloc) : ipvbase_t(alloc) {} + inplace_vector(inplace_vector&& rhs, const allocator_type& alloc) : ipvbase_t(std::move(rhs), alloc) {} + inplace_vector(const inplace_vector& rhs, const allocator_type& alloc) : ipvbase_t(rhs, alloc) {} + constexpr inplace_vector(std::initializer_list il, const allocator_type& alloc) : inplace_vector(il.begin(), il.end(), alloc) {} + constexpr inplace_vector(size_type n, const allocator_type& alloc) : ipvbase_t(alloc) { + if (n > N) { + SG14_INPLACE_VECTOR_THROW(std::bad_alloc()); + } + sg14::aaipv::uninitialized_value_construct_a(get_allocator(), data_, data_ + n); + set_size_(n); + } + constexpr inplace_vector(size_type n, const value_type& value, const allocator_type& alloc) : ipvbase_t(alloc) { assign(n, value); } + + template::iterator_category>, int> = 0> + constexpr inplace_vector(It first, It last, const allocator_type& alloc) : ipvbase_t(alloc) { + if constexpr (std::is_base_of_v::iterator_category>) { + size_t n = last - first; + if (n > N) { + SG14_INPLACE_VECTOR_THROW(std::bad_alloc()); + } + sg14::aaipv::uninitialized_copy_n_a(get_allocator(), first, n, data_); + set_size_(n); + } else { + for (; first != last; ++first) { + emplace_back(*first); + } + } + } + + constexpr void assign(std::initializer_list il) { assign(il.begin(), il.end()); } + + constexpr void assign(size_type n, const value_type& value) { + if (n <= size_) { + for (size_t i = 0; i < n; ++i) { + data_[i] = value; + } + sg14::aaipv::destroy_a(get_allocator(), data_ + n, data_ + size_); + set_size_(n); + } else if (n > N) { + SG14_INPLACE_VECTOR_THROW(std::bad_alloc()); + } else { + size_t m = size_; + for (size_t i = 0; i < m; ++i) { + data_[i] = value; + } + for (size_t i = m; i < n; ++i) { + unchecked_emplace_back(value); + } + } + } + + template::iterator_category>, int> = 0> + constexpr void assign(It first, It last) { + size_t n = size_; + for (size_t i = 0; i < n; ++i) { + if (first == last) { + sg14::aaipv::destroy_a(get_allocator(), data_ + i, data_ + n); + set_size_(i); + return; + } + data_[i] = *first; + ++first; + } + for (; first != last; ++first) { + emplace_back(*first); + } + } + +#if __cpp_lib_ranges >= 201911L && __cpp_lib_ranges_to_container >= 202202L + template + requires std::convertible_to, value_type> + constexpr inplace_vector(std::from_range_t, R&& rg) { + if constexpr (std::ranges::sized_range) { + size_t n = std::ranges::size(rg); + if (n > N) { + SG14_INPLACE_VECTOR_THROW(std::bad_alloc()); + } + sg14::aaipv::ranges_uninitialized_copy_n_a(get_allocator(), std::ranges::begin(rg), n, data_, std::unreachable_sentinel); + set_size_(n); + } else { + for (auto&& e : rg) { + emplace_back(decltype(e)(e)); + } + } + } + + template + requires std::convertible_to, value_type> + constexpr inplace_vector(std::from_range_t, R&& rg, const allocator_type& alloc) : ipvbase_t(alloc) { + for (auto&& e : rg) { + emplace_back(decltype(e)(e)); + } + } + + template + requires std::convertible_to, value_type> + constexpr void assign_range(R&& rg) { + auto first = std::ranges::begin(rg); + auto last = std::ranges::end(rg); + size_t n = size_; + for (size_t i = 0; i < n; ++i) { + if (first == last) { + sg14::aaipv::destroy_a(get_allocator(), data_ + i, data_ + n); + set_size_(i); + return; + } + data_[i] = *first; + ++first; + } + for (; first != last; ++first) { + emplace_back(*first); + } + } +#endif // __cpp_lib_ranges >= 201911L && __cpp_lib_ranges_to_container >= 202202L + + constexpr allocator_type get_allocator() const noexcept { return get_allocator_(); } + + // iterators + + constexpr iterator begin() noexcept { return data_; } + constexpr iterator end() noexcept { return data_ + size_; } + constexpr const_iterator begin() const noexcept { return data_; } + constexpr const_iterator end() const noexcept { return data_ + size_; } + constexpr reverse_iterator rbegin() noexcept { return reverse_iterator(end()); } + constexpr reverse_iterator rend() noexcept { return reverse_iterator(begin()); } + constexpr const_reverse_iterator rbegin() const noexcept { return const_reverse_iterator(end()); } + constexpr const_reverse_iterator rend() const noexcept { return const_reverse_iterator(begin()); } + constexpr const_iterator cbegin() const noexcept { return data_; } + constexpr const_iterator cend() const noexcept { return data_ + size_; } + constexpr const_reverse_iterator crbegin() const noexcept { return const_reverse_iterator(end()); } + constexpr const_reverse_iterator crend() const noexcept { return const_reverse_iterator(begin()); } + + constexpr void resize(size_type n) { + if (n < size_) { + sg14::aaipv::destroy_a(get_allocator(), data_ + n, data_ + size_); + set_size_(n); + } else if (n > N) { + SG14_INPLACE_VECTOR_THROW(std::bad_alloc()); + } else { + for (size_t i = n - size_; i != 0; --i) { + unchecked_emplace_back(); + } + } + } + + constexpr void resize(size_type n, const value_type& value) { + if (n < size_) { + sg14::aaipv::destroy_a(get_allocator(), data_ + n, data_ + size_); + set_size_(n); + } else if (n > N) { + SG14_INPLACE_VECTOR_THROW(std::bad_alloc()); + } else { + for (size_t i = n - size_; i != 0; --i) { + unchecked_emplace_back(value); + } + } + } + + static constexpr void reserve(size_type n) { if (n > N) SG14_INPLACE_VECTOR_THROW(std::bad_alloc()); } + static constexpr void shrink_to_fit() noexcept {} + + // element access + + constexpr reference operator[](size_type i) { return data_[i]; } + constexpr reference front() { return data_[0]; } + constexpr reference back() { return data_[size_ - 1]; } + + constexpr const_reference operator[](size_type i) const { return data_[i]; } + constexpr const_reference front() const { return data_[0]; } + constexpr const_reference back() const { return data_[size_ - 1]; } + + constexpr reference at(size_type i) { + if (i >= size_) { + SG14_INPLACE_VECTOR_THROW(std::out_of_range("inplace_vector::at")); + } + return data_[i]; + } + constexpr const_reference at(size_type i) const { + if (i >= size_) { + SG14_INPLACE_VECTOR_THROW(std::out_of_range("inplace_vector::at")); + } + return data_[i]; + } + + // [inplace.vector.data] + + constexpr T* data() noexcept { return data_; } + constexpr const T* data() const noexcept { return data_; } + constexpr size_type size() const noexcept { return size_; } + static constexpr size_type max_size() noexcept { return N; } + static constexpr size_type capacity() noexcept { return N; } + [[nodiscard]] constexpr bool empty() const noexcept { return size_ == 0; }; + + // [inplace.vector.modifiers] + + template + value_type& unchecked_emplace_back(Args&&... args) { + // Precondition: (size_ < N) + value_type *p = data_ + size_; + allocator_type alloc = get_allocator(); + std::allocator_traits::construct(alloc, p, static_cast(args)...); + set_size_(size_ + 1); + return *p; + } + value_type& unchecked_push_back(const value_type& value) { return unchecked_emplace_back(value); } + value_type& unchecked_push_back(value_type&& value) { return unchecked_emplace_back(static_cast(value)); } + + template + constexpr value_type *try_emplace_back(Args&&... args) { + if (size_ == N) { + return nullptr; + } + return std::addressof(unchecked_emplace_back(static_cast(args)...)); + } + constexpr value_type *try_push_back(const value_type& value) { return try_emplace_back(value); } + constexpr value_type *try_push_back(value_type&& value) { return try_emplace_back(static_cast(value)); } + + template + value_type& emplace_back(Args&&... args) { + if (size_ == N) { + SG14_INPLACE_VECTOR_THROW(std::bad_alloc()); + } + return unchecked_emplace_back(static_cast(args)...); + } + value_type& push_back(const value_type& value) { return emplace_back(value); } + value_type& push_back(value_type&& value) { return emplace_back(static_cast(value)); } + +#if __cpp_lib_ranges >= 201911L && __cpp_lib_ranges_to_container >= 202202L + template + requires std::convertible_to, value_type> + constexpr void append_range(R&& rg) { + for (auto&& e : rg) { + emplace_back(static_cast(e)); + } + } +#endif // __cpp_lib_ranges >= 201911L && __cpp_lib_ranges_to_container >= 202202L + + void pop_back() { + std::destroy_at(data_ + size_ - 1); + set_size_(size_ - 1); + } + + template + iterator emplace(const_iterator pos, Args&&... args) { + T *it = const_cast(std::to_address(pos)); + emplace_back(static_cast(args)...); + std::rotate(it, data_ + size_ - 1, data_ + size_); + return std::pointer_traits::pointer_to(*it); + } + iterator insert(const_iterator pos, const value_type& value) { return emplace(pos, value); } + iterator insert(const_iterator pos, value_type&& value) { return emplace(pos, static_cast(value)); } + + iterator insert(const_iterator pos, size_type n, const value_type& value) { + if (N - size_ < n) { + SG14_INPLACE_VECTOR_THROW(std::bad_alloc()); + } + T *it = const_cast(std::to_address(pos)); + T *oldend = data_ + size_; +#if defined(__cpp_lib_trivially_relocatable) + // Open a window and fill in-place; if filling fails, close the window again. + if constexpr (sg14::aaipv::relocate_in_insert_and_erase::value) { + std::uninitialized_relocate_backward(it, oldend, oldend + n); + try { + std::uninitialized_fill_n(it, n, value); + set_size_(size_ + n); + } catch (...) { + std::uninitialized_relocate(it + n, oldend + n, it); + throw; + } + return std::pointer_traits::pointer_to(*it); + } +#endif + // Fill at the end of the vector, then rotate into place. + sg14::aaipv::uninitialized_fill_a(oldend, oldend + n, value); + set_size_(size_ + n); + std::rotate(it, oldend, oldend + n); + return std::pointer_traits::pointer_to(*it); + } + + template::iterator_category>, int> = 0> + iterator insert(const_iterator pos, It first, It last) { + T *it = const_cast(std::to_address(pos)); + T *oldend = data_ + size_; + if constexpr (std::is_base_of_v::iterator_category>) { + size_t n = (last - first); + if (N - size_ < n) { + SG14_INPLACE_VECTOR_THROW(std::bad_alloc()); + } +#if defined(__cpp_lib_trivially_relocatable) + // Open a window and fill in-place; if filling fails, close the window again. + if constexpr (sg14::aaipv::relocate_in_insert_and_erase::value) { + std::uninitialized_relocate_backward(it, oldend, oldend + n); + try { + std::uninitialized_copy_n(first, n, it); + set_size_(size_ + n); + } catch (...) { + std::uninitialized_relocate(it + n, oldend + n, it); + throw; + } + return std::pointer_traits::pointer_to(*it); + } +#endif + // Fill at the end of the vector, then rotate into place. + sg14::aaipv::uninitialized_copy_n_a(get_allocator(), first, n, oldend); + set_size_(size_ + n); + std::rotate(it, oldend, oldend + n); + } else { + auto oldend = end(); + for (; first != last; ++first) { + emplace_back(*first); + } + std::rotate(it, oldend, end()); + } + return std::pointer_traits::pointer_to(*it); + } + +#if __cpp_lib_ranges >= 201911L && __cpp_lib_ranges_to_container >= 202202L + template + requires std::convertible_to, value_type> + iterator insert_range(const_iterator pos, R&& rg) { + T *it = const_cast(std::to_address(pos)); + T *oldend = data_ + size_; + if constexpr (std::ranges::sized_range) { + size_type n = std::ranges::size(rg); + if (N - size_ < n) { + SG14_INPLACE_VECTOR_THROW(std::bad_alloc()); + } +#if defined(__cpp_lib_trivially_relocatable) + // Open a window and fill in-place; if filling fails, close the window again. + if constexpr (sg14::aaipv::relocate_in_insert_and_erase::value) { + std::uninitialized_relocate_backward(it, oldend, oldend + n); + try { + std::ranges::uninitialized_copy_n(std::ranges::begin(rg), n, it, std::unreachable_sentinel); + set_size_(size_ + n); + } catch (...) { + std::uninitialized_relocate(it + n, oldend + n, it); + throw; + } + return std::pointer_traits::pointer_to(*it); + } +#endif + // Fill at the end of the vector, then rotate into place. + sg14::aaipv::ranges_uninitialized_copy_n_a(get_allocator(), std::ranges::begin(rg), n, oldend, std::unreachable_sentinel); + set_size_(size_ + n); + std::rotate(it, oldend, oldend + n); + } else { + auto last = std::ranges::end(rg); + auto [rgend, newend] = sg14::aaipv::ranges_uninitialized_copy_a(get_allocator(), std::ranges::begin(rg), last, oldend, data_ + N); + if (rgend != last) { + sg14::aaipv::destroy_a(get_allocator(), oldend, newend); + SG14_INPLACE_VECTOR_THROW(std::bad_alloc()); + } else { + set_size_(newend - data_); + std::rotate(it, oldend, newend); + } + } + return std::pointer_traits::pointer_to(*it); + } +#endif // __cpp_lib_ranges >= 201911L && __cpp_lib_ranges_to_container >= 202202L + + iterator insert(const_iterator pos, std::initializer_list il) { return insert(pos, il.begin(), il.end()); } + + iterator erase(const_iterator pos) { return erase(pos, pos + 1); } + + iterator erase(const_iterator first, const_iterator last) { + T *ifirst = const_cast(std::to_address(first)); + T *ilast = const_cast(std::to_address(last)); + auto n = ilast - ifirst; + if (n != 0) { + T *oldend = data_ + size_; +#if defined(__cpp_lib_trivially_relocatable) + if constexpr (sg14::aaipv::relocate_in_insert_and_erase::value) { + std::destroy(ifirst, ilast); + std::uninitialized_relocate(ilast, oldend, ifirst); + set_size_(size_ - n); + return std::pointer_traits::pointer_to(*ifirst); + } +#endif // __cpp_lib_trivially_relocatable + sg14::aaipv::destroy_a(get_allocator(), std::move(ilast, oldend, ifirst), oldend); + set_size_(size_ - n); + } + return std::pointer_traits::pointer_to(*ifirst); + } + + constexpr void clear() noexcept { + sg14::aaipv::destroy_a(get_allocator(), data_, data_ + size_); + set_size_(0); + } + + constexpr void swap(inplace_vector& b) noexcept(noexcept(ipvbase_t::swap_(*this, b))) { + ipvbase_t::swap_(*this, b); + } + + friend constexpr void swap(inplace_vector& a, inplace_vector& b) noexcept(noexcept(a.swap(b))) { + a.swap(b); + } + + constexpr friend bool operator==(const inplace_vector& a, const inplace_vector& b) { + if (a.size_ != b.size_) { + return false; + } + const T *adata = a.data_; + const T *bdata = b.data_; + for (size_t i = 0; i < a.size_; ++i) { + if (adata[i] != bdata[i]) { + return false; + } + } + return true; + } + + constexpr friend auto operator<=>(const inplace_vector& a, const inplace_vector& b) { + const T *adata = a.data_; + const T *bdata = b.data_; + size_t n = (a.size_ < b.size_) ? a.size_ : b.size_; + for (size_t i = 0; i < n; ++i) { + if (auto r = adata[i] <=> bdata[i]; r != 0) { + return r; + } + } + return (a.size_ <=> b.size_); + } +}; + +} // namespace sg14 + +#if __has_include() +#include +namespace sg14::pmr { + +template +using inplace_vector = sg14::inplace_vector>; + +} // namespace sg14::pmr +#endif diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index a0f1379..fec76de 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -3,6 +3,9 @@ find_package(GTest REQUIRED) include(GoogleTest) add_executable(utest + aa_inplace_vector_pmr_test.cpp + aa_inplace_vector_smallsize_test.cpp + aa_inplace_vector_stdallocator_test.cpp flat_map_test.cpp flat_set_test.cpp hive_test.cpp @@ -42,6 +45,8 @@ elseif ("${CMAKE_CXX_COMPILER_ID}" STREQUAL "GNU") elseif ("${CMAKE_CXX_COMPILER_ID}" STREQUAL "MSVC") set_property(TARGET utest APPEND PROPERTY COMPILE_OPTIONS "-W4;-WX;-DNOMINMAX;-D_SCL_SECURE_NO_WARNINGS") # MSVC gives bogus "conditional expression is constant" warnings + set_property(SOURCE aa_inplace_vector_pmr_test.cpp APPEND PROPERTY COMPILE_OPTIONS "-wd4127") + set_property(SOURCE aa_inplace_vector_smallsize_test.cpp APPEND PROPERTY COMPILE_OPTIONS "-wd4127") set_property(SOURCE hive_test.cpp APPEND PROPERTY COMPILE_OPTIONS "-wd4127") set_property(SOURCE inplace_vector_test.cpp APPEND PROPERTY COMPILE_OPTIONS "-wd4127") # MSVC gives bogus "possible loss of data" warnings diff --git a/test/aa_inplace_vector_pmr_test.cpp b/test/aa_inplace_vector_pmr_test.cpp new file mode 100644 index 0000000..e5b262c --- /dev/null +++ b/test/aa_inplace_vector_pmr_test.cpp @@ -0,0 +1,1519 @@ +#if __cplusplus >= 202002L + +#include + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define IPV_TEST_NAME aa_inplace_vector_pmr + +struct Seq { + std::vector v_; + + template + explicit Seq(const Chars*... ts) : v_{ts...} {} + + template + friend bool operator==(const V& v, const Seq& seq) { + return std::equal(v.begin(), v.end(), seq.v_.begin(), seq.v_.end()); + } +}; + +struct MoveOnly { + MoveOnly(const char *s) : s_(std::make_unique(s)) {} + friend bool operator==(const MoveOnly& a, const MoveOnly& b) { return *a.s_ == *b.s_; } + friend bool operator!=(const MoveOnly& a, const MoveOnly& b) { return *a.s_ != *b.s_; } + std::unique_ptr s_; +}; + +struct MoveOnlyNT { + MoveOnlyNT(const char *s) : s_(s) {} + MoveOnlyNT(MoveOnlyNT&&) = default; + MoveOnlyNT(const MoveOnlyNT&) = delete; + MoveOnlyNT& operator=(MoveOnlyNT&&) = default; + MoveOnlyNT& operator=(const MoveOnlyNT&) = delete; + ~MoveOnlyNT() {} // deliberately non-trivial + friend bool operator==(const MoveOnlyNT& a, const MoveOnlyNT& b) { return a.s_ == b.s_; } + friend bool operator!=(const MoveOnlyNT& a, const MoveOnlyNT& b) { return a.s_ != b.s_; } + + std::string s_; +}; + +TEST(IPV_TEST_NAME, TrivialTraits) +{ + { + using T = sg14::pmr::inplace_vector; + static_assert(!std::is_trivially_copyable_v); + static_assert(!std::is_trivially_copy_constructible_v); // because SOCCC + static_assert(std::is_trivially_move_constructible_v); + static_assert(!std::is_trivially_copy_assignable_v); + static_assert(!std::is_trivially_move_assignable_v); + static_assert(std::is_trivially_destructible_v); + static_assert(!std::is_nothrow_copy_constructible_v); // because SOCCC + static_assert(std::is_nothrow_move_constructible_v); + static_assert(std::is_nothrow_copy_assignable_v); + static_assert(std::is_nothrow_move_assignable_v); + static_assert(std::is_nothrow_destructible_v); +#if defined(__cpp_lib_trivially_relocatable) + static_assert(!std::is_trivially_relocatable_v); +#endif // __cpp_lib_trivially_relocatable + } + { + using T = sg14::pmr::inplace_vector, 10>; + static_assert(!std::is_trivially_copyable_v); + static_assert(!std::is_trivially_copy_constructible_v); + static_assert(!std::is_trivially_move_constructible_v); + static_assert(!std::is_trivially_copy_assignable_v); + static_assert(!std::is_trivially_move_assignable_v); + static_assert(!std::is_trivially_destructible_v); + static_assert(!std::is_nothrow_copy_constructible_v); + static_assert(!std::is_nothrow_move_constructible_v); // because pmr::vector isn't nothrow move-assignable + static_assert(!std::is_nothrow_copy_assignable_v); + static_assert(!std::is_nothrow_move_assignable_v); + static_assert(std::is_nothrow_destructible_v); +#if defined(__cpp_lib_trivially_relocatable) + static_assert(!std::is_trivially_relocatable_v); +#endif // __cpp_lib_trivially_relocatable + } + { + using T = sg14::pmr::inplace_vector; + static_assert(!std::is_trivially_copyable_v); + static_assert(!std::is_trivially_copy_constructible_v); + static_assert(!std::is_trivially_move_constructible_v); + static_assert(!std::is_trivially_copy_assignable_v); + static_assert(!std::is_trivially_move_assignable_v); + static_assert(!std::is_trivially_destructible_v); + static_assert(!std::is_copy_constructible_v); + static_assert(std::is_nothrow_move_constructible_v); + static_assert(!std::is_copy_assignable_v); + static_assert(std::is_nothrow_move_assignable_v); + static_assert(std::is_nothrow_destructible_v); +#if defined(__cpp_lib_trivially_relocatable) + static_assert(!std::is_trivially_relocatable_v); +#endif // __cpp_lib_trivially_relocatable + } + { + using T = sg14::pmr::inplace_vector; + static_assert(!std::is_trivially_copyable_v); + static_assert(!std::is_trivially_copy_constructible_v); + static_assert(!std::is_trivially_move_constructible_v); + static_assert(!std::is_trivially_copy_assignable_v); + static_assert(!std::is_trivially_move_assignable_v); + static_assert(!std::is_trivially_destructible_v); + static_assert(!std::is_copy_constructible_v); + static_assert(std::is_nothrow_move_constructible_v); + static_assert(!std::is_copy_assignable_v); + static_assert(std::is_nothrow_move_assignable_v); + static_assert(std::is_nothrow_destructible_v); +#if defined(__cpp_lib_trivially_relocatable) + static_assert(!std::is_trivially_relocatable_v); +#endif // __cpp_lib_trivially_relocatable + } + { + using T = sg14::pmr::inplace_vector; + static_assert(!std::is_trivially_copyable_v); + static_assert(!std::is_trivially_copy_constructible_v); // because SOCCC + static_assert(std::is_trivially_move_constructible_v); + static_assert(!std::is_trivially_copy_assignable_v); + static_assert(!std::is_trivially_move_assignable_v); + static_assert(std::is_trivially_destructible_v); + static_assert(!std::is_nothrow_copy_constructible_v); // because SOCCC + static_assert(std::is_nothrow_move_constructible_v); + static_assert(std::is_nothrow_copy_assignable_v); + static_assert(std::is_nothrow_move_assignable_v); + static_assert(std::is_nothrow_destructible_v); +#if defined(__cpp_lib_trivially_relocatable) + static_assert(!std::is_trivially_relocatable_v); +#endif // __cpp_lib_trivially_relocatable + } + { + using T = sg14::pmr::inplace_vector, 0>; + static_assert(!std::is_trivially_copyable_v); + static_assert(!std::is_trivially_copy_constructible_v); // because SOCCC + static_assert(std::is_trivially_move_constructible_v); + static_assert(!std::is_trivially_copy_assignable_v); + static_assert(!std::is_trivially_move_assignable_v); + static_assert(std::is_trivially_destructible_v); + static_assert(!std::is_nothrow_copy_constructible_v); // because SOCCC + static_assert(std::is_nothrow_move_constructible_v); + static_assert(std::is_nothrow_copy_assignable_v); + static_assert(std::is_nothrow_move_assignable_v); + static_assert(std::is_nothrow_destructible_v); +#if defined(__cpp_lib_trivially_relocatable) + static_assert(!std::is_trivially_relocatable_v); +#endif // __cpp_lib_trivially_relocatable + } + { + using T = sg14::pmr::inplace_vector, 0>; + static_assert(!std::is_trivially_copyable_v); + static_assert(!std::is_trivially_copy_constructible_v); + static_assert(std::is_trivially_move_constructible_v); + static_assert(!std::is_trivially_copy_assignable_v); + static_assert(!std::is_trivially_move_assignable_v); + static_assert(std::is_trivially_destructible_v); + static_assert(!std::is_copy_constructible_v); + static_assert(std::is_nothrow_move_constructible_v); + static_assert(!std::is_copy_assignable_v); + static_assert(std::is_nothrow_move_assignable_v); + static_assert(std::is_nothrow_destructible_v); +#if defined(__cpp_lib_trivially_relocatable) + static_assert(!std::is_trivially_relocatable_v); +#endif // __cpp_lib_trivially_relocatable + } +} + +TEST(IPV_TEST_NAME, PartiallyTrivialTraits) +{ + { + struct S { + int *p_ = nullptr; + S(int *p) : p_(p) {} + S(const S& s) : p_(s.p_) { *p_ += 1; } + S(S&&) = default; + S& operator=(const S&) = default; + S& operator=(S&&) = default; + ~S() = default; + }; + using T = sg14::pmr::inplace_vector; + static_assert(!std::is_trivially_copyable_v); + static_assert(!std::is_trivially_copy_constructible_v); + static_assert(!std::is_trivially_copy_assignable_v); + static_assert(std::is_trivially_move_constructible_v); + static_assert(!std::is_trivially_move_assignable_v); // because Lakos rule + static_assert(std::is_trivially_destructible_v); + T v; + int count = 0; + v.push_back(S(&count)); + v.push_back(S(&count)); + count = 0; + T w = v; + EXPECT_EQ(count, 2); // should have copied two S objects + T x = std::move(v); + EXPECT_EQ(count, 2); // moving them is trivial + EXPECT_EQ(v.size(), 2); + v.clear(); w = v; + EXPECT_EQ(count, 2); // destroying them is trivial + v = x; + EXPECT_EQ(count, 4); // should have copy-constructed two S objects + v = x; + EXPECT_EQ(count, 4); // copy-assigning them is trivial + v = std::move(x); + EXPECT_EQ(count, 4); // move-assigning them is trivial + EXPECT_EQ(x.size(), 2); + x.clear(); x = std::move(v); + EXPECT_EQ(count, 4); // move-constructing them is trivial + EXPECT_EQ(v.size(), 2); + v.clear(); x = std::move(v); + EXPECT_EQ(count, 4); // destroying them is trivial + EXPECT_TRUE(v.empty() && w.empty() && x.empty()); + } + { + struct S { + int *p_ = nullptr; + S(int *p) : p_(p) {} + S(const S& s) = default; + S(S&&) = default; + S& operator=(const S&) { *p_ += 1; return *this; } + S& operator=(S&&) = default; + ~S() = default; + }; + using T = sg14::pmr::inplace_vector; + static_assert(!std::is_trivially_copyable_v); + static_assert(!std::is_trivially_copy_constructible_v); // because SOCCC + static_assert(!std::is_trivially_copy_assignable_v); + static_assert(std::is_trivially_move_constructible_v); + static_assert(!std::is_trivially_move_assignable_v); // because Lakos rule + static_assert(std::is_trivially_destructible_v); + T v; + int count = 0; + v.push_back(S(&count)); + v.push_back(S(&count)); + count = 0; + T w = v; + EXPECT_EQ(count, 0); // copying them is trivial + T x = std::move(v); + EXPECT_EQ(count, 0); // moving them is trivial + EXPECT_EQ(v.size(), 2); + v.clear(); w = v; + EXPECT_EQ(count, 0); // destroying them is trivial + v = x; + EXPECT_EQ(count, 0); // copying them is trivial + v = x; + EXPECT_EQ(count, 2); // should have copy-assigned two S objects + v = std::move(x); + EXPECT_EQ(count, 2); // move-assigning them is trivial + EXPECT_EQ(x.size(), 2); + x.clear(); x = std::move(v); + EXPECT_EQ(count, 2); // move-constructing them is trivial + EXPECT_EQ(v.size(), 2); + v.clear(); x = std::move(v); + EXPECT_EQ(count, 2); // destroying them is trivial + EXPECT_TRUE(v.empty() && w.empty() && x.empty()); + } + { + struct S { + int *p_ = nullptr; + S(int *p) : p_(p) {} + S(const S& s) = default; + S(S&&) = default; + S& operator=(const S&) = default; + S& operator=(S&&) = default; + ~S() { *p_ += 1; } + }; + using T = sg14::pmr::inplace_vector; + static_assert(!std::is_trivially_copyable_v); + // T's non-trivial dtor prevents `is_trivially_copy_constructible`, + // even though T's copy constructor itself is trivial. + static_assert(!std::is_trivially_copy_constructible_v); + static_assert(!std::is_trivially_copy_assignable_v); + static_assert(!std::is_trivially_move_constructible_v); + static_assert(!std::is_trivially_move_assignable_v); + static_assert(!std::is_trivially_destructible_v); + T v; + int count = 0; + v.push_back(S(&count)); + v.push_back(S(&count)); + count = 0; + T w = v; + EXPECT_EQ(count, 0); // copying them is trivial + T x = std::move(v); + EXPECT_EQ(count, 0); // moving them is trivial + EXPECT_EQ(v.size(), 2); + v.clear(); + EXPECT_EQ(count, 2); // should have destroyed two S objects + w = v; + EXPECT_EQ(count, 4); // should have destroyed two S objects + v = x; + EXPECT_EQ(count, 4); // copying them is trivial + v = x; + EXPECT_EQ(count, 4); // copy-assigning them is trivial + v = std::move(x); + EXPECT_EQ(count, 4); // move-assigning them is trivial + EXPECT_EQ(x.size(), 2); + x.clear(); + EXPECT_EQ(count, 6); // should have destroyed two S objects + x = std::move(v); + EXPECT_EQ(count, 6); // move-constructing them is trivial + EXPECT_EQ(v.size(), 2); + v.clear(); + EXPECT_EQ(count, 8); // should have destroyed two S objects + x = std::move(v); + EXPECT_EQ(count, 10); // should have destroyed two S objects + EXPECT_TRUE(v.empty() && w.empty() && x.empty()); + } +} + +TEST(IPV_TEST_NAME, ZeroSized) +{ + { + sg14::pmr::inplace_vector v; + static_assert(!std::is_empty_v); + static_assert(!std::is_trivial_v); + static_assert(!std::is_trivially_copyable_v); + EXPECT_EQ(v.size(), 0u); + EXPECT_EQ(v.max_size(), 0u); + EXPECT_EQ(v.capacity(), 0u); + EXPECT_EQ(v.try_push_back(42), nullptr); + EXPECT_EQ(v.try_emplace_back(), nullptr); + ASSERT_THROW(v.push_back(42), std::bad_alloc); + ASSERT_THROW(v.emplace_back(), std::bad_alloc); + EXPECT_TRUE(v.empty()); + EXPECT_EQ(v.erase(v.begin(), v.begin()), v.begin()); + int a[1] = {1}; + EXPECT_EQ(v.insert(v.begin(), a, a), v.begin()); + } + { + sg14::pmr::inplace_vector v; + static_assert(!std::is_empty_v); + static_assert(!std::is_trivial_v); + static_assert(!std::is_trivially_copyable_v); + EXPECT_EQ(v.size(), 0u); + EXPECT_EQ(v.max_size(), 0u); + EXPECT_EQ(v.capacity(), 0u); + EXPECT_EQ(v.try_push_back("abc"), nullptr); + EXPECT_EQ(v.try_emplace_back("abc", 3), nullptr); + ASSERT_THROW(v.push_back("abc"), std::bad_alloc); + ASSERT_THROW(v.emplace_back("abc", 3), std::bad_alloc); + EXPECT_TRUE(v.empty()); + EXPECT_EQ(v.erase(v.begin(), v.begin()), v.begin()); + std::string a[1] = {"abc"}; + EXPECT_EQ(v.insert(v.begin(), a, a), v.begin()); + } + { + sg14::pmr::inplace_vector, 0> v; + static_assert(!std::is_empty_v); + static_assert(!std::is_trivial_v); + static_assert(!std::is_trivially_copyable_v); + EXPECT_EQ(v.size(), 0u); + EXPECT_EQ(v.max_size(), 0u); + EXPECT_EQ(v.capacity(), 0u); + EXPECT_EQ(v.try_push_back({1,2,3}), nullptr); + EXPECT_EQ(v.try_emplace_back(), nullptr); + ASSERT_THROW(v.push_back({1,2,3}), std::bad_alloc); + ASSERT_THROW(v.emplace_back(), std::bad_alloc); + EXPECT_TRUE(v.empty()); + EXPECT_EQ(v.erase(v.begin(), v.begin()), v.begin()); + std::pmr::list a[1] = {{1,2,3}}; + EXPECT_EQ(v.insert(v.begin(), a, a), v.begin()); + } + { + sg14::pmr::inplace_vector, 0> v; + static_assert(!std::is_empty_v); + static_assert(!std::is_trivial_v); + static_assert(!std::is_trivially_copyable_v); + EXPECT_EQ(v.size(), 0u); + EXPECT_EQ(v.max_size(), 0u); + EXPECT_EQ(v.capacity(), 0u); + EXPECT_EQ(v.try_push_back(nullptr), nullptr); + EXPECT_EQ(v.try_emplace_back(), nullptr); + ASSERT_THROW(v.push_back(nullptr), std::bad_alloc); + ASSERT_THROW(v.emplace_back(), std::bad_alloc); + EXPECT_TRUE(v.empty()); + EXPECT_EQ(v.erase(v.begin(), v.begin()), v.begin()); + std::unique_ptr a[1] = {nullptr}; + EXPECT_EQ(v.insert(v.begin(), std::make_move_iterator(a), std::make_move_iterator(a)), v.begin()); + } +} + +TEST(IPV_TEST_NAME, Iterators) +{ + { + using V = sg14::pmr::inplace_vector; + using I = typename V::iterator; + using CI = typename V::const_iterator; + using RI = typename V::reverse_iterator; + using CRI = typename V::const_reverse_iterator; + static_assert(std::is_same_v); + static_assert(std::is_same_v); + static_assert(std::is_same_v>); + static_assert(std::is_same_v>); + V v(2); + const V cv(2); + static_assert(std::is_same_v); + static_assert(std::is_same_v); + static_assert(std::is_same_v); + static_assert(std::is_same_v); + static_assert(std::is_same_v); + static_assert(std::is_same_v); + static_assert(std::is_same_v); + static_assert(std::is_same_v); + static_assert(std::is_same_v); + static_assert(std::is_same_v); + static_assert(std::is_same_v); + static_assert(std::is_same_v); + EXPECT_EQ(v.end(), v.begin() + 2); + EXPECT_EQ(v.cend(), v.cbegin() + 2); + EXPECT_EQ(v.rend(), v.rbegin() + 2); + EXPECT_EQ(v.crend(), v.crbegin() + 2); + EXPECT_EQ(cv.end(), cv.begin() + 2); + EXPECT_EQ(cv.cend(), cv.cbegin() + 2); + EXPECT_EQ(cv.rend(), cv.rbegin() + 2); + EXPECT_EQ(cv.crend(), cv.crbegin() + 2); + for (int& i : v) EXPECT_EQ(i, 0); + for (const int& i : cv) EXPECT_EQ(i, 0); + } + { + using V = sg14::pmr::inplace_vector; + using I = typename V::iterator; + using CI = typename V::const_iterator; + using RI = typename V::reverse_iterator; + using CRI = typename V::const_reverse_iterator; + static_assert(std::is_same_v); + static_assert(std::is_same_v); + static_assert(std::is_same_v>); + static_assert(std::is_same_v>); + V v(0); + const V cv(0); + static_assert(std::is_same_v); + static_assert(std::is_same_v); + static_assert(std::is_same_v); + static_assert(std::is_same_v); + static_assert(std::is_same_v); + static_assert(std::is_same_v); + static_assert(std::is_same_v); + static_assert(std::is_same_v); + static_assert(std::is_same_v); + static_assert(std::is_same_v); + static_assert(std::is_same_v); + static_assert(std::is_same_v); + EXPECT_EQ(v.end(), v.begin() + 0); + EXPECT_EQ(v.cend(), v.cbegin() + 0); + EXPECT_EQ(v.rend(), v.rbegin() + 0); + EXPECT_EQ(v.crend(), v.crbegin() + 0); + EXPECT_EQ(cv.end(), cv.begin() + 0); + EXPECT_EQ(cv.cend(), cv.cbegin() + 0); + EXPECT_EQ(cv.rend(), cv.rbegin() + 0); + EXPECT_EQ(cv.crend(), cv.crbegin() + 0); + for (int& i : v) EXPECT_EQ(i, 0); + for (const int& i : cv) EXPECT_EQ(i, 0); + } +} + +TEST(IPV_TEST_NAME, Constructors) +{ + { + using V = sg14::pmr::inplace_vector; + long a[] = {1,2,3}; + V v1; + EXPECT_TRUE(v1.empty()); + V v2 = {}; + EXPECT_TRUE(v2.empty()); + V v3 = {1,2,3}; + EXPECT_TRUE(std::equal(v3.begin(), v3.end(), a, a+3)); + auto v4 = V(a, a+3); + EXPECT_TRUE(std::equal(v4.begin(), v4.end(), a, a+3)); + auto iss = std::istringstream("1 2 3"); + auto v5 = V(std::istream_iterator(iss), std::istream_iterator()); + EXPECT_TRUE(std::equal(v5.begin(), v5.end(), a, a+3)); + auto v6 = V(3); + EXPECT_EQ(v6, V(3, 0)); + auto v7 = V(3, 42); + EXPECT_EQ(v7, (V{42, 42, 42})); + } + { + using V = sg14::pmr::inplace_vector; + const char *a[] = {"1", "2", "3"}; + V v1; + EXPECT_TRUE(v1.empty()); + V v2 = {}; + EXPECT_TRUE(v2.empty()); + V v3 = {"1", "2", "3"}; + EXPECT_TRUE(std::equal(v3.begin(), v3.end(), a, a+3)); + auto v4 = V(a, a+3); + EXPECT_TRUE(std::equal(v4.begin(), v4.end(), a, a+3)); + auto iss = std::istringstream("1 2 3"); + auto v5 = V(std::istream_iterator(iss), std::istream_iterator()); + EXPECT_TRUE(std::equal(v5.begin(), v5.end(), a, a+3)); + auto v6 = V(3); + EXPECT_EQ(v6, V(3, "")); + auto v7 = V(3, "42"); + EXPECT_EQ(v7, (V{"42", "42", "42"})); + } +#if __cpp_lib_ranges >= 201911L && __cpp_lib_ranges_to_container >= 202202L + { + auto iss = std::istringstream("1 2 3 4"); + auto rg = std::views::istream(iss); + auto v1 = sg14::pmr::inplace_vector(std::from_range, rg); + EXPECT_EQ(v1, (sg14::pmr::inplace_vector{1,2,3,4})); + auto v2 = v1 | std::ranges::to>(); + EXPECT_EQ(v2, (sg14::pmr::inplace_vector{1,2,3,4})); + } +#endif // __cpp_lib_ranges >= 201911L && __cpp_lib_ranges_to_container >= 202202L +} + +TEST(IPV_TEST_NAME, ConstructorsThrow) +{ + { + using V = sg14::pmr::inplace_vector; + long a[] = {1,2,3,4,5}; + ASSERT_NO_THROW(V(a, a+3)); + ASSERT_THROW(V(a, a+4), std::bad_alloc); + ASSERT_NO_THROW(V(3)); + ASSERT_THROW(V(4), std::bad_alloc); + ASSERT_NO_THROW(V(3, 42)); + ASSERT_THROW(V(4, 42), std::bad_alloc); + ASSERT_NO_THROW(V({1,2,3})); + ASSERT_THROW(V({1,2,3,4}), std::bad_alloc); +#if __cpp_lib_ranges >= 201911L && __cpp_lib_ranges_to_container >= 202202L + auto iss = std::istringstream("1 2 3 4"); + auto rg = std::views::istream(iss); + ASSERT_THROW(V(std::from_range, rg), std::bad_alloc); +#endif // __cpp_lib_ranges >= 201911L && __cpp_lib_ranges_to_container >= 202202L + } +} + +TEST(IPV_TEST_NAME, IterPairNonAssignable) +{ + struct NA { + std::string s_; + NA(const char *s) : s_(s) {} + NA(NA&&) = delete; + NA& operator=(NA&&) = delete; + bool operator==(const char *s) const { return s_ == s; } + }; + const char *a[] = {"1", "2", "3"}; + { + auto v = sg14::pmr::inplace_vector(a, a+3); + EXPECT_EQ(v, Seq("1", "2", "3")); + } +#if __cpp_lib_ranges >= 201911L && __cpp_lib_ranges_to_container >= 202202L + { + auto v = sg14::pmr::inplace_vector(std::from_range, a); + EXPECT_EQ(v, Seq("1", "2", "3")); + } +#endif // __cpp_lib_ranges >= 201911L && __cpp_lib_ranges_to_container >= 202202L +} + +TEST(IPV_TEST_NAME, Copying) +{ + { + using V = sg14::pmr::inplace_vector; + V source = {1,2,3}; + V dest = source; + EXPECT_EQ(dest, (V{1,2,3})); + dest = {4,5}; + EXPECT_EQ(dest, (V{4,5})); + dest = source; + EXPECT_EQ(dest, (V{1,2,3})); + EXPECT_EQ(source, (V{1,2,3})); + } + { + using V = sg14::pmr::inplace_vector; + V source = {"1", "2", "3"}; + V dest = source; + EXPECT_EQ(dest, Seq("1", "2", "3")); + dest = {"4", "5"}; + EXPECT_EQ(dest, Seq("4", "5")); + dest = source; + EXPECT_EQ(dest, Seq("1", "2", "3")); + EXPECT_EQ(source, Seq("1", "2", "3")); + } +} + +TEST(IPV_TEST_NAME, TransfersOfOwnership) +{ + { + // Trivially copyable value_type + using V = sg14::pmr::inplace_vector; + V source = {1,2,3}; + static_assert(std::is_trivially_move_constructible_v); + static_assert(!std::is_trivially_move_assignable_v); // because Lakos rule + V dest = std::move(source); + EXPECT_EQ(source.size(), 3u); + EXPECT_EQ(source, (V{1,2,3})); + EXPECT_EQ(dest.size(), 3u); + EXPECT_EQ(dest, (V{1,2,3})); + dest = {1,2}; + dest = std::move(source); + EXPECT_EQ(source.size(), 3u); + EXPECT_EQ(source, (V{1,2,3})); + EXPECT_EQ(dest.size(), 3u); + EXPECT_EQ(dest, (V{1,2,3})); + } + { + // Trivially relocatable (but not trivially copyable) value_type + using V = sg14::pmr::inplace_vector, 10>; + V source; + source.push_back(std::make_unique(1)); + source.push_back(std::make_unique(2)); + source.push_back(std::make_unique(3)); + static_assert(!std::is_trivially_move_constructible_v); + static_assert(!std::is_trivially_move_assignable_v); +#if defined(__cpp_lib_trivially_relocatable) + static_assert(std::is_trivially_relocatable_v>); + static_assert(!std::is_trivially_relocatable_v); + V dest = std::move(source); // move-construct + EXPECT_EQ(source.size(), 0u); + EXPECT_EQ(dest.size(), 3u); + EXPECT_EQ(*dest.front(), 1); + EXPECT_EQ(*dest.back(), 3); + source = V(2); + source[0] = std::make_unique(42); + source = std::move(dest); // move-assign 2 := 3 + EXPECT_EQ(source.size(), 3u); + EXPECT_EQ(dest.size(), 2u); + EXPECT_EQ(dest.front(), nullptr); + EXPECT_EQ(*source.front(), 1); + EXPECT_EQ(*source.back(), 3); + dest = V(9); + dest[0] = std::make_unique(42); + dest[4] = std::make_unique(42); + dest = std::move(source); // move-assign 9 := 3 + EXPECT_EQ(source.size(), 3u); + EXPECT_EQ(source.front(), nullptr); + EXPECT_EQ(dest.size(), 3u); + EXPECT_EQ(*dest.front(), 1); + EXPECT_EQ(*dest.back(), 3); +#else + V dest = std::move(source); // move-construct + EXPECT_EQ(source.size(), 3u); + EXPECT_EQ(source.front(), nullptr); + EXPECT_EQ(source.back(), nullptr); + EXPECT_EQ(dest.size(), 3u); + EXPECT_EQ(*dest.front(), 1); + EXPECT_EQ(*dest.back(), 3); + source = V(2); + source[0] = std::make_unique(42); + source = std::move(dest); // move-assign 2 := 3 + EXPECT_EQ(source.size(), 3u); + EXPECT_EQ(dest.size(), 3u); + EXPECT_EQ(*source.front(), 1); + EXPECT_EQ(*source.back(), 3); + dest = V(9); + dest[0] = std::make_unique(42); + dest[4] = std::make_unique(42); + dest = std::move(source); // move-assign 9 := 3 + EXPECT_EQ(source.size(), 3u); + EXPECT_EQ(dest.size(), 3u); + EXPECT_EQ(*dest.front(), 1); + EXPECT_EQ(*dest.back(), 3); +#endif + } + { + // Non-trivially relocatable value_type + using V = sg14::pmr::inplace_vector; + V source; + source.push_back("abc"); + source.push_back("def"); + source.push_back("ghi"); +#if defined(__cpp_lib_trivially_relocatable) + static_assert(!std::is_trivially_relocatable_v); +#endif + V dest = std::move(source); + EXPECT_EQ(source.size(), 3u); + EXPECT_EQ(dest.size(), 3u); + EXPECT_EQ(dest.front(), "abc"); + EXPECT_EQ(dest.back(), "ghi"); + } +} + +TEST(IPV_TEST_NAME, Clear) +{ + sg14::pmr::inplace_vector v = {1,2,3}; + v.clear(); + EXPECT_EQ(v.size(), 0u); + EXPECT_TRUE(v.empty()); +} + +TEST(IPV_TEST_NAME, Noexceptness) +{ + sg14::pmr::inplace_vector v; + static_assert(noexcept(v.size())); + static_assert(noexcept(v.empty())); + static_assert(noexcept(v.data())); + static_assert(noexcept(v.begin())); + static_assert(noexcept(v.end())); + static_assert(noexcept(v.cbegin())); + static_assert(noexcept(v.cend())); + static_assert(noexcept(v.rbegin())); + static_assert(noexcept(v.rend())); + static_assert(noexcept(v.crbegin())); + static_assert(noexcept(v.crend())); + static_assert(noexcept(v.clear())); + std::string lvalue = "abc"; + std::initializer_list il = {"abc", "def"}; + static_assert(!noexcept(v.insert(v.begin(), lvalue))); + static_assert(!noexcept(v.insert(v.begin(), il))); + static_assert(!noexcept(v.insert(v.begin(), il.begin(), il.end()))); + static_assert(!noexcept(v.emplace(v.begin(), "abc", 3))); + static_assert(!noexcept(v.emplace_back("abc", 3))); + static_assert(!noexcept(v.push_back(lvalue))); + static_assert(!noexcept(v.try_emplace_back("abc", 3))); + static_assert(!noexcept(v.try_push_back(lvalue))); + static_assert(!noexcept(v.unchecked_emplace_back("abc", 3))); + static_assert(!noexcept(v.unchecked_push_back(lvalue))); +#if __cpp_lib_ranges >= 201911L && __cpp_lib_ranges_to_container >= 202202L + static_assert(!noexcept(v.assign_range(il))); + static_assert(!noexcept(v.append_range(il))); + static_assert(!noexcept(v.insert_range(v.begin(), il))); +#endif // __cpp_lib_ranges >= 201911L && __cpp_lib_ranges_to_container >= 202202L + + // Lakos rule + static_assert(!noexcept(v.front())); + static_assert(!noexcept(v.back())); + static_assert(!noexcept(v.at(0))); + static_assert(!noexcept(v[0])); + static_assert(!noexcept(v.try_push_back(std::move(lvalue)))); + static_assert(!noexcept(v.unchecked_push_back(std::move(lvalue)))); + static_assert(!noexcept(v.erase(v.begin()))); + static_assert(!noexcept(v.erase(v.begin(), v.end()))); +} + +TEST(IPV_TEST_NAME, NoexceptnessOfSwap) +{ + using std::swap; + { + std::string lvalue; + sg14::pmr::inplace_vector v; + static_assert(noexcept(swap(lvalue, lvalue))); + static_assert(noexcept(v.swap(v))); // because the allocator doesn't propagate + static_assert(noexcept(swap(v, v))); + } + { + std::string lvalue; + sg14::pmr::inplace_vector v; + static_assert(noexcept(swap(lvalue, lvalue))); + static_assert(!noexcept(v.swap(v))); // because allocator-extended move-construction might throw + static_assert(!noexcept(swap(v, v))); + } + { + sg14::pmr::inplace_vector v; + static_assert(noexcept(v.swap(v))); + static_assert(noexcept(swap(v, v))); + } + { + struct ThrowingSwappable { + explicit ThrowingSwappable() { } + ThrowingSwappable(const ThrowingSwappable&) { } + void operator=(const ThrowingSwappable&) { } + ~ThrowingSwappable() { } + }; + ThrowingSwappable lvalue; + sg14::pmr::inplace_vector v; + static_assert(!noexcept(swap(lvalue, lvalue))); + static_assert(noexcept(v.swap(v))); + static_assert(noexcept(swap(v, v))); + } +} + +TEST(IPV_TEST_NAME, InsertSingle) +{ + { + using V = sg14::pmr::inplace_vector; + V v = {1, 4, 7}; + int lvalue = 23; + static_assert(std::is_same_v); + static_assert(std::is_same_v); + static_assert(std::is_same_v); + static_assert(std::is_same_v); + auto it = v.insert(v.begin(), 24); + EXPECT_EQ(it, v.begin()); + EXPECT_EQ(v, (V{24, 1, 4, 7})); + it = v.insert(v.begin() + 2, lvalue); + EXPECT_EQ(it, v.begin() + 2); + EXPECT_EQ(v, (V{24, 1, 23, 4, 7})); + ASSERT_THROW(v.insert(v.begin() + 2, 23), std::bad_alloc); + ASSERT_THROW(v.insert(v.end(), lvalue), std::bad_alloc); + it = v.erase(v.begin() + 1); + EXPECT_EQ(it, v.begin() + 1); + EXPECT_EQ(v, (V{24, 23, 4, 7})); + it = v.insert(v.end(), 10); + EXPECT_EQ(it, v.end() - 1); + EXPECT_EQ(v, (V{24, 23, 4, 7, 10})); + EXPECT_EQ(v.size(), 5u); + } + { + using V = sg14::pmr::inplace_vector; + V v = {"abc", "def", "ghi"}; + auto it = v.insert(v.begin(), "xyz"); + EXPECT_EQ(it, v.begin()); + EXPECT_EQ(v, (V{"xyz", "abc", "def", "ghi"})); + std::string lvalue = "wxy"; + it = v.insert(v.begin() + 2, lvalue); + EXPECT_EQ(it, v.begin() + 2); + EXPECT_EQ(v, (V{"xyz", "abc", "wxy", "def", "ghi"})); + ASSERT_THROW(v.insert(v.begin() + 2, "wxy"), std::bad_alloc); + ASSERT_THROW(v.insert(v.end(), lvalue), std::bad_alloc); + it = v.erase(v.begin() + 1); + EXPECT_EQ(it, v.begin() + 1); + EXPECT_EQ(v, (V{"xyz", "wxy", "def", "ghi"})); + it = v.insert(v.end(), "jkl"); + EXPECT_EQ(it, v.end() - 1); + EXPECT_EQ(v, (V{"xyz", "wxy", "def", "ghi", "jkl"})); + EXPECT_EQ(v.size(), 5u); + } + { + using V = sg14::pmr::inplace_vector, 5>; + V v = {{1,2,3}, {4,5,6}, {7,8,9}}; + auto it = v.insert(v.begin(), {24,25,26}); + EXPECT_EQ(it, v.begin()); + EXPECT_EQ(v, (V{{24,25,26}, {1,2,3}, {4,5,6}, {7,8,9}})); + std::list lvalue = {23,24,25}; + it = v.insert(v.begin() + 2, lvalue); + EXPECT_EQ(it, v.begin() + 2); + EXPECT_EQ(v, (V{{24,25,26}, {1,2,3}, {23,24,25}, {4,5,6}, {7,8,9}})); + ASSERT_THROW(v.insert(v.begin() + 2, {23,24,25}), std::bad_alloc); + ASSERT_THROW(v.insert(v.end(), lvalue), std::bad_alloc); + it = v.erase(v.begin() + 1); + EXPECT_EQ(it, v.begin() + 1); + EXPECT_EQ(v, (V{{24,25,26}, {23,24,25}, {4,5,6}, {7,8,9}})); + it = v.insert(v.end(), {10,11,12}); + EXPECT_EQ(it, v.end() - 1); + EXPECT_EQ(v, (V{{24,25,26}, {23,24,25}, {4,5,6}, {7,8,9}, {10,11,12}})); + EXPECT_EQ(v.size(), 5u); + } + { + using V = sg14::pmr::inplace_vector; + MoveOnly a[3] = {"abc", "def", "ghi"}; + V v = V(std::make_move_iterator(a), std::make_move_iterator(a+3)); + auto it = v.insert(v.begin(), "xyz"); + EXPECT_EQ(it, v.begin()); + EXPECT_EQ(*it, "xyz"); + MoveOnly lvalue = "wxy"; + it = v.insert(v.begin() + 2, std::move(lvalue)); + EXPECT_EQ(it, v.begin() + 2); + EXPECT_EQ(v, Seq("xyz", "abc", "wxy", "def", "ghi")); + ASSERT_THROW(v.insert(v.begin() + 2, "wxy"), std::bad_alloc); + it = v.erase(v.begin() + 1); + EXPECT_EQ(it, v.begin() + 1); + EXPECT_EQ(*it, "wxy"); + it = v.insert(v.end(), "jkl"); + EXPECT_EQ(it, v.end() - 1); + EXPECT_EQ(v, Seq("xyz", "wxy", "def", "ghi", "jkl")); + EXPECT_EQ(v.size(), 5u); + } +} + +TEST(IPV_TEST_NAME, InsertMulti) +{ + { + using V = sg14::pmr::inplace_vector; + V v = {1, 2, 3}; + int a[2] = {4, 5}; + static_assert(std::is_same_v); + static_assert(std::is_same_v); + static_assert(std::is_same_v); + static_assert(std::is_same_v); + auto it = v.insert(v.begin(), a, a+2); + EXPECT_EQ(it, v.begin()); + EXPECT_EQ(v, (V{4, 5, 1, 2, 3})); + it = v.insert(v.begin(), a, a); + EXPECT_EQ(it, v.begin()); + EXPECT_EQ(v, (V{4, 5, 1, 2, 3})); + ASSERT_THROW(v.insert(v.begin(), a, a+2), std::bad_alloc); + v.clear(); + it = v.insert(v.begin(), a, a+2); + EXPECT_EQ(it, v.begin()); + it = v.insert(v.end(), {1, 2}); // insert(initializer_list) + EXPECT_EQ(it, v.begin() + 2); + EXPECT_EQ(v, (V{4, 5, 1, 2})); + ASSERT_THROW(v.insert(v.begin() + 2, a, a+2), std::bad_alloc); + EXPECT_LE(v.size(), 5u); + } + { + using V = sg14::pmr::inplace_vector; + V v = {"1", "2", "3"}; + const char *a[2] = {"4", "5"}; + auto it = v.insert(v.begin(), a, a+2); + EXPECT_EQ(it, v.begin()); + EXPECT_EQ(v, (V{"4", "5", "1", "2", "3"})); + it = v.insert(v.begin(), a, a); + EXPECT_EQ(it, v.begin()); + EXPECT_EQ(v, (V{"4", "5", "1", "2", "3"})); + ASSERT_THROW(v.insert(v.begin(), a, a+2), std::bad_alloc); + v.clear(); + it = v.insert(v.begin(), a, a+2); + EXPECT_EQ(it, v.begin()); + it = v.insert(v.end(), {"1", "2"}); // insert(initializer_list) + EXPECT_EQ(it, v.begin() + 2); + EXPECT_EQ(v, (V{"4", "5", "1", "2"})); + ASSERT_THROW(v.insert(v.begin() + 2, a, a+2), std::bad_alloc); + EXPECT_LE(v.size(), 5u); + } + { + using V = sg14::pmr::inplace_vector; + V v; + v.emplace_back("1"); + v.emplace_back("2"); + v.emplace_back("3"); + const char *a[2] = {"4", "5"}; + auto it = v.insert(v.begin(), a, a+2); + EXPECT_EQ(it, v.begin()); + EXPECT_EQ(v, Seq("4", "5", "1", "2", "3")); + it = v.insert(v.begin(), a, a); + EXPECT_EQ(it, v.begin()); + EXPECT_EQ(v, Seq("4", "5", "1", "2", "3")); + ASSERT_THROW(v.insert(v.begin(), a, a+2), std::bad_alloc); + v.clear(); + it = v.insert(v.begin(), a, a+2); + EXPECT_EQ(it, v.begin()); + MoveOnly il[2] = {"1", "2"}; + it = v.insert(v.end(), std::make_move_iterator(il), std::make_move_iterator(il + 2)); + EXPECT_EQ(it, v.begin() + 2); + EXPECT_EQ(v, Seq("4", "5", "1", "2")); + ASSERT_THROW(v.insert(v.begin() + 2, a, a+2), std::bad_alloc); + EXPECT_LE(v.size(), 5u); + } + { + using V = sg14::pmr::inplace_vector; + V v; + v.emplace_back("1"); + v.emplace_back("2"); + v.emplace_back("3"); + const char *a[2] = {"4", "5"}; + auto it = v.insert(v.begin(), a, a+2); + EXPECT_EQ(it, v.begin()); + EXPECT_EQ(v, Seq("4", "5", "1", "2", "3")); + it = v.insert(v.begin(), a, a); + EXPECT_EQ(it, v.begin()); + EXPECT_EQ(v, Seq("4", "5", "1", "2", "3")); + ASSERT_THROW(v.insert(v.begin(), a, a+2), std::bad_alloc); + v.clear(); + it = v.insert(v.begin(), a, a+2); + EXPECT_EQ(it, v.begin()); + MoveOnlyNT il[2] = {"1", "2"}; + it = v.insert(v.end(), std::make_move_iterator(il), std::make_move_iterator(il + 2)); + EXPECT_EQ(it, v.begin() + 2); + EXPECT_EQ(v, Seq("4", "5", "1", "2")); + ASSERT_THROW(v.insert(v.begin() + 2, a, a+2), std::bad_alloc); + EXPECT_LE(v.size(), 5u); + } +} + +TEST(IPV_TEST_NAME, Assign) +{ + { + using V = sg14::pmr::inplace_vector; + V v; + int a[] = {1, 2, 3, 4, 5, 6}; + v.assign(a, a+2); + static_assert(std::is_same_v); + EXPECT_EQ(v, (V{1, 2})); + v.assign(a+2, a+6); + EXPECT_EQ(v, (V{3, 4, 5, 6})); + v.assign(a, a+2); + EXPECT_EQ(v, (V{1, 2})); + ASSERT_THROW(v.assign(a, a+6), std::bad_alloc); + EXPECT_LE(v.size(), 5u); + } + { + using V = sg14::pmr::inplace_vector; + V v; + const char *a[] = {"1", "2", "3", "4", "5", "6"}; + v.assign(a, a+2); + EXPECT_EQ(v, Seq("1", "2")); + v.assign(a+2, a+6); + EXPECT_EQ(v, Seq("3", "4", "5", "6")); + v.assign(a, a+2); + EXPECT_EQ(v, Seq("1", "2")); + ASSERT_THROW(v.assign(a, a+6), std::bad_alloc); + EXPECT_LE(v.size(), 5u); + } + { + using V = sg14::pmr::inplace_vector; + V v; + auto iss = std::istringstream("1 2"); + v.assign(std::istream_iterator(iss), {}); + EXPECT_EQ(v, (V{1, 2})); + iss = std::istringstream("4 5 6 7"); + v.assign(std::istream_iterator(iss), {}); + EXPECT_EQ(v, (V{4, 5, 6, 7})); + iss = std::istringstream("1 2"); + v.assign(std::istream_iterator(iss), {}); + EXPECT_EQ(v, (V{1, 2})); + iss = std::istringstream("1 2 3 4 5 6"); + ASSERT_THROW(v.assign(std::istream_iterator(iss), {}), std::bad_alloc); + EXPECT_LE(v.size(), 5u); + } + { + using V = sg14::pmr::inplace_vector; + const char *a[] = {"1", "2", "3", "4", "5", "6"}; + V v; + v.assign(a, a+2); + EXPECT_EQ(v, Seq("1", "2")); + v.assign(a+2, a+6); + EXPECT_EQ(v, Seq("3", "4", "5", "6")); + v.assign(a, a+2); + EXPECT_EQ(v, Seq("1", "2")); + ASSERT_THROW(v.assign(a, a+6), std::bad_alloc); + EXPECT_LE(v.size(), 5u); + } +} + +TEST(IPV_TEST_NAME, AssignRange) +{ +#if __cpp_lib_ranges >= 201911L && __cpp_lib_ranges_to_container >= 202202L + { + using V = sg14::pmr::inplace_vector; + V v; + v.assign_range(std::vector{1, 2}); + static_assert(std::is_same_v); + EXPECT_EQ(v, (V{1, 2})); + v.assign_range(std::vector{4, 5, 6, 7}); + EXPECT_EQ(v, (V{4, 5, 6, 7})); + v.assign_range(std::vector{1, 2}); + EXPECT_EQ(v, (V{1, 2})); + ASSERT_THROW(v.assign_range(std::vector(6)), std::bad_alloc); + EXPECT_LE(v.size(), 5u); + } + { + using V = sg14::pmr::inplace_vector; + V v; + v.assign_range(std::vector{"1", "2"}); + EXPECT_EQ(v, Seq("1", "2")); + v.assign_range(std::vector{"4", "5", "6", "7"}); + EXPECT_EQ(v, Seq("4", "5", "6", "7")); + v.assign_range(std::vector{"1", "2"}); + EXPECT_EQ(v, Seq("1", "2")); + ASSERT_THROW(v.assign_range(std::vector(6)), std::bad_alloc); + EXPECT_LE(v.size(), 5u); + } + { + using V = sg14::pmr::inplace_vector; + V v; + auto iss = std::istringstream("1 2"); + v.assign_range(std::views::istream(iss)); + EXPECT_EQ(v, (V{1, 2})); + iss = std::istringstream("4 5 6 7"); + v.assign_range(std::views::istream(iss)); + EXPECT_EQ(v, (V{4, 5, 6, 7})); + iss = std::istringstream("1 2"); + v.assign_range(std::views::istream(iss)); + EXPECT_EQ(v, (V{1, 2})); + iss = std::istringstream("1 2 3 4 5 6"); + ASSERT_THROW(v.assign_range(std::views::istream(iss)), std::bad_alloc); + EXPECT_LE(v.size(), 5u); + } + { + using V = sg14::pmr::inplace_vector; + auto iss = std::istringstream("4 5 6 7"); + V v = {"1", "2"}; + v.assign_range(std::views::istream(iss) | std::views::take(3)); + EXPECT_EQ(v, Seq("4", "5", "6")); + iss = std::istringstream("6 7"); + v.assign_range(std::views::istream(iss) | std::views::take(2)); + EXPECT_EQ(v, Seq("6", "7")); + iss = std::istringstream("6 7 8 9 10 11"); + ASSERT_THROW(v.assign_range(std::views::istream(iss) | std::views::take(6)), std::bad_alloc); + EXPECT_LE(v.size(), 5u); + } + { + using V = sg14::pmr::inplace_vector; + const char *a[] = {"1", "2", "3", "4", "5", "6"}; + V v; + v.assign_range(a | std::views::take(2)); + EXPECT_EQ(v, Seq("1", "2")); + v.assign_range(a | std::views::drop(2) | std::views::take(4)); + EXPECT_EQ(v, Seq("3", "4", "5", "6")); + v.assign_range(a | std::views::take(2)); + EXPECT_EQ(v, Seq("1", "2")); + ASSERT_THROW(v.assign_range(a), std::bad_alloc); + EXPECT_LE(v.size(), 5u); + } +#if __cpp_lib_ranges_as_rvalue >= 202207L + { + using V = sg14::pmr::inplace_vector; + MoveOnly a[] = {"1", "2", "3", "4", "5", "6", "7", "8"}; + V v; + v.assign_range(a | std::views::as_rvalue | std::views::take(2)); + EXPECT_EQ(v, Seq("1", "2")); + v.assign_range(a | std::views::as_rvalue | std::views::drop(2) | std::views::take(4)); + EXPECT_EQ(v, Seq("3", "4", "5", "6")); + v.assign_range(a | std::views::as_rvalue | std::views::drop(6) | std::views::take(2)); + EXPECT_EQ(v, Seq("7", "8")); + ASSERT_THROW(v.assign_range(a | std::views::as_rvalue), std::bad_alloc); + EXPECT_LE(v.size(), 5u); + } +#endif // __cpp_lib_ranges_as_rvalue >= 202207L +#endif // __cpp_lib_ranges >= 201911L && __cpp_lib_ranges_to_container >= 202202L +} + +TEST(IPV_TEST_NAME, InsertRange) +{ +#if __cpp_lib_ranges >= 201911L && __cpp_lib_ranges_to_container >= 202202L + { + using V = sg14::pmr::inplace_vector; + V v = {1, 2}; + auto it = v.insert_range(v.begin() + 1, std::vector{4, 5}); + static_assert(std::is_same_v); + EXPECT_EQ(it, v.begin() + 1); + EXPECT_EQ(v, (V{1, 4, 5, 2})); + ASSERT_THROW(v.insert_range(v.begin() + 1, std::vector{4, 5}), std::bad_alloc); + EXPECT_EQ(v, (V{1, 4, 5, 2})); + } + { + using V = sg14::pmr::inplace_vector; + auto iss = std::istringstream("4 5 6 7"); + V v = {1, 2, 3}; + auto it = v.insert_range(v.begin() + 1, std::views::istream(iss) | std::views::take(2)); + static_assert(std::is_same_v); + EXPECT_EQ(it, v.begin() + 1); + EXPECT_EQ(v, (V{1, 4, 5, 2, 3})); + iss = std::istringstream("6 7"); + ASSERT_THROW(v.insert_range(v.begin() + 1, std::views::istream(iss) | std::views::take(2)), std::bad_alloc); + EXPECT_EQ(v, (V{1, 4, 5, 2, 3})); + } + { + using V = sg14::pmr::inplace_vector; + auto iss = std::istringstream("4 5 6 7"); + V v = {"1", "2"}; + auto it = v.insert_range(v.begin() + 1, std::views::istream(iss) | std::views::take(2)); + EXPECT_EQ(it, v.begin() + 1); + EXPECT_EQ(v, (V{"1", "4", "5", "2"})); + iss = std::istringstream("6 7"); + ASSERT_THROW(v.insert_range(v.begin() + 1, std::views::istream(iss) | std::views::take(2)), std::bad_alloc); + EXPECT_EQ(v, (V{"1", "4", "5", "2"})); + } +#if __cpp_lib_ranges_as_rvalue >= 202207L + { + using V = sg14::pmr::inplace_vector; + MoveOnly a[2] = {"abc", "def"}; + V v; + v.emplace_back("wxy"); + v.emplace_back("xyz"); + auto it = v.insert_range(v.begin() + 1, a | std::views::as_rvalue); + EXPECT_EQ(it, v.begin() + 1); + EXPECT_EQ(v, Seq("wxy", "abc", "def", "xyz")); + ASSERT_THROW(v.insert_range(v.begin() + 1, a | std::views::as_rvalue), std::bad_alloc); + EXPECT_EQ(v, Seq("wxy", "abc", "def", "xyz")); + } +#endif // __cpp_lib_ranges_as_rvalue >= 202207L +#endif // __cpp_lib_ranges >= 201911L && __cpp_lib_ranges_to_container >= 202202L +} + +TEST(IPV_TEST_NAME, AppendRange) +{ +#if __cpp_lib_ranges >= 201911L && __cpp_lib_ranges_to_container >= 202202L + { + using V = sg14::pmr::inplace_vector; + V v = {1, 2}; + v.append_range(std::vector{4, 5}); + static_assert(std::is_same_v{4, 5})), void>); + EXPECT_EQ(v, (V{1, 2, 4, 5})); + ASSERT_THROW(v.append_range(std::vector{4, 5}), std::bad_alloc); + EXPECT_LE(v.size(), 5u); + } + { + using V = sg14::pmr::inplace_vector; + auto iss = std::istringstream("4 5 6 7"); + V v = {1, 2, 3}; + v.append_range(std::views::istream(iss) | std::views::take(2)); + EXPECT_EQ(v, (V{1, 2, 3, 4, 5})); + iss = std::istringstream("6 7"); + ASSERT_THROW(v.append_range(std::views::istream(iss) | std::views::take(2)), std::bad_alloc); + EXPECT_LE(v.size(), 5u); + } + { + using V = sg14::pmr::inplace_vector; + auto iss = std::istringstream("4 5 6 7"); + V v = {"1", "2"}; + v.append_range(std::views::istream(iss) | std::views::take(2)); + EXPECT_EQ(v, Seq("1", "2", "4", "5")); + iss = std::istringstream("6 7"); + ASSERT_THROW(v.append_range(std::views::istream(iss) | std::views::take(2)), std::bad_alloc); + EXPECT_LE(v.size(), 5u); + } +#if __cpp_lib_ranges_as_rvalue >= 202207L + { + using V = sg14::pmr::inplace_vector; + MoveOnly a[2] = {"abc", "def"}; + V v; + v.emplace_back("wxy"); + v.emplace_back("xyz"); + v.append_range(a | std::views::as_rvalue); + EXPECT_EQ(v, Seq("wxy", "xyz", "abc", "def")); + ASSERT_THROW(v.append_range(a | std::views::as_rvalue), std::bad_alloc); + EXPECT_LE(v.size(), 5u); + } +#endif // __cpp_lib_ranges_as_rvalue >= 202207L +#endif // __cpp_lib_ranges >= 201911L && __cpp_lib_ranges_to_container >= 202202L +} + +TEST(IPV_TEST_NAME, PushBack) +{ + { + using V = sg14::pmr::inplace_vector; + V v = {1, 4}; + int lvalue = 23; + static_assert(std::is_same_v); + static_assert(std::is_same_v); + static_assert(std::is_same_v); + EXPECT_EQ(&v.push_back(24), v.data() + 2); + EXPECT_EQ(&v.push_back(lvalue), v.data() + 3); + EXPECT_EQ(&v.emplace_back(), v.data() + 4); + EXPECT_EQ(v, (V{1, 4, 24, 23, 0})); + EXPECT_THROW(v.push_back(24), std::bad_alloc); + EXPECT_THROW(v.push_back(lvalue), std::bad_alloc); + EXPECT_THROW(v.emplace_back(), std::bad_alloc); + EXPECT_EQ(v, (V{1, 4, 24, 23, 0})); // failed push_back has the strong guarantee + + v = {1, 4}; + static_assert(std::is_same_v); + static_assert(std::is_same_v); + static_assert(std::is_same_v); + EXPECT_EQ(v.try_push_back(24), v.data() + 2); + EXPECT_EQ(v.try_push_back(lvalue), v.data() + 3); + EXPECT_EQ(v.try_emplace_back(), v.data() + 4); + EXPECT_EQ(v, (V{1, 4, 24, 23, 0})); + EXPECT_EQ(v.try_push_back(24), nullptr); + EXPECT_EQ(v.try_push_back(lvalue), nullptr); + EXPECT_EQ(v.try_emplace_back(), nullptr); + EXPECT_EQ(v, (V{1, 4, 24, 23, 0})); // failed try_push_back has the strong guarantee + } + { + using V = sg14::pmr::inplace_vector; + V v = {"1", "4"}; + std::string lvalue = "23"; + static_assert(std::is_same_v); + static_assert(std::is_same_v); + static_assert(std::is_same_v); + EXPECT_EQ(&v.push_back("24"), v.data() + 2); + EXPECT_EQ(&v.push_back(lvalue), v.data() + 3); + EXPECT_EQ(&v.emplace_back(), v.data() + 4); + EXPECT_EQ(v, (V{"1", "4", "24", "23", ""})); + EXPECT_THROW(v.push_back("24"), std::bad_alloc); + EXPECT_THROW(v.push_back(lvalue), std::bad_alloc); + EXPECT_THROW(v.emplace_back(), std::bad_alloc); + EXPECT_EQ(v, (V{"1", "4", "24", "23", ""})); // failed push_back has the strong guarantee + + v = {"1", "4"}; + static_assert(std::is_same_v); + static_assert(std::is_same_v); + static_assert(std::is_same_v); + EXPECT_EQ(v.try_push_back("24"), v.data() + 2); + EXPECT_EQ(v.try_push_back(lvalue), v.data() + 3); + EXPECT_EQ(v.try_emplace_back(), v.data() + 4); + EXPECT_EQ(v, (V{"1", "4", "24", "23", ""})); + EXPECT_EQ(v.try_push_back("24"), nullptr); + EXPECT_EQ(v.try_push_back(lvalue), nullptr); + EXPECT_EQ(v.try_emplace_back(), nullptr); + EXPECT_EQ(v, (V{"1", "4", "24", "23", ""})); // failed try_push_back has the strong guarantee + } +} + +TEST(IPV_TEST_NAME, EraseSingle) +{ + { + using V = sg14::pmr::inplace_vector; + V v = {1, 2, 3, 4}; + auto it = v.erase(v.begin() + 1); + static_assert(std::is_same_v); + EXPECT_EQ(it, v.begin() + 1); + EXPECT_EQ(v, (V{1, 3, 4})); + it = v.erase(v.begin()); + EXPECT_EQ(it, v.begin()); + EXPECT_EQ(v, (V{3, 4})); + it = v.erase(v.begin() + 1); + EXPECT_EQ(it, v.end()); + EXPECT_EQ(v, V{3}); + it = v.erase(v.begin()); + EXPECT_EQ(it, v.end()); + EXPECT_TRUE(v.empty()); + } + { + using V = sg14::pmr::inplace_vector; + V v = {"1", "2", "3", "4"}; + auto it = v.erase(v.begin() + 1); + static_assert(std::is_same_v); + EXPECT_EQ(it, v.begin() + 1); + EXPECT_EQ(v, (V{"1", "3", "4"})); + it = v.erase(v.begin()); + EXPECT_EQ(it, v.begin()); + EXPECT_EQ(v, (V{"3", "4"})); + it = v.erase(v.begin() + 1); + EXPECT_EQ(it, v.end()); + EXPECT_EQ(v, V{"3"}); + it = v.erase(v.begin()); + EXPECT_EQ(it, v.end()); + EXPECT_TRUE(v.empty()); + } + { + using V = sg14::pmr::inplace_vector; + const char *a[4] = {"1", "2", "3", "4"}; + V v = V(a, a+4); + auto it = v.erase(v.begin() + 1); + static_assert(std::is_same_v); + EXPECT_EQ(it, v.begin() + 1); + EXPECT_EQ(v.size(), 3u); + it = v.erase(v.begin()); + EXPECT_EQ(it, v.begin()); + EXPECT_EQ(v, V(a+2, a+4)); + it = v.erase(v.begin() + 1); + EXPECT_EQ(it, v.end()); + EXPECT_EQ(v, V(a+2, a+3)); + it = v.erase(v.begin()); + EXPECT_EQ(it, v.end()); + EXPECT_TRUE(v.empty()); + } +} + +TEST(IPV_TEST_NAME, EraseMulti) +{ + { + using V = sg14::pmr::inplace_vector; + V v = {1, 2, 3, 4}; + auto it = v.erase(v.begin() + 1, v.begin() + 3); + static_assert(std::is_same_v); + EXPECT_EQ(it, v.begin() + 1); + EXPECT_EQ(v, (V{1, 4})); + it = v.erase(v.begin(), v.begin()); + EXPECT_EQ(it, v.begin()); + EXPECT_EQ(v, (V{1, 4})); + it = v.erase(v.begin() + 1, v.end()); + EXPECT_EQ(it, v.end()); + EXPECT_EQ(v, V{1}); + it = v.erase(v.begin(), v.end()); + EXPECT_EQ(it, v.end()); + EXPECT_TRUE(v.empty()); + } + { + using V = sg14::pmr::inplace_vector; + V v = {"1", "2", "3", "4"}; + auto it = v.erase(v.begin() + 1, v.begin() + 3); + static_assert(std::is_same_v); + EXPECT_EQ(it, v.begin() + 1); + EXPECT_EQ(v, (V{"1", "4"})); + it = v.erase(v.begin(), v.begin()); + EXPECT_EQ(it, v.begin()); + EXPECT_EQ(v, (V{"1", "4"})); + it = v.erase(v.begin() + 1, v.end()); + EXPECT_EQ(it, v.end()); + EXPECT_EQ(v, V{"1"}); + it = v.erase(v.begin(), v.end()); + EXPECT_EQ(it, v.end()); + EXPECT_TRUE(v.empty()); + } + { + using V = sg14::pmr::inplace_vector; + const char *a[4] = {"1", "2", "3", "4"}; + V v = V(a, a+4); + auto it = v.erase(v.begin() + 1, v.begin() + 3); + static_assert(std::is_same_v); + EXPECT_EQ(it, v.begin() + 1); + EXPECT_EQ(v, Seq("1", "4")); + it = v.erase(v.begin(), v.begin()); + EXPECT_EQ(it, v.begin()); + EXPECT_EQ(v, Seq("1", "4")); + it = v.erase(v.begin() + 1, v.end()); + EXPECT_EQ(it, v.end()); + EXPECT_EQ(v, Seq("1")); + it = v.erase(v.begin(), v.end()); + EXPECT_EQ(it, v.end()); + EXPECT_TRUE(v.empty()); + } + { + using V = sg14::pmr::inplace_vector; + const char *a[4] = {"1", "2", "3", "4"}; + V v = V(a, a+4); + auto it = v.erase(v.begin() + 1, v.begin() + 3); + static_assert(std::is_same_v); + EXPECT_EQ(it, v.begin() + 1); + EXPECT_EQ(v, Seq("1", "4")); + it = v.erase(v.begin(), v.begin()); + EXPECT_EQ(it, v.begin()); + EXPECT_EQ(v, Seq("1", "4")); + it = v.erase(v.begin() + 1, v.end()); + EXPECT_EQ(it, v.end()); + EXPECT_EQ(v, Seq("1")); + it = v.erase(v.begin(), v.end()); + EXPECT_EQ(it, v.end()); + EXPECT_TRUE(v.empty()); + } +} + +TEST(IPV_TEST_NAME, EraseRemoveIdiom) +{ + sg14::pmr::inplace_vector v; + std::mt19937 g; + std::generate_n(std::back_inserter(v), 1000, std::ref(g)); + v.erase(std::remove_if(v.begin(), v.end(), [](auto x) { return x % 100 == 0; }), v.end()); + EXPECT_TRUE(std::none_of(v.begin(), v.end(), [](auto x) { return x % 100 == 0; })); + v.erase(std::remove_if(v.begin(), v.end(), [](auto x) { return x % 100 == 1; }), v.end()); + EXPECT_TRUE(std::none_of(v.begin(), v.end(), [](auto x) { return x % 100 == 1; })); + for (unsigned i = 0; i < 100; ++i) { + v.erase(std::remove_if(v.begin(), v.end(), [&](auto x) { return x % 100 == i; }), v.end()); + EXPECT_TRUE(std::none_of(v.begin(), v.end(), [&](auto x) { return x % 100 == i; })); + } + EXPECT_TRUE(v.empty()); +} + +TEST(IPV_TEST_NAME, AssignFromInitList) +{ + { + using V = sg14::pmr::inplace_vector, 4>; + V v = {{1,2}, {3,4}}; + v = {{5,6}}; + EXPECT_EQ(v, (V{{5,6}})); + v = {{7,8},{9,10},{11,12}}; + EXPECT_EQ(v, (V{{7,8},{9,10},{11,12}})); + ASSERT_THROW((v = {{}, {}, {}, {}, {}, {}}), std::bad_alloc); + static_assert(std::is_same_v); + static_assert(std::is_same_v); + } + { + struct Counter { + int i_ = 0; + Counter(int i): i_(i) {} + Counter(const Counter& rhs) : i_(rhs.i_ + 1) {} + void operator=(const Counter& rhs) { i_ = rhs.i_ + 10; } + }; + sg14::pmr::inplace_vector v = { 100 }; + EXPECT_EQ(v[0].i_, 101); // copied from init-list into vector + v = { 200, 300 }; + EXPECT_EQ(v[0].i_, 210); // assigned from init-list into vector + EXPECT_EQ(v[1].i_, 301); // copied from init-list into vector + } +} + +TEST(IPV_TEST_NAME, Comparison) +{ + sg14::pmr::inplace_vector a; + sg14::pmr::inplace_vector b; + EXPECT_TRUE((a == b) && (a <= b) && (a >= b)); + EXPECT_FALSE((a != b) || (a < b) || (a > b)); +#if __cpp_impl_three_way_comparison >= 201907L + EXPECT_EQ(a <=> b, std::strong_ordering::equal); +#endif + a = {1,2,3}; + b = {1,2,3}; + EXPECT_TRUE((a == b) && (a <= b) && (a >= b)); + EXPECT_FALSE((a != b) || (a < b) || (a > b)); +#if __cpp_impl_three_way_comparison >= 201907L + EXPECT_EQ(a <=> b, std::strong_ordering::equal); +#endif + a = {1,2,3}; + b = {1}; + EXPECT_TRUE((a != b) && (a > b) && (a >= b)); + EXPECT_TRUE((b != a) && (b < a) && (b <= a)); + EXPECT_FALSE((a == b) || (a < b) || (a <= b)); + EXPECT_FALSE((b == a) || (b > a) || (b >= a)); +#if __cpp_impl_three_way_comparison >= 201907L + EXPECT_EQ(a <=> b, std::strong_ordering::greater); + EXPECT_EQ(b <=> a, std::strong_ordering::less); +#endif + a = {1,3}; + b = {1,2,3}; + EXPECT_TRUE((a != b) && (a > b) && (a >= b)); + EXPECT_TRUE((b != a) && (b < a) && (b <= a)); + EXPECT_FALSE((a == b) || (a < b) || (a <= b)); + EXPECT_FALSE((b == a) || (b > a) || (b >= a)); +#if __cpp_impl_three_way_comparison >= 201907L + EXPECT_EQ(a <=> b, std::strong_ordering::greater); + EXPECT_EQ(b <=> a, std::strong_ordering::less); +#endif + a = {1,2,4}; + b = {1,2,3}; + EXPECT_TRUE((a != b) && (a > b) && (a >= b)); + EXPECT_TRUE((b != a) && (b < a) && (b <= a)); + EXPECT_FALSE((a == b) || (a < b) || (a <= b)); + EXPECT_FALSE((b == a) || (b > a) || (b >= a)); +#if __cpp_impl_three_way_comparison >= 201907L + EXPECT_EQ(a <=> b, std::strong_ordering::greater); + EXPECT_EQ(b <=> a, std::strong_ordering::less); +#endif +} + +TEST(IPV_TEST_NAME, Reserve) +{ + sg14::pmr::inplace_vector v = {1,2,3}; + v.reserve(0); + EXPECT_EQ(v.capacity(), 10u); + v.reserve(5); + EXPECT_EQ(v.capacity(), 10u); + v.reserve(10); + EXPECT_EQ(v.capacity(), 10u); + ASSERT_THROW(v.reserve(11), std::bad_alloc); + v.shrink_to_fit(); + EXPECT_EQ(v.capacity(), 10u); + EXPECT_EQ(v, (sg14::pmr::inplace_vector{1,2,3})); +} + +TEST(IPV_TEST_NAME, Resize) +{ + sg14::pmr::inplace_vector v; + v.resize(2); + EXPECT_EQ(v, (sg14::pmr::inplace_vector{"", ""})); + v.resize(1, "a"); + EXPECT_EQ(v, (sg14::pmr::inplace_vector{""})); + v.resize(3, "b"); + EXPECT_EQ(v, (sg14::pmr::inplace_vector{"", "b", "b"})); + v.resize(4); + EXPECT_EQ(v, (sg14::pmr::inplace_vector{"", "b", "b", ""})); + v.resize(2, "c"); + EXPECT_EQ(v, (sg14::pmr::inplace_vector{"", "b"})); + ASSERT_THROW(v.resize(5), std::bad_alloc); + ASSERT_THROW(v.resize(6, "d"), std::bad_alloc); + EXPECT_EQ(v, (sg14::pmr::inplace_vector{"", "b"})); // unchanged +} + +#endif // __cplusplus >= 202002L diff --git a/test/aa_inplace_vector_smallsize_test.cpp b/test/aa_inplace_vector_smallsize_test.cpp new file mode 100644 index 0000000..4326e84 --- /dev/null +++ b/test/aa_inplace_vector_smallsize_test.cpp @@ -0,0 +1,82 @@ +#if __cplusplus >= 202002L + +#include +#include + +template +struct SmallSizeAllocator { + using value_type = T; + using size_type = std::make_unsigned_t; + using difference_type = std::make_signed_t; + + // Because inplace_vector never allocates memory, the `allocate` and + // `deallocate` members can be omitted. This makes `SmallSizeAllocator` + // not-an-allocator from the Standard's point of view. SG14 is happy + // with it, though. +}; + +namespace smallsize { + template using inplace_vector = sg14::inplace_vector>; +} + +#define IPV_TEST_NAME aa_inplace_vector_smallsize +#define IPV_HAS_CONDITIONALLY_TRIVIAL_SMFS 1 +#define sg14 smallsize +#include "inplace_vector_common_tests.cpp" +#undef sg14 + +TEST(IPV_TEST_NAME, SizeAndAlignment) +{ +#ifdef _MSC_VER + // Our `ipv_alloc_holder` has a single [[no_unique_address]] member. + // Even using [[msvc::no_unique_address]], MSVC fails to treat + // `ipv_alloc_holder` as an empty base for the purposes of optimization, + // which means it occupies an extra byte compared to the Itanium ABI. + // + static constexpr bool msvc = true; +#else + static constexpr bool msvc = false; +#endif + + static_assert(sizeof(sg14::inplace_vector) == 32); + static_assert(alignof(sg14::inplace_vector) == 8); + static_assert(sizeof(sg14::pmr::inplace_vector) == 40); + static_assert(alignof(sg14::pmr::inplace_vector) == 8); + static_assert(sizeof(smallsize::inplace_vector) == (msvc ? 24 : 23)); + static_assert(alignof(smallsize::inplace_vector) == 1); + + static_assert(sizeof(smallsize::inplace_vector) == (msvc ? 4 : 3)); + static_assert(alignof(smallsize::inplace_vector) == 1); + + static_assert(sizeof(sg14::inplace_vector>) == (msvc ? 6 : 4)); + static_assert(alignof(sg14::inplace_vector>) == 2); +} + +TEST(IPV_TEST_NAME, Capacity) +{ + using T = smallsize::inplace_vector; + static_assert(std::is_same_v); + static_assert(std::is_same_v); + static_assert(std::is_same_v); + static_assert(T::capacity() == 2); + static_assert(T::max_size() == 2); + static_assert(T().capacity() == 2); + static_assert(T().max_size() == 2); +} + +TEST(IPV_TEST_NAME, Smoke) +{ + smallsize::inplace_vector a = {1, 2}; + smallsize::inplace_vector b = {3}; + EXPECT_TRUE(a < b); + EXPECT_FALSE(a == b); + EXPECT_FALSE(a < a); + EXPECT_TRUE(a == a); + EXPECT_EQ(a.size(), 2); + EXPECT_EQ(b.size(), 1); + b.erase(b.begin()); + EXPECT_TRUE(b.empty()); + EXPECT_THROW(a.push_back(3), std::bad_alloc); +} + +#endif // __cplusplus >= 202002L diff --git a/test/aa_inplace_vector_stdallocator_test.cpp b/test/aa_inplace_vector_stdallocator_test.cpp new file mode 100644 index 0000000..a93f463 --- /dev/null +++ b/test/aa_inplace_vector_stdallocator_test.cpp @@ -0,0 +1,9 @@ +#if __cplusplus >= 202002L + +#include +#define IPV_TEST_NAME aa_inplace_vector +#define IPV_HAS_CONDITIONALLY_TRIVIAL_SMFS 1 +#include "inplace_vector_common_tests.cpp" + +#endif // __cplusplus >= 202002L + diff --git a/test/inplace_vector_common_tests.cpp b/test/inplace_vector_common_tests.cpp new file mode 100644 index 0000000..61b40f6 --- /dev/null +++ b/test/inplace_vector_common_tests.cpp @@ -0,0 +1,1541 @@ + +#include + +#include +#include +#include +#include +#include +#if __has_include() +#include +#endif +#include +#include +#include +#include + +struct Seq { + std::vector v_; + + template + explicit Seq(const Chars*... ts) : v_{ts...} {} + + template + friend bool operator==(const V& v, const Seq& seq) { + return std::equal(v.begin(), v.end(), seq.v_.begin(), seq.v_.end()); + } +}; + +struct MoveOnly { + MoveOnly(const char *s) : s_(std::make_unique(s)) {} + friend bool operator==(const MoveOnly& a, const MoveOnly& b) { return *a.s_ == *b.s_; } + friend bool operator!=(const MoveOnly& a, const MoveOnly& b) { return *a.s_ != *b.s_; } + std::unique_ptr s_; +}; + +struct MoveOnlyNT { + MoveOnlyNT(const char *s) : s_(s) {} + MoveOnlyNT(MoveOnlyNT&&) = default; + MoveOnlyNT(const MoveOnlyNT&) = delete; + MoveOnlyNT& operator=(MoveOnlyNT&&) = default; + MoveOnlyNT& operator=(const MoveOnlyNT&) = delete; + ~MoveOnlyNT() {} // deliberately non-trivial + friend bool operator==(const MoveOnlyNT& a, const MoveOnlyNT& b) { return a.s_ == b.s_; } + friend bool operator!=(const MoveOnlyNT& a, const MoveOnlyNT& b) { return a.s_ != b.s_; } + + std::string s_; +}; + +TEST(IPV_TEST_NAME, TrivialTraits) +{ + { + using T = sg14::inplace_vector; + static_assert(std::is_trivially_copyable_v); + static_assert(std::is_trivially_copy_constructible_v); + static_assert(std::is_trivially_move_constructible_v); + static_assert(std::is_trivially_copy_assignable_v); + static_assert(std::is_trivially_move_assignable_v); + static_assert(std::is_trivially_destructible_v); + static_assert(std::is_nothrow_copy_constructible_v); + static_assert(std::is_nothrow_move_constructible_v); + static_assert(std::is_nothrow_copy_assignable_v); + static_assert(std::is_nothrow_move_assignable_v); + static_assert(std::is_nothrow_destructible_v); +#if defined(__cpp_lib_trivially_relocatable) + static_assert(std::is_trivially_relocatable_v); +#endif // __cpp_lib_trivially_relocatable + } + { + using T = sg14::inplace_vector, 10>; + static_assert(!std::is_trivially_copyable_v); + static_assert(!std::is_trivially_copy_constructible_v); + static_assert(!std::is_trivially_move_constructible_v); + static_assert(!std::is_trivially_copy_assignable_v); + static_assert(!std::is_trivially_move_assignable_v); + static_assert(!std::is_trivially_destructible_v); + static_assert(!std::is_nothrow_copy_constructible_v); + static_assert(std::is_nothrow_move_constructible_v); + static_assert(!std::is_nothrow_copy_assignable_v); + static_assert(std::is_nothrow_move_assignable_v); + static_assert(std::is_nothrow_destructible_v); +#if defined(__cpp_lib_trivially_relocatable) + static_assert(std::is_trivially_relocatable_v); +#endif // __cpp_lib_trivially_relocatable + } + { + constexpr bool msvc = !std::is_nothrow_move_constructible_v>; + using T = sg14::inplace_vector, 10>; + static_assert(!std::is_trivially_copyable_v); + static_assert(!std::is_trivially_copy_constructible_v); + static_assert(!std::is_trivially_move_constructible_v); + static_assert(!std::is_trivially_copy_assignable_v); + static_assert(!std::is_trivially_move_assignable_v); + static_assert(!std::is_trivially_destructible_v); + static_assert(!std::is_nothrow_copy_constructible_v); + static_assert(std::is_nothrow_move_constructible_v == !msvc); + static_assert(!std::is_nothrow_copy_assignable_v); + static_assert(std::is_nothrow_move_assignable_v == !msvc); + static_assert(std::is_nothrow_destructible_v); +#if defined(__cpp_lib_trivially_relocatable) + static_assert(std::is_trivially_relocatable_v == msvc); +#endif // __cpp_lib_trivially_relocatable + } + { + using T = sg14::inplace_vector; + static_assert(!std::is_trivially_copyable_v); + static_assert(!std::is_trivially_copy_constructible_v); + static_assert(!std::is_trivially_move_constructible_v); + static_assert(!std::is_trivially_copy_assignable_v); + static_assert(!std::is_trivially_move_assignable_v); + static_assert(!std::is_trivially_destructible_v); + static_assert(!std::is_copy_constructible_v); + static_assert(std::is_nothrow_move_constructible_v); + static_assert(!std::is_copy_assignable_v); + static_assert(std::is_nothrow_move_assignable_v); + static_assert(std::is_nothrow_destructible_v); +#if defined(__cpp_lib_trivially_relocatable) + static_assert(std::is_trivially_relocatable_v); +#endif // __cpp_lib_trivially_relocatable + } + { + using T = sg14::inplace_vector; + static_assert(!std::is_trivially_copyable_v); + static_assert(!std::is_trivially_copy_constructible_v); + static_assert(!std::is_trivially_move_constructible_v); + static_assert(!std::is_trivially_copy_assignable_v); + static_assert(!std::is_trivially_move_assignable_v); + static_assert(!std::is_trivially_destructible_v); + static_assert(!std::is_copy_constructible_v); + static_assert(std::is_nothrow_move_constructible_v); + static_assert(!std::is_copy_assignable_v); + static_assert(std::is_nothrow_move_assignable_v); + static_assert(std::is_nothrow_destructible_v); +#if defined(__cpp_lib_trivially_relocatable) + static_assert(!std::is_trivially_relocatable_v); +#endif // __cpp_lib_trivially_relocatable + } + { + using T = sg14::inplace_vector; + static_assert(std::is_trivially_copyable_v); + static_assert(std::is_trivially_copy_constructible_v); + static_assert(std::is_trivially_move_constructible_v); + static_assert(std::is_trivially_copy_assignable_v); + static_assert(std::is_trivially_move_assignable_v); + static_assert(std::is_trivially_destructible_v); + static_assert(std::is_nothrow_copy_constructible_v); + static_assert(std::is_nothrow_move_constructible_v); + static_assert(std::is_nothrow_copy_assignable_v); + static_assert(std::is_nothrow_move_assignable_v); + static_assert(std::is_nothrow_destructible_v); +#if defined(__cpp_lib_trivially_relocatable) + static_assert(std::is_trivially_relocatable_v); +#endif // __cpp_lib_trivially_relocatable + } + { + using T = sg14::inplace_vector, 0>; + static_assert(std::is_trivially_copyable_v); + static_assert(std::is_trivially_copy_constructible_v); + static_assert(std::is_trivially_move_constructible_v); + static_assert(std::is_trivially_copy_assignable_v); + static_assert(std::is_trivially_move_assignable_v); + static_assert(std::is_trivially_destructible_v); + static_assert(std::is_nothrow_copy_constructible_v); + static_assert(std::is_nothrow_move_constructible_v); + static_assert(std::is_nothrow_copy_assignable_v); + static_assert(std::is_nothrow_move_assignable_v); + static_assert(std::is_nothrow_destructible_v); +#if defined(__cpp_lib_trivially_relocatable) + static_assert(std::is_trivially_relocatable_v); +#endif // __cpp_lib_trivially_relocatable + } + { + using T = sg14::inplace_vector, 0>; + static_assert(std::is_trivially_copyable_v); + static_assert(std::is_trivially_copy_constructible_v); + static_assert(std::is_trivially_move_constructible_v); + static_assert(std::is_trivially_copy_assignable_v); + static_assert(std::is_trivially_move_assignable_v); + static_assert(std::is_trivially_destructible_v); + static_assert(std::is_nothrow_copy_constructible_v); + static_assert(std::is_nothrow_move_constructible_v); + static_assert(std::is_nothrow_copy_assignable_v); + static_assert(std::is_nothrow_move_assignable_v); + static_assert(std::is_nothrow_destructible_v); +#if defined(__cpp_lib_trivially_relocatable) + static_assert(std::is_trivially_relocatable_v); +#endif // __cpp_lib_trivially_relocatable + } + { + using T = sg14::inplace_vector, 0>; + static_assert(std::is_trivially_copyable_v); + static_assert(!std::is_trivially_copy_constructible_v); + static_assert(std::is_trivially_move_constructible_v); + static_assert(!std::is_trivially_copy_assignable_v); + static_assert(std::is_trivially_move_assignable_v); + static_assert(std::is_trivially_destructible_v); + static_assert(!std::is_copy_constructible_v); + static_assert(std::is_nothrow_move_constructible_v); + static_assert(!std::is_copy_assignable_v); + static_assert(std::is_nothrow_move_assignable_v); + static_assert(std::is_nothrow_destructible_v); +#if defined(__cpp_lib_trivially_relocatable) + static_assert(std::is_trivially_relocatable_v); +#endif // __cpp_lib_trivially_relocatable + } +} + +TEST(IPV_TEST_NAME, PartiallyTrivialTraits) +{ +#if IPV_HAS_CONDITIONALLY_TRIVIAL_SMFS + constexpr bool ConditionallyTrivial = true; +#else + constexpr bool ConditionallyTrivial = false; +#endif + { + struct S { + int *p_ = nullptr; + S(int *p) : p_(p) {} + S(const S& s) : p_(s.p_) { *p_ += 1; } + S(S&&) = default; + S& operator=(const S&) = default; + S& operator=(S&&) = default; + ~S() = default; + }; + using T = sg14::inplace_vector; + static_assert(!std::is_trivially_copyable_v); + static_assert(!std::is_trivially_copy_constructible_v); + static_assert(!std::is_trivially_copy_assignable_v); + static_assert(std::is_trivially_move_constructible_v == ConditionallyTrivial); + static_assert(std::is_trivially_move_assignable_v == ConditionallyTrivial); + static_assert(std::is_trivially_destructible_v == ConditionallyTrivial); + T v; + int count = 0; + v.push_back(S(&count)); + v.push_back(S(&count)); + count = 0; + T w = v; + EXPECT_EQ(count, 2); // should have copied two S objects + T x = std::move(v); + EXPECT_EQ(count, 2); // moving them is trivial + EXPECT_EQ(v.size(), 2); + v.clear(); w = v; + EXPECT_EQ(count, 2); // destroying them is trivial + v = x; + EXPECT_EQ(count, 4); // should have copy-constructed two S objects + v = x; + EXPECT_EQ(count, 4); // copy-assigning them is trivial + v = std::move(x); + EXPECT_EQ(count, 4); // move-assigning them is trivial + EXPECT_EQ(x.size(), 2); + x.clear(); x = std::move(v); + EXPECT_EQ(count, 4); // move-constructing them is trivial + EXPECT_EQ(v.size(), 2); + v.clear(); x = std::move(v); + EXPECT_EQ(count, 4); // destroying them is trivial + EXPECT_TRUE(v.empty() && w.empty() && x.empty()); + } + { + struct S { + int *p_ = nullptr; + S(int *p) : p_(p) {} + S(const S& s) = default; + S(S&&) = default; + S& operator=(const S&) { *p_ += 1; return *this; } + S& operator=(S&&) = default; + ~S() = default; + }; + using T = sg14::inplace_vector; + static_assert(!std::is_trivially_copyable_v); + static_assert(std::is_trivially_copy_constructible_v == ConditionallyTrivial); + static_assert(!std::is_trivially_copy_assignable_v); + static_assert(std::is_trivially_move_constructible_v == ConditionallyTrivial); + static_assert(std::is_trivially_move_assignable_v == ConditionallyTrivial); + static_assert(std::is_trivially_destructible_v == ConditionallyTrivial); + T v; + int count = 0; + v.push_back(S(&count)); + v.push_back(S(&count)); + count = 0; + T w = v; + EXPECT_EQ(count, 0); // copying them is trivial + T x = std::move(v); + EXPECT_EQ(count, 0); // moving them is trivial + EXPECT_EQ(v.size(), 2); + v.clear(); w = v; + EXPECT_EQ(count, 0); // destroying them is trivial + v = x; + EXPECT_EQ(count, 0); // copying them is trivial + v = x; + EXPECT_EQ(count, 2); // should have copy-assigned two S objects + v = std::move(x); + EXPECT_EQ(count, 2); // move-assigning them is trivial + EXPECT_EQ(x.size(), 2); + x.clear(); x = std::move(v); + EXPECT_EQ(count, 2); // move-constructing them is trivial + EXPECT_EQ(v.size(), 2); + v.clear(); x = std::move(v); + EXPECT_EQ(count, 2); // destroying them is trivial + EXPECT_TRUE(v.empty() && w.empty() && x.empty()); + } + { + struct S { + int *p_ = nullptr; + S(int *p) : p_(p) {} + S(const S& s) = default; + S(S&&) = default; + S& operator=(const S&) = default; + S& operator=(S&&) = default; + ~S() { *p_ += 1; } + }; + using T = sg14::inplace_vector; + static_assert(!std::is_trivially_copyable_v); + // T's non-trivial dtor prevents `is_trivially_copy_constructible`, + // even though T's copy constructor itself is trivial. + static_assert(!std::is_trivially_copy_constructible_v); + static_assert(!std::is_trivially_copy_assignable_v); + static_assert(!std::is_trivially_move_constructible_v); + static_assert(!std::is_trivially_move_assignable_v); + static_assert(!std::is_trivially_destructible_v); + T v; + int count = 0; + v.push_back(S(&count)); + v.push_back(S(&count)); + count = 0; + T w = v; + EXPECT_EQ(count, 0); // copying them is trivial + T x = std::move(v); + EXPECT_EQ(count, 0); // moving them is trivial + EXPECT_EQ(v.size(), 2); + v.clear(); + EXPECT_EQ(count, 2); // should have destroyed two S objects + w = v; + EXPECT_EQ(count, 4); // should have destroyed two S objects + v = x; + EXPECT_EQ(count, 4); // copying them is trivial + v = x; + EXPECT_EQ(count, 4); // copy-assigning them is trivial + v = std::move(x); + EXPECT_EQ(count, 4); // move-assigning them is trivial + EXPECT_EQ(x.size(), 2); + x.clear(); + EXPECT_EQ(count, 6); // should have destroyed two S objects + x = std::move(v); + EXPECT_EQ(count, 6); // move-constructing them is trivial + EXPECT_EQ(v.size(), 2); + v.clear(); + EXPECT_EQ(count, 8); // should have destroyed two S objects + x = std::move(v); + EXPECT_EQ(count, 10); // should have destroyed two S objects + EXPECT_TRUE(v.empty() && w.empty() && x.empty()); + } +} + +TEST(IPV_TEST_NAME, ZeroSized) +{ + { + sg14::inplace_vector v; + static_assert(std::is_empty_v); + static_assert(std::is_trivial_v); + static_assert(std::is_trivially_copyable_v); + EXPECT_EQ(v.size(), 0u); + EXPECT_EQ(v.max_size(), 0u); + EXPECT_EQ(v.capacity(), 0u); + EXPECT_EQ(v.try_push_back(42), nullptr); + EXPECT_EQ(v.try_emplace_back(), nullptr); + ASSERT_THROW(v.push_back(42), std::bad_alloc); + ASSERT_THROW(v.emplace_back(), std::bad_alloc); + EXPECT_TRUE(v.empty()); + EXPECT_EQ(v.erase(v.begin(), v.begin()), v.begin()); + int a[1] = {1}; + EXPECT_EQ(v.insert(v.begin(), a, a), v.begin()); + } + { + sg14::inplace_vector v; + static_assert(std::is_empty_v); + static_assert(std::is_trivial_v); + static_assert(std::is_trivially_copyable_v); + EXPECT_EQ(v.size(), 0u); + EXPECT_EQ(v.max_size(), 0u); + EXPECT_EQ(v.capacity(), 0u); + EXPECT_EQ(v.try_push_back("abc"), nullptr); + EXPECT_EQ(v.try_emplace_back("abc", 3), nullptr); + ASSERT_THROW(v.push_back("abc"), std::bad_alloc); + ASSERT_THROW(v.emplace_back("abc", 3), std::bad_alloc); + EXPECT_TRUE(v.empty()); + EXPECT_EQ(v.erase(v.begin(), v.begin()), v.begin()); + std::string a[1] = {"abc"}; + EXPECT_EQ(v.insert(v.begin(), a, a), v.begin()); + } + { + sg14::inplace_vector, 0> v; + static_assert(std::is_empty_v); + static_assert(std::is_trivial_v); + static_assert(std::is_trivially_copyable_v); + EXPECT_EQ(v.size(), 0u); + EXPECT_EQ(v.max_size(), 0u); + EXPECT_EQ(v.capacity(), 0u); + EXPECT_EQ(v.try_push_back({1,2,3}), nullptr); + EXPECT_EQ(v.try_emplace_back(), nullptr); + ASSERT_THROW(v.push_back({1,2,3}), std::bad_alloc); + ASSERT_THROW(v.emplace_back(), std::bad_alloc); + EXPECT_TRUE(v.empty()); + EXPECT_EQ(v.erase(v.begin(), v.begin()), v.begin()); + std::list a[1] = {{1,2,3}}; + EXPECT_EQ(v.insert(v.begin(), a, a), v.begin()); + } + { + sg14::inplace_vector, 0> v; + static_assert(std::is_empty_v); + static_assert(std::is_trivial_v); + static_assert(std::is_trivially_copyable_v); + EXPECT_EQ(v.size(), 0u); + EXPECT_EQ(v.max_size(), 0u); + EXPECT_EQ(v.capacity(), 0u); + EXPECT_EQ(v.try_push_back(nullptr), nullptr); + EXPECT_EQ(v.try_emplace_back(), nullptr); + ASSERT_THROW(v.push_back(nullptr), std::bad_alloc); + ASSERT_THROW(v.emplace_back(), std::bad_alloc); + EXPECT_TRUE(v.empty()); + EXPECT_EQ(v.erase(v.begin(), v.begin()), v.begin()); + std::unique_ptr a[1] = {nullptr}; + EXPECT_EQ(v.insert(v.begin(), std::make_move_iterator(a), std::make_move_iterator(a)), v.begin()); + } +} + +TEST(IPV_TEST_NAME, Iterators) +{ + { + using V = sg14::inplace_vector; + using I = typename V::iterator; + using CI = typename V::const_iterator; + using RI = typename V::reverse_iterator; + using CRI = typename V::const_reverse_iterator; + static_assert(std::is_same_v); + static_assert(std::is_same_v); + static_assert(std::is_same_v>); + static_assert(std::is_same_v>); + V v(2); + const V cv(2); + static_assert(std::is_same_v); + static_assert(std::is_same_v); + static_assert(std::is_same_v); + static_assert(std::is_same_v); + static_assert(std::is_same_v); + static_assert(std::is_same_v); + static_assert(std::is_same_v); + static_assert(std::is_same_v); + static_assert(std::is_same_v); + static_assert(std::is_same_v); + static_assert(std::is_same_v); + static_assert(std::is_same_v); + EXPECT_EQ(v.end(), v.begin() + 2); + EXPECT_EQ(v.cend(), v.cbegin() + 2); + EXPECT_EQ(v.rend(), v.rbegin() + 2); + EXPECT_EQ(v.crend(), v.crbegin() + 2); + EXPECT_EQ(cv.end(), cv.begin() + 2); + EXPECT_EQ(cv.cend(), cv.cbegin() + 2); + EXPECT_EQ(cv.rend(), cv.rbegin() + 2); + EXPECT_EQ(cv.crend(), cv.crbegin() + 2); + for (int& i : v) EXPECT_EQ(i, 0); + for (const int& i : cv) EXPECT_EQ(i, 0); + } + { + using V = sg14::inplace_vector; + using I = typename V::iterator; + using CI = typename V::const_iterator; + using RI = typename V::reverse_iterator; + using CRI = typename V::const_reverse_iterator; + static_assert(std::is_same_v); + static_assert(std::is_same_v); + static_assert(std::is_same_v>); + static_assert(std::is_same_v>); + V v(0); + const V cv(0); + static_assert(std::is_same_v); + static_assert(std::is_same_v); + static_assert(std::is_same_v); + static_assert(std::is_same_v); + static_assert(std::is_same_v); + static_assert(std::is_same_v); + static_assert(std::is_same_v); + static_assert(std::is_same_v); + static_assert(std::is_same_v); + static_assert(std::is_same_v); + static_assert(std::is_same_v); + static_assert(std::is_same_v); + EXPECT_EQ(v.end(), v.begin() + 0); + EXPECT_EQ(v.cend(), v.cbegin() + 0); + EXPECT_EQ(v.rend(), v.rbegin() + 0); + EXPECT_EQ(v.crend(), v.crbegin() + 0); + EXPECT_EQ(cv.end(), cv.begin() + 0); + EXPECT_EQ(cv.cend(), cv.cbegin() + 0); + EXPECT_EQ(cv.rend(), cv.rbegin() + 0); + EXPECT_EQ(cv.crend(), cv.crbegin() + 0); + for (int& i : v) EXPECT_EQ(i, 0); + for (const int& i : cv) EXPECT_EQ(i, 0); + } +} + +TEST(IPV_TEST_NAME, Constructors) +{ + { + using V = sg14::inplace_vector; + long a[] = {1,2,3}; + V v1; + EXPECT_TRUE(v1.empty()); + V v2 = {}; + EXPECT_TRUE(v2.empty()); + V v3 = {1,2,3}; + EXPECT_TRUE(std::equal(v3.begin(), v3.end(), a, a+3)); + auto v4 = V(a, a+3); + EXPECT_TRUE(std::equal(v4.begin(), v4.end(), a, a+3)); + auto iss = std::istringstream("1 2 3"); + auto v5 = V(std::istream_iterator(iss), std::istream_iterator()); + EXPECT_TRUE(std::equal(v5.begin(), v5.end(), a, a+3)); + auto v6 = V(3); + EXPECT_EQ(v6, V(3, 0)); + auto v7 = V(3, 42); + EXPECT_EQ(v7, (V{42, 42, 42})); + } + { + using V = sg14::inplace_vector; + const char *a[] = {"1", "2", "3"}; + V v1; + EXPECT_TRUE(v1.empty()); + V v2 = {}; + EXPECT_TRUE(v2.empty()); + V v3 = {"1", "2", "3"}; + EXPECT_TRUE(std::equal(v3.begin(), v3.end(), a, a+3)); + auto v4 = V(a, a+3); + EXPECT_TRUE(std::equal(v4.begin(), v4.end(), a, a+3)); + auto iss = std::istringstream("1 2 3"); + auto v5 = V(std::istream_iterator(iss), std::istream_iterator()); + EXPECT_TRUE(std::equal(v5.begin(), v5.end(), a, a+3)); + auto v6 = V(3); + EXPECT_EQ(v6, V(3, "")); + auto v7 = V(3, "42"); + EXPECT_EQ(v7, (V{"42", "42", "42"})); + } +#if __cpp_lib_ranges >= 201911L && __cpp_lib_ranges_to_container >= 202202L + { + auto iss = std::istringstream("1 2 3 4"); + auto rg = std::views::istream(iss); + auto v1 = sg14::inplace_vector(std::from_range, rg); + EXPECT_EQ(v1, (sg14::inplace_vector{1,2,3,4})); + auto v2 = v1 | std::ranges::to>(); + EXPECT_EQ(v2, (sg14::inplace_vector{1,2,3,4})); + } +#endif // __cpp_lib_ranges >= 201911L && __cpp_lib_ranges_to_container >= 202202L +} + +TEST(IPV_TEST_NAME, ConstructorsThrow) +{ + { + using V = sg14::inplace_vector; + long a[] = {1,2,3,4,5}; + ASSERT_NO_THROW(V(a, a+3)); + ASSERT_THROW(V(a, a+4), std::bad_alloc); + ASSERT_NO_THROW(V(3)); + ASSERT_THROW(V(4), std::bad_alloc); + ASSERT_NO_THROW(V(3, 42)); + ASSERT_THROW(V(4, 42), std::bad_alloc); + ASSERT_NO_THROW(V({1,2,3})); + ASSERT_THROW(V({1,2,3,4}), std::bad_alloc); +#if __cpp_lib_ranges >= 201911L && __cpp_lib_ranges_to_container >= 202202L + auto iss = std::istringstream("1 2 3 4"); + auto rg = std::views::istream(iss); + ASSERT_THROW(V(std::from_range, rg), std::bad_alloc); +#endif // __cpp_lib_ranges >= 201911L && __cpp_lib_ranges_to_container >= 202202L + } +} + +TEST(IPV_TEST_NAME, IterPairNonAssignable) +{ + struct NA { + std::string s_; + NA(const char *s) : s_(s) {} + NA(NA&&) = delete; + NA& operator=(NA&&) = delete; + bool operator==(const char *s) const { return s_ == s; } + }; + const char *a[] = {"1", "2", "3"}; + { + auto v = sg14::inplace_vector(a, a+3); + EXPECT_EQ(v, Seq("1", "2", "3")); + } +#if __cpp_lib_ranges >= 201911L && __cpp_lib_ranges_to_container >= 202202L + { + auto v = sg14::inplace_vector(std::from_range, a); + EXPECT_EQ(v, Seq("1", "2", "3")); + } +#endif // __cpp_lib_ranges >= 201911L && __cpp_lib_ranges_to_container >= 202202L +} + +TEST(IPV_TEST_NAME, Copying) +{ + { + using V = sg14::inplace_vector; + V source = {1,2,3}; + V dest = source; + EXPECT_EQ(dest, (V{1,2,3})); + dest = {4,5}; + EXPECT_EQ(dest, (V{4,5})); + dest = source; + EXPECT_EQ(dest, (V{1,2,3})); + EXPECT_EQ(source, (V{1,2,3})); + } + { + using V = sg14::inplace_vector; + V source = {"1", "2", "3"}; + V dest = source; + EXPECT_EQ(dest, Seq("1", "2", "3")); + dest = {"4", "5"}; + EXPECT_EQ(dest, Seq("4", "5")); + dest = source; + EXPECT_EQ(dest, Seq("1", "2", "3")); + EXPECT_EQ(source, Seq("1", "2", "3")); + } +} + +TEST(IPV_TEST_NAME, TransfersOfOwnership) +{ + { + // Trivially copyable value_type + using V = sg14::inplace_vector; + V source = {1,2,3}; + static_assert(std::is_trivially_move_constructible_v); + static_assert(std::is_trivially_move_assignable_v); + V dest = std::move(source); + EXPECT_EQ(source.size(), 3u); + EXPECT_EQ(source, (V{1,2,3})); + EXPECT_EQ(dest.size(), 3u); + EXPECT_EQ(dest, (V{1,2,3})); + dest = {1,2}; + dest = std::move(source); + EXPECT_EQ(source.size(), 3u); + EXPECT_EQ(source, (V{1,2,3})); + EXPECT_EQ(dest.size(), 3u); + EXPECT_EQ(dest, (V{1,2,3})); + } + { + // Trivially relocatable (but not trivially copyable) value_type + using V = sg14::inplace_vector, 10>; + V source; + source.push_back(std::make_unique(1)); + source.push_back(std::make_unique(2)); + source.push_back(std::make_unique(3)); + static_assert(!std::is_trivially_move_constructible_v); + static_assert(!std::is_trivially_move_assignable_v); +#if defined(__cpp_lib_trivially_relocatable) + static_assert(std::is_trivially_relocatable_v); + V dest = std::move(source); // move-construct + EXPECT_EQ(source.size(), 0u); + EXPECT_EQ(dest.size(), 3u); + EXPECT_EQ(*dest.front(), 1); + EXPECT_EQ(*dest.back(), 3); + source = V(2); + source[0] = std::make_unique(42); + source = std::move(dest); // move-assign 2 := 3 + EXPECT_EQ(source.size(), 3u); + EXPECT_EQ(dest.size(), 2u); + EXPECT_EQ(dest.front(), nullptr); + EXPECT_EQ(*source.front(), 1); + EXPECT_EQ(*source.back(), 3); + dest = V(9); + dest[0] = std::make_unique(42); + dest[4] = std::make_unique(42); + dest = std::move(source); // move-assign 9 := 3 + EXPECT_EQ(source.size(), 3u); + EXPECT_EQ(source.front(), nullptr); + EXPECT_EQ(dest.size(), 3u); + EXPECT_EQ(*dest.front(), 1); + EXPECT_EQ(*dest.back(), 3); +#else + V dest = std::move(source); // move-construct + EXPECT_EQ(source.size(), 3u); + EXPECT_EQ(source.front(), nullptr); + EXPECT_EQ(source.back(), nullptr); + EXPECT_EQ(dest.size(), 3u); + EXPECT_EQ(*dest.front(), 1); + EXPECT_EQ(*dest.back(), 3); + source = V(2); + source[0] = std::make_unique(42); + source = std::move(dest); // move-assign 2 := 3 + EXPECT_EQ(source.size(), 3u); + EXPECT_EQ(dest.size(), 3u); + EXPECT_EQ(*source.front(), 1); + EXPECT_EQ(*source.back(), 3); + dest = V(9); + dest[0] = std::make_unique(42); + dest[4] = std::make_unique(42); + dest = std::move(source); // move-assign 9 := 3 + EXPECT_EQ(source.size(), 3u); + EXPECT_EQ(dest.size(), 3u); + EXPECT_EQ(*dest.front(), 1); + EXPECT_EQ(*dest.back(), 3); +#endif + } + { + // Non-trivially relocatable value_type + using V = sg14::inplace_vector; + V source; + source.push_back("abc"); + source.push_back("def"); + source.push_back("ghi"); +#if defined(__cpp_lib_trivially_relocatable) + static_assert(!std::is_trivially_relocatable_v); +#endif + V dest = std::move(source); + EXPECT_EQ(source.size(), 3u); + EXPECT_EQ(dest.size(), 3u); + EXPECT_EQ(dest.front(), "abc"); + EXPECT_EQ(dest.back(), "ghi"); + } +} + +TEST(IPV_TEST_NAME, Clear) +{ + sg14::inplace_vector v = {1,2,3}; + v.clear(); + EXPECT_EQ(v.size(), 0u); + EXPECT_TRUE(v.empty()); +} + +TEST(IPV_TEST_NAME, Noexceptness) +{ + sg14::inplace_vector v; + static_assert(noexcept(v.size())); + static_assert(noexcept(v.empty())); + static_assert(noexcept(v.data())); + static_assert(noexcept(v.begin())); + static_assert(noexcept(v.end())); + static_assert(noexcept(v.cbegin())); + static_assert(noexcept(v.cend())); + static_assert(noexcept(v.rbegin())); + static_assert(noexcept(v.rend())); + static_assert(noexcept(v.crbegin())); + static_assert(noexcept(v.crend())); + static_assert(noexcept(v.clear())); + std::string lvalue = "abc"; + std::initializer_list il = {"abc", "def"}; + static_assert(!noexcept(v.insert(v.begin(), lvalue))); + static_assert(!noexcept(v.insert(v.begin(), il))); + static_assert(!noexcept(v.insert(v.begin(), il.begin(), il.end()))); + static_assert(!noexcept(v.emplace(v.begin(), "abc", 3))); + static_assert(!noexcept(v.emplace_back("abc", 3))); + static_assert(!noexcept(v.push_back(lvalue))); + static_assert(!noexcept(v.try_emplace_back("abc", 3))); + static_assert(!noexcept(v.try_push_back(lvalue))); + static_assert(!noexcept(v.unchecked_emplace_back("abc", 3))); + static_assert(!noexcept(v.unchecked_push_back(lvalue))); +#if __cpp_lib_ranges >= 201911L && __cpp_lib_ranges_to_container >= 202202L + static_assert(!noexcept(v.assign_range(il))); + static_assert(!noexcept(v.append_range(il))); + static_assert(!noexcept(v.insert_range(v.begin(), il))); +#endif // __cpp_lib_ranges >= 201911L && __cpp_lib_ranges_to_container >= 202202L + + // Lakos rule + static_assert(!noexcept(v.front())); + static_assert(!noexcept(v.back())); + static_assert(!noexcept(v.at(0))); + static_assert(!noexcept(v[0])); + static_assert(!noexcept(v.try_push_back(std::move(lvalue)))); + static_assert(!noexcept(v.unchecked_push_back(std::move(lvalue)))); + static_assert(!noexcept(v.erase(v.begin()))); + static_assert(!noexcept(v.erase(v.begin(), v.end()))); +} + +TEST(IPV_TEST_NAME, NoexceptnessOfSwap) +{ + using std::swap; + { + std::string lvalue; + sg14::inplace_vector v; + static_assert(noexcept(swap(lvalue, lvalue))); + static_assert(noexcept(v.swap(v))); + static_assert(noexcept(swap(v, v))); + } + { + struct ThrowingSwappable { + explicit ThrowingSwappable() { } + ThrowingSwappable(const ThrowingSwappable&) { } + void operator=(const ThrowingSwappable&) { } + ~ThrowingSwappable() { } + }; + ThrowingSwappable lvalue; + sg14::inplace_vector v; + static_assert(!noexcept(swap(lvalue, lvalue))); + static_assert(!noexcept(v.swap(v))); + static_assert(!noexcept(swap(v, v))); + } +} + +TEST(IPV_TEST_NAME, InsertSingle) +{ + { + using V = sg14::inplace_vector; + V v = {1, 4, 7}; + int lvalue = 23; + static_assert(std::is_same_v); + static_assert(std::is_same_v); + static_assert(std::is_same_v); + static_assert(std::is_same_v); + auto it = v.insert(v.begin(), 24); + EXPECT_EQ(it, v.begin()); + EXPECT_EQ(v, (V{24, 1, 4, 7})); + it = v.insert(v.begin() + 2, lvalue); + EXPECT_EQ(it, v.begin() + 2); + EXPECT_EQ(v, (V{24, 1, 23, 4, 7})); + ASSERT_THROW(v.insert(v.begin() + 2, 23), std::bad_alloc); + ASSERT_THROW(v.insert(v.end(), lvalue), std::bad_alloc); + it = v.erase(v.begin() + 1); + EXPECT_EQ(it, v.begin() + 1); + EXPECT_EQ(v, (V{24, 23, 4, 7})); + it = v.insert(v.end(), 10); + EXPECT_EQ(it, v.end() - 1); + EXPECT_EQ(v, (V{24, 23, 4, 7, 10})); + EXPECT_EQ(v.size(), 5u); + } + { + using V = sg14::inplace_vector; + V v = {"abc", "def", "ghi"}; + auto it = v.insert(v.begin(), "xyz"); + EXPECT_EQ(it, v.begin()); + EXPECT_EQ(v, (V{"xyz", "abc", "def", "ghi"})); + std::string lvalue = "wxy"; + it = v.insert(v.begin() + 2, lvalue); + EXPECT_EQ(it, v.begin() + 2); + EXPECT_EQ(v, (V{"xyz", "abc", "wxy", "def", "ghi"})); + ASSERT_THROW(v.insert(v.begin() + 2, "wxy"), std::bad_alloc); + ASSERT_THROW(v.insert(v.end(), lvalue), std::bad_alloc); + it = v.erase(v.begin() + 1); + EXPECT_EQ(it, v.begin() + 1); + EXPECT_EQ(v, (V{"xyz", "wxy", "def", "ghi"})); + it = v.insert(v.end(), "jkl"); + EXPECT_EQ(it, v.end() - 1); + EXPECT_EQ(v, (V{"xyz", "wxy", "def", "ghi", "jkl"})); + EXPECT_EQ(v.size(), 5u); + } + { + using V = sg14::inplace_vector, 5>; + V v = {{1,2,3}, {4,5,6}, {7,8,9}}; + auto it = v.insert(v.begin(), {24,25,26}); + EXPECT_EQ(it, v.begin()); + EXPECT_EQ(v, (V{{24,25,26}, {1,2,3}, {4,5,6}, {7,8,9}})); + std::list lvalue = {23,24,25}; + it = v.insert(v.begin() + 2, lvalue); + EXPECT_EQ(it, v.begin() + 2); + EXPECT_EQ(v, (V{{24,25,26}, {1,2,3}, {23,24,25}, {4,5,6}, {7,8,9}})); + ASSERT_THROW(v.insert(v.begin() + 2, {23,24,25}), std::bad_alloc); + ASSERT_THROW(v.insert(v.end(), lvalue), std::bad_alloc); + it = v.erase(v.begin() + 1); + EXPECT_EQ(it, v.begin() + 1); + EXPECT_EQ(v, (V{{24,25,26}, {23,24,25}, {4,5,6}, {7,8,9}})); + it = v.insert(v.end(), {10,11,12}); + EXPECT_EQ(it, v.end() - 1); + EXPECT_EQ(v, (V{{24,25,26}, {23,24,25}, {4,5,6}, {7,8,9}, {10,11,12}})); + EXPECT_EQ(v.size(), 5u); + } + { + using V = sg14::inplace_vector; + MoveOnly a[3] = {"abc", "def", "ghi"}; + V v = V(std::make_move_iterator(a), std::make_move_iterator(a+3)); + auto it = v.insert(v.begin(), "xyz"); + EXPECT_EQ(it, v.begin()); + EXPECT_EQ(*it, "xyz"); + MoveOnly lvalue = "wxy"; + it = v.insert(v.begin() + 2, std::move(lvalue)); + EXPECT_EQ(it, v.begin() + 2); + EXPECT_EQ(v, Seq("xyz", "abc", "wxy", "def", "ghi")); + ASSERT_THROW(v.insert(v.begin() + 2, "wxy"), std::bad_alloc); + it = v.erase(v.begin() + 1); + EXPECT_EQ(it, v.begin() + 1); + EXPECT_EQ(*it, "wxy"); + it = v.insert(v.end(), "jkl"); + EXPECT_EQ(it, v.end() - 1); + EXPECT_EQ(v, Seq("xyz", "wxy", "def", "ghi", "jkl")); + EXPECT_EQ(v.size(), 5u); + } +} + +TEST(IPV_TEST_NAME, InsertMulti) +{ + { + using V = sg14::inplace_vector; + V v = {1, 2, 3}; + int a[2] = {4, 5}; + static_assert(std::is_same_v); + static_assert(std::is_same_v); + static_assert(std::is_same_v); + static_assert(std::is_same_v); + auto it = v.insert(v.begin(), a, a+2); + EXPECT_EQ(it, v.begin()); + EXPECT_EQ(v, (V{4, 5, 1, 2, 3})); + it = v.insert(v.begin(), a, a); + EXPECT_EQ(it, v.begin()); + EXPECT_EQ(v, (V{4, 5, 1, 2, 3})); + ASSERT_THROW(v.insert(v.begin(), a, a+2), std::bad_alloc); + v.clear(); + it = v.insert(v.begin(), a, a+2); + EXPECT_EQ(it, v.begin()); + it = v.insert(v.end(), {1, 2}); // insert(initializer_list) + EXPECT_EQ(it, v.begin() + 2); + EXPECT_EQ(v, (V{4, 5, 1, 2})); + ASSERT_THROW(v.insert(v.begin() + 2, a, a+2), std::bad_alloc); + EXPECT_LE(v.size(), 5u); + } + { + using V = sg14::inplace_vector; + V v = {"1", "2", "3"}; + const char *a[2] = {"4", "5"}; + auto it = v.insert(v.begin(), a, a+2); + EXPECT_EQ(it, v.begin()); + EXPECT_EQ(v, (V{"4", "5", "1", "2", "3"})); + it = v.insert(v.begin(), a, a); + EXPECT_EQ(it, v.begin()); + EXPECT_EQ(v, (V{"4", "5", "1", "2", "3"})); + ASSERT_THROW(v.insert(v.begin(), a, a+2), std::bad_alloc); + v.clear(); + it = v.insert(v.begin(), a, a+2); + EXPECT_EQ(it, v.begin()); + it = v.insert(v.end(), {"1", "2"}); // insert(initializer_list) + EXPECT_EQ(it, v.begin() + 2); + EXPECT_EQ(v, (V{"4", "5", "1", "2"})); + ASSERT_THROW(v.insert(v.begin() + 2, a, a+2), std::bad_alloc); + EXPECT_LE(v.size(), 5u); + } + { + using V = sg14::inplace_vector; + V v; + v.emplace_back("1"); + v.emplace_back("2"); + v.emplace_back("3"); + const char *a[2] = {"4", "5"}; + auto it = v.insert(v.begin(), a, a+2); + EXPECT_EQ(it, v.begin()); + EXPECT_EQ(v, Seq("4", "5", "1", "2", "3")); + it = v.insert(v.begin(), a, a); + EXPECT_EQ(it, v.begin()); + EXPECT_EQ(v, Seq("4", "5", "1", "2", "3")); + ASSERT_THROW(v.insert(v.begin(), a, a+2), std::bad_alloc); + v.clear(); + it = v.insert(v.begin(), a, a+2); + EXPECT_EQ(it, v.begin()); + MoveOnly il[2] = {"1", "2"}; + it = v.insert(v.end(), std::make_move_iterator(il), std::make_move_iterator(il + 2)); + EXPECT_EQ(it, v.begin() + 2); + EXPECT_EQ(v, Seq("4", "5", "1", "2")); + ASSERT_THROW(v.insert(v.begin() + 2, a, a+2), std::bad_alloc); + EXPECT_LE(v.size(), 5u); + } + { + using V = sg14::inplace_vector; + V v; + v.emplace_back("1"); + v.emplace_back("2"); + v.emplace_back("3"); + const char *a[2] = {"4", "5"}; + auto it = v.insert(v.begin(), a, a+2); + EXPECT_EQ(it, v.begin()); + EXPECT_EQ(v, Seq("4", "5", "1", "2", "3")); + it = v.insert(v.begin(), a, a); + EXPECT_EQ(it, v.begin()); + EXPECT_EQ(v, Seq("4", "5", "1", "2", "3")); + ASSERT_THROW(v.insert(v.begin(), a, a+2), std::bad_alloc); + v.clear(); + it = v.insert(v.begin(), a, a+2); + EXPECT_EQ(it, v.begin()); + MoveOnlyNT il[2] = {"1", "2"}; + it = v.insert(v.end(), std::make_move_iterator(il), std::make_move_iterator(il + 2)); + EXPECT_EQ(it, v.begin() + 2); + EXPECT_EQ(v, Seq("4", "5", "1", "2")); + ASSERT_THROW(v.insert(v.begin() + 2, a, a+2), std::bad_alloc); + EXPECT_LE(v.size(), 5u); + } +} + +TEST(IPV_TEST_NAME, Assign) +{ + { + using V = sg14::inplace_vector; + V v; + int a[] = {1, 2, 3, 4, 5, 6}; + v.assign(a, a+2); + static_assert(std::is_same_v); + EXPECT_EQ(v, (V{1, 2})); + v.assign(a+2, a+6); + EXPECT_EQ(v, (V{3, 4, 5, 6})); + v.assign(a, a+2); + EXPECT_EQ(v, (V{1, 2})); + ASSERT_THROW(v.assign(a, a+6), std::bad_alloc); + EXPECT_LE(v.size(), 5u); + } + { + using V = sg14::inplace_vector; + V v; + const char *a[] = {"1", "2", "3", "4", "5", "6"}; + v.assign(a, a+2); + EXPECT_EQ(v, Seq("1", "2")); + v.assign(a+2, a+6); + EXPECT_EQ(v, Seq("3", "4", "5", "6")); + v.assign(a, a+2); + EXPECT_EQ(v, Seq("1", "2")); + ASSERT_THROW(v.assign(a, a+6), std::bad_alloc); + EXPECT_LE(v.size(), 5u); + } + { + using V = sg14::inplace_vector; + V v; + auto iss = std::istringstream("1 2"); + v.assign(std::istream_iterator(iss), {}); + EXPECT_EQ(v, (V{1, 2})); + iss = std::istringstream("4 5 6 7"); + v.assign(std::istream_iterator(iss), {}); + EXPECT_EQ(v, (V{4, 5, 6, 7})); + iss = std::istringstream("1 2"); + v.assign(std::istream_iterator(iss), {}); + EXPECT_EQ(v, (V{1, 2})); + iss = std::istringstream("1 2 3 4 5 6"); + ASSERT_THROW(v.assign(std::istream_iterator(iss), {}), std::bad_alloc); + EXPECT_LE(v.size(), 5u); + } + { + using V = sg14::inplace_vector; + const char *a[] = {"1", "2", "3", "4", "5", "6"}; + V v; + v.assign(a, a+2); + EXPECT_EQ(v, Seq("1", "2")); + v.assign(a+2, a+6); + EXPECT_EQ(v, Seq("3", "4", "5", "6")); + v.assign(a, a+2); + EXPECT_EQ(v, Seq("1", "2")); + ASSERT_THROW(v.assign(a, a+6), std::bad_alloc); + EXPECT_LE(v.size(), 5u); + } +} + +TEST(IPV_TEST_NAME, AssignRange) +{ +#if __cpp_lib_ranges >= 201911L && __cpp_lib_ranges_to_container >= 202202L + { + using V = sg14::inplace_vector; + V v; + v.assign_range(std::vector{1, 2}); + static_assert(std::is_same_v); + EXPECT_EQ(v, (V{1, 2})); + v.assign_range(std::vector{4, 5, 6, 7}); + EXPECT_EQ(v, (V{4, 5, 6, 7})); + v.assign_range(std::vector{1, 2}); + EXPECT_EQ(v, (V{1, 2})); + ASSERT_THROW(v.assign_range(std::vector(6)), std::bad_alloc); + EXPECT_LE(v.size(), 5u); + } + { + using V = sg14::inplace_vector; + V v; + v.assign_range(std::vector{"1", "2"}); + EXPECT_EQ(v, Seq("1", "2")); + v.assign_range(std::vector{"4", "5", "6", "7"}); + EXPECT_EQ(v, Seq("4", "5", "6", "7")); + v.assign_range(std::vector{"1", "2"}); + EXPECT_EQ(v, Seq("1", "2")); + ASSERT_THROW(v.assign_range(std::vector(6)), std::bad_alloc); + EXPECT_LE(v.size(), 5u); + } + { + using V = sg14::inplace_vector; + V v; + auto iss = std::istringstream("1 2"); + v.assign_range(std::views::istream(iss)); + EXPECT_EQ(v, (V{1, 2})); + iss = std::istringstream("4 5 6 7"); + v.assign_range(std::views::istream(iss)); + EXPECT_EQ(v, (V{4, 5, 6, 7})); + iss = std::istringstream("1 2"); + v.assign_range(std::views::istream(iss)); + EXPECT_EQ(v, (V{1, 2})); + iss = std::istringstream("1 2 3 4 5 6"); + ASSERT_THROW(v.assign_range(std::views::istream(iss)), std::bad_alloc); + EXPECT_LE(v.size(), 5u); + } + { + using V = sg14::inplace_vector; + auto iss = std::istringstream("4 5 6 7"); + V v = {"1", "2"}; + v.assign_range(std::views::istream(iss) | std::views::take(3)); + EXPECT_EQ(v, Seq("4", "5", "6")); + iss = std::istringstream("6 7"); + v.assign_range(std::views::istream(iss) | std::views::take(2)); + EXPECT_EQ(v, Seq("6", "7")); + iss = std::istringstream("6 7 8 9 10 11"); + ASSERT_THROW(v.assign_range(std::views::istream(iss) | std::views::take(6)), std::bad_alloc); + EXPECT_LE(v.size(), 5u); + } + { + using V = sg14::inplace_vector; + const char *a[] = {"1", "2", "3", "4", "5", "6"}; + V v; + v.assign_range(a | std::views::take(2)); + EXPECT_EQ(v, Seq("1", "2")); + v.assign_range(a | std::views::drop(2) | std::views::take(4)); + EXPECT_EQ(v, Seq("3", "4", "5", "6")); + v.assign_range(a | std::views::take(2)); + EXPECT_EQ(v, Seq("1", "2")); + ASSERT_THROW(v.assign_range(a), std::bad_alloc); + EXPECT_LE(v.size(), 5u); + } +#if __cpp_lib_ranges_as_rvalue >= 202207L + { + using V = sg14::inplace_vector; + MoveOnly a[] = {"1", "2", "3", "4", "5", "6", "7", "8"}; + V v; + v.assign_range(a | std::views::as_rvalue | std::views::take(2)); + EXPECT_EQ(v, Seq("1", "2")); + v.assign_range(a | std::views::as_rvalue | std::views::drop(2) | std::views::take(4)); + EXPECT_EQ(v, Seq("3", "4", "5", "6")); + v.assign_range(a | std::views::as_rvalue | std::views::drop(6) | std::views::take(2)); + EXPECT_EQ(v, Seq("7", "8")); + ASSERT_THROW(v.assign_range(a | std::views::as_rvalue), std::bad_alloc); + EXPECT_LE(v.size(), 5u); + } +#endif // __cpp_lib_ranges_as_rvalue >= 202207L +#endif // __cpp_lib_ranges >= 201911L && __cpp_lib_ranges_to_container >= 202202L +} + +TEST(IPV_TEST_NAME, InsertRange) +{ +#if __cpp_lib_ranges >= 201911L && __cpp_lib_ranges_to_container >= 202202L + { + using V = sg14::inplace_vector; + V v = {1, 2}; + auto it = v.insert_range(v.begin() + 1, std::vector{4, 5}); + static_assert(std::is_same_v); + EXPECT_EQ(it, v.begin() + 1); + EXPECT_EQ(v, (V{1, 4, 5, 2})); + ASSERT_THROW(v.insert_range(v.begin() + 1, std::vector{4, 5}), std::bad_alloc); + EXPECT_EQ(v, (V{1, 4, 5, 2})); + } + { + using V = sg14::inplace_vector; + auto iss = std::istringstream("4 5 6 7"); + V v = {1, 2, 3}; + auto it = v.insert_range(v.begin() + 1, std::views::istream(iss) | std::views::take(2)); + static_assert(std::is_same_v); + EXPECT_EQ(it, v.begin() + 1); + EXPECT_EQ(v, (V{1, 4, 5, 2, 3})); + iss = std::istringstream("6 7"); + ASSERT_THROW(v.insert_range(v.begin() + 1, std::views::istream(iss) | std::views::take(2)), std::bad_alloc); + EXPECT_EQ(v, (V{1, 4, 5, 2, 3})); + } + { + using V = sg14::inplace_vector; + auto iss = std::istringstream("4 5 6 7"); + V v = {"1", "2"}; + auto it = v.insert_range(v.begin() + 1, std::views::istream(iss) | std::views::take(2)); + EXPECT_EQ(it, v.begin() + 1); + EXPECT_EQ(v, (V{"1", "4", "5", "2"})); + iss = std::istringstream("6 7"); + ASSERT_THROW(v.insert_range(v.begin() + 1, std::views::istream(iss) | std::views::take(2)), std::bad_alloc); + EXPECT_EQ(v, (V{"1", "4", "5", "2"})); + } +#if __cpp_lib_ranges_as_rvalue >= 202207L + { + using V = sg14::inplace_vector; + MoveOnly a[2] = {"abc", "def"}; + V v; + v.emplace_back("wxy"); + v.emplace_back("xyz"); + auto it = v.insert_range(v.begin() + 1, a | std::views::as_rvalue); + EXPECT_EQ(it, v.begin() + 1); + EXPECT_EQ(v, Seq("wxy", "abc", "def", "xyz")); + ASSERT_THROW(v.insert_range(v.begin() + 1, a | std::views::as_rvalue), std::bad_alloc); + EXPECT_EQ(v, Seq("wxy", "abc", "def", "xyz")); + } +#endif // __cpp_lib_ranges_as_rvalue >= 202207L +#endif // __cpp_lib_ranges >= 201911L && __cpp_lib_ranges_to_container >= 202202L +} + +TEST(IPV_TEST_NAME, AppendRange) +{ +#if __cpp_lib_ranges >= 201911L && __cpp_lib_ranges_to_container >= 202202L + { + using V = sg14::inplace_vector; + V v = {1, 2}; + v.append_range(std::vector{4, 5}); + static_assert(std::is_same_v{4, 5})), void>); + EXPECT_EQ(v, (V{1, 2, 4, 5})); + ASSERT_THROW(v.append_range(std::vector{4, 5}), std::bad_alloc); + EXPECT_LE(v.size(), 5u); + } + { + using V = sg14::inplace_vector; + auto iss = std::istringstream("4 5 6 7"); + V v = {1, 2, 3}; + v.append_range(std::views::istream(iss) | std::views::take(2)); + EXPECT_EQ(v, (V{1, 2, 3, 4, 5})); + iss = std::istringstream("6 7"); + ASSERT_THROW(v.append_range(std::views::istream(iss) | std::views::take(2)), std::bad_alloc); + EXPECT_LE(v.size(), 5u); + } + { + using V = sg14::inplace_vector; + auto iss = std::istringstream("4 5 6 7"); + V v = {"1", "2"}; + v.append_range(std::views::istream(iss) | std::views::take(2)); + EXPECT_EQ(v, Seq("1", "2", "4", "5")); + iss = std::istringstream("6 7"); + ASSERT_THROW(v.append_range(std::views::istream(iss) | std::views::take(2)), std::bad_alloc); + EXPECT_LE(v.size(), 5u); + } +#if __cpp_lib_ranges_as_rvalue >= 202207L + { + using V = sg14::inplace_vector; + MoveOnly a[2] = {"abc", "def"}; + V v; + v.emplace_back("wxy"); + v.emplace_back("xyz"); + v.append_range(a | std::views::as_rvalue); + EXPECT_EQ(v, Seq("wxy", "xyz", "abc", "def")); + ASSERT_THROW(v.append_range(a | std::views::as_rvalue), std::bad_alloc); + EXPECT_LE(v.size(), 5u); + } +#endif // __cpp_lib_ranges_as_rvalue >= 202207L +#endif // __cpp_lib_ranges >= 201911L && __cpp_lib_ranges_to_container >= 202202L +} + +TEST(IPV_TEST_NAME, PushBack) +{ + { + using V = sg14::inplace_vector; + V v = {1, 4}; + int lvalue = 23; + static_assert(std::is_same_v); + static_assert(std::is_same_v); + static_assert(std::is_same_v); + EXPECT_EQ(&v.push_back(24), v.data() + 2); + EXPECT_EQ(&v.push_back(lvalue), v.data() + 3); + EXPECT_EQ(&v.emplace_back(), v.data() + 4); + EXPECT_EQ(v, (V{1, 4, 24, 23, 0})); + EXPECT_THROW(v.push_back(24), std::bad_alloc); + EXPECT_THROW(v.push_back(lvalue), std::bad_alloc); + EXPECT_THROW(v.emplace_back(), std::bad_alloc); + EXPECT_EQ(v, (V{1, 4, 24, 23, 0})); // failed push_back has the strong guarantee + + v = {1, 4}; + static_assert(std::is_same_v); + static_assert(std::is_same_v); + static_assert(std::is_same_v); + EXPECT_EQ(v.try_push_back(24), v.data() + 2); + EXPECT_EQ(v.try_push_back(lvalue), v.data() + 3); + EXPECT_EQ(v.try_emplace_back(), v.data() + 4); + EXPECT_EQ(v, (V{1, 4, 24, 23, 0})); + EXPECT_EQ(v.try_push_back(24), nullptr); + EXPECT_EQ(v.try_push_back(lvalue), nullptr); + EXPECT_EQ(v.try_emplace_back(), nullptr); + EXPECT_EQ(v, (V{1, 4, 24, 23, 0})); // failed try_push_back has the strong guarantee + } + { + using V = sg14::inplace_vector; + V v = {"1", "4"}; + std::string lvalue = "23"; + static_assert(std::is_same_v); + static_assert(std::is_same_v); + static_assert(std::is_same_v); + EXPECT_EQ(&v.push_back("24"), v.data() + 2); + EXPECT_EQ(&v.push_back(lvalue), v.data() + 3); + EXPECT_EQ(&v.emplace_back(), v.data() + 4); + EXPECT_EQ(v, (V{"1", "4", "24", "23", ""})); + EXPECT_THROW(v.push_back("24"), std::bad_alloc); + EXPECT_THROW(v.push_back(lvalue), std::bad_alloc); + EXPECT_THROW(v.emplace_back(), std::bad_alloc); + EXPECT_EQ(v, (V{"1", "4", "24", "23", ""})); // failed push_back has the strong guarantee + + v = {"1", "4"}; + static_assert(std::is_same_v); + static_assert(std::is_same_v); + static_assert(std::is_same_v); + EXPECT_EQ(v.try_push_back("24"), v.data() + 2); + EXPECT_EQ(v.try_push_back(lvalue), v.data() + 3); + EXPECT_EQ(v.try_emplace_back(), v.data() + 4); + EXPECT_EQ(v, (V{"1", "4", "24", "23", ""})); + EXPECT_EQ(v.try_push_back("24"), nullptr); + EXPECT_EQ(v.try_push_back(lvalue), nullptr); + EXPECT_EQ(v.try_emplace_back(), nullptr); + EXPECT_EQ(v, (V{"1", "4", "24", "23", ""})); // failed try_push_back has the strong guarantee + } +} + +TEST(IPV_TEST_NAME, EraseSingle) +{ + { + using V = sg14::inplace_vector; + V v = {1, 2, 3, 4}; + auto it = v.erase(v.begin() + 1); + static_assert(std::is_same_v); + EXPECT_EQ(it, v.begin() + 1); + EXPECT_EQ(v, (V{1, 3, 4})); + it = v.erase(v.begin()); + EXPECT_EQ(it, v.begin()); + EXPECT_EQ(v, (V{3, 4})); + it = v.erase(v.begin() + 1); + EXPECT_EQ(it, v.end()); + EXPECT_EQ(v, V{3}); + it = v.erase(v.begin()); + EXPECT_EQ(it, v.end()); + EXPECT_TRUE(v.empty()); + } + { + using V = sg14::inplace_vector; + V v = {"1", "2", "3", "4"}; + auto it = v.erase(v.begin() + 1); + static_assert(std::is_same_v); + EXPECT_EQ(it, v.begin() + 1); + EXPECT_EQ(v, (V{"1", "3", "4"})); + it = v.erase(v.begin()); + EXPECT_EQ(it, v.begin()); + EXPECT_EQ(v, (V{"3", "4"})); + it = v.erase(v.begin() + 1); + EXPECT_EQ(it, v.end()); + EXPECT_EQ(v, V{"3"}); + it = v.erase(v.begin()); + EXPECT_EQ(it, v.end()); + EXPECT_TRUE(v.empty()); + } + { + using V = sg14::inplace_vector; + const char *a[4] = {"1", "2", "3", "4"}; + V v = V(a, a+4); + auto it = v.erase(v.begin() + 1); + static_assert(std::is_same_v); + EXPECT_EQ(it, v.begin() + 1); + EXPECT_EQ(v.size(), 3u); + it = v.erase(v.begin()); + EXPECT_EQ(it, v.begin()); + EXPECT_EQ(v, V(a+2, a+4)); + it = v.erase(v.begin() + 1); + EXPECT_EQ(it, v.end()); + EXPECT_EQ(v, V(a+2, a+3)); + it = v.erase(v.begin()); + EXPECT_EQ(it, v.end()); + EXPECT_TRUE(v.empty()); + } +} + +TEST(IPV_TEST_NAME, EraseMulti) +{ + { + using V = sg14::inplace_vector; + V v = {1, 2, 3, 4}; + auto it = v.erase(v.begin() + 1, v.begin() + 3); + static_assert(std::is_same_v); + EXPECT_EQ(it, v.begin() + 1); + EXPECT_EQ(v, (V{1, 4})); + it = v.erase(v.begin(), v.begin()); + EXPECT_EQ(it, v.begin()); + EXPECT_EQ(v, (V{1, 4})); + it = v.erase(v.begin() + 1, v.end()); + EXPECT_EQ(it, v.end()); + EXPECT_EQ(v, V{1}); + it = v.erase(v.begin(), v.end()); + EXPECT_EQ(it, v.end()); + EXPECT_TRUE(v.empty()); + } + { + using V = sg14::inplace_vector; + V v = {"1", "2", "3", "4"}; + auto it = v.erase(v.begin() + 1, v.begin() + 3); + static_assert(std::is_same_v); + EXPECT_EQ(it, v.begin() + 1); + EXPECT_EQ(v, (V{"1", "4"})); + it = v.erase(v.begin(), v.begin()); + EXPECT_EQ(it, v.begin()); + EXPECT_EQ(v, (V{"1", "4"})); + it = v.erase(v.begin() + 1, v.end()); + EXPECT_EQ(it, v.end()); + EXPECT_EQ(v, V{"1"}); + it = v.erase(v.begin(), v.end()); + EXPECT_EQ(it, v.end()); + EXPECT_TRUE(v.empty()); + } + { + using V = sg14::inplace_vector; + const char *a[4] = {"1", "2", "3", "4"}; + V v = V(a, a+4); + auto it = v.erase(v.begin() + 1, v.begin() + 3); + static_assert(std::is_same_v); + EXPECT_EQ(it, v.begin() + 1); + EXPECT_EQ(v, Seq("1", "4")); + it = v.erase(v.begin(), v.begin()); + EXPECT_EQ(it, v.begin()); + EXPECT_EQ(v, Seq("1", "4")); + it = v.erase(v.begin() + 1, v.end()); + EXPECT_EQ(it, v.end()); + EXPECT_EQ(v, Seq("1")); + it = v.erase(v.begin(), v.end()); + EXPECT_EQ(it, v.end()); + EXPECT_TRUE(v.empty()); + } + { + using V = sg14::inplace_vector; + const char *a[4] = {"1", "2", "3", "4"}; + V v = V(a, a+4); + auto it = v.erase(v.begin() + 1, v.begin() + 3); + static_assert(std::is_same_v); + EXPECT_EQ(it, v.begin() + 1); + EXPECT_EQ(v, Seq("1", "4")); + it = v.erase(v.begin(), v.begin()); + EXPECT_EQ(it, v.begin()); + EXPECT_EQ(v, Seq("1", "4")); + it = v.erase(v.begin() + 1, v.end()); + EXPECT_EQ(it, v.end()); + EXPECT_EQ(v, Seq("1")); + it = v.erase(v.begin(), v.end()); + EXPECT_EQ(it, v.end()); + EXPECT_TRUE(v.empty()); + } +} + +TEST(IPV_TEST_NAME, EraseRemoveIdiom) +{ + sg14::inplace_vector v; + std::mt19937 g; + std::generate_n(std::back_inserter(v), 1000, std::ref(g)); + v.erase(std::remove_if(v.begin(), v.end(), [](auto x) { return x % 100 == 0; }), v.end()); + EXPECT_TRUE(std::none_of(v.begin(), v.end(), [](auto x) { return x % 100 == 0; })); + v.erase(std::remove_if(v.begin(), v.end(), [](auto x) { return x % 100 == 1; }), v.end()); + EXPECT_TRUE(std::none_of(v.begin(), v.end(), [](auto x) { return x % 100 == 1; })); + for (unsigned i = 0; i < 100; ++i) { + v.erase(std::remove_if(v.begin(), v.end(), [&](auto x) { return x % 100 == i; }), v.end()); + EXPECT_TRUE(std::none_of(v.begin(), v.end(), [&](auto x) { return x % 100 == i; })); + } + EXPECT_TRUE(v.empty()); +} + +TEST(IPV_TEST_NAME, AssignFromInitList) +{ + { + using V = sg14::inplace_vector, 4>; + V v = {{1,2}, {3,4}}; + v = {{5,6}}; + EXPECT_EQ(v, (V{{5,6}})); + v = {{7,8},{9,10},{11,12}}; + EXPECT_EQ(v, (V{{7,8},{9,10},{11,12}})); + ASSERT_THROW((v = {{}, {}, {}, {}, {}, {}}), std::bad_alloc); + static_assert(std::is_same_v); + static_assert(std::is_same_v); + } + { + struct Counter { + int i_ = 0; + Counter(int i): i_(i) {} + Counter(const Counter& rhs) : i_(rhs.i_ + 1) {} + void operator=(const Counter& rhs) { i_ = rhs.i_ + 10; } + }; + sg14::inplace_vector v = { 100 }; + EXPECT_EQ(v[0].i_, 101); // copied from init-list into vector + v = { 200, 300 }; + EXPECT_EQ(v[0].i_, 210); // assigned from init-list into vector + EXPECT_EQ(v[1].i_, 301); // copied from init-list into vector + } +} + +TEST(IPV_TEST_NAME, Comparison) +{ + sg14::inplace_vector a; + sg14::inplace_vector b; + EXPECT_TRUE((a == b) && (a <= b) && (a >= b)); + EXPECT_FALSE((a != b) || (a < b) || (a > b)); +#if __cpp_impl_three_way_comparison >= 201907L + EXPECT_EQ(a <=> b, std::strong_ordering::equal); +#endif + a = {1,2,3}; + b = {1,2,3}; + EXPECT_TRUE((a == b) && (a <= b) && (a >= b)); + EXPECT_FALSE((a != b) || (a < b) || (a > b)); +#if __cpp_impl_three_way_comparison >= 201907L + EXPECT_EQ(a <=> b, std::strong_ordering::equal); +#endif + a = {1,2,3}; + b = {1}; + EXPECT_TRUE((a != b) && (a > b) && (a >= b)); + EXPECT_TRUE((b != a) && (b < a) && (b <= a)); + EXPECT_FALSE((a == b) || (a < b) || (a <= b)); + EXPECT_FALSE((b == a) || (b > a) || (b >= a)); +#if __cpp_impl_three_way_comparison >= 201907L + EXPECT_EQ(a <=> b, std::strong_ordering::greater); + EXPECT_EQ(b <=> a, std::strong_ordering::less); +#endif + a = {1,3}; + b = {1,2,3}; + EXPECT_TRUE((a != b) && (a > b) && (a >= b)); + EXPECT_TRUE((b != a) && (b < a) && (b <= a)); + EXPECT_FALSE((a == b) || (a < b) || (a <= b)); + EXPECT_FALSE((b == a) || (b > a) || (b >= a)); +#if __cpp_impl_three_way_comparison >= 201907L + EXPECT_EQ(a <=> b, std::strong_ordering::greater); + EXPECT_EQ(b <=> a, std::strong_ordering::less); +#endif + a = {1,2,4}; + b = {1,2,3}; + EXPECT_TRUE((a != b) && (a > b) && (a >= b)); + EXPECT_TRUE((b != a) && (b < a) && (b <= a)); + EXPECT_FALSE((a == b) || (a < b) || (a <= b)); + EXPECT_FALSE((b == a) || (b > a) || (b >= a)); +#if __cpp_impl_three_way_comparison >= 201907L + EXPECT_EQ(a <=> b, std::strong_ordering::greater); + EXPECT_EQ(b <=> a, std::strong_ordering::less); +#endif +} + +TEST(IPV_TEST_NAME, Reserve) +{ + sg14::inplace_vector v = {1,2,3}; + v.reserve(0); + EXPECT_EQ(v.capacity(), 10u); + v.reserve(5); + EXPECT_EQ(v.capacity(), 10u); + v.reserve(10); + EXPECT_EQ(v.capacity(), 10u); + ASSERT_THROW(v.reserve(11), std::bad_alloc); + v.shrink_to_fit(); + EXPECT_EQ(v.capacity(), 10u); + EXPECT_EQ(v, (sg14::inplace_vector{1,2,3})); +} + +TEST(IPV_TEST_NAME, Resize) +{ + sg14::inplace_vector v; + v.resize(2); + EXPECT_EQ(v, (sg14::inplace_vector{"", ""})); + v.resize(1, "a"); + EXPECT_EQ(v, (sg14::inplace_vector{""})); + v.resize(3, "b"); + EXPECT_EQ(v, (sg14::inplace_vector{"", "b", "b"})); + v.resize(4); + EXPECT_EQ(v, (sg14::inplace_vector{"", "b", "b", ""})); + v.resize(2, "c"); + EXPECT_EQ(v, (sg14::inplace_vector{"", "b"})); + ASSERT_THROW(v.resize(5), std::bad_alloc); + ASSERT_THROW(v.resize(6, "d"), std::bad_alloc); + EXPECT_EQ(v, (sg14::inplace_vector{"", "b"})); // unchanged +} diff --git a/test/inplace_vector_test.cpp b/test/inplace_vector_test.cpp index 8a3d523..9eabd76 100644 --- a/test/inplace_vector_test.cpp +++ b/test/inplace_vector_test.cpp @@ -1,1546 +1,9 @@ -#if __cplusplus >= 201703 +#if __cplusplus >= 201703L #include +#define IPV_TEST_NAME inplace_vector +#define IPV_HAS_CONDITIONALLY_TRIVIAL_SMFS (__cpp_concepts >= 202002L) +#include "inplace_vector_common_tests.cpp" -#include +#endif // __cplusplus >= 201703L -#include -#include -#include -#include -#include -#if __has_include() -#include -#endif -#include -#include -#include -#include - -struct Seq { - std::vector v_; - - template - explicit Seq(const Chars*... ts) : v_{ts...} {} - - template - friend bool operator==(const V& v, const Seq& seq) { - return std::equal(v.begin(), v.end(), seq.v_.begin(), seq.v_.end()); - } -}; - -struct MoveOnly { - MoveOnly(const char *s) : s_(std::make_unique(s)) {} - friend bool operator==(const MoveOnly& a, const MoveOnly& b) { return *a.s_ == *b.s_; } - friend bool operator!=(const MoveOnly& a, const MoveOnly& b) { return *a.s_ != *b.s_; } - std::unique_ptr s_; -}; - -struct MoveOnlyNT { - MoveOnlyNT(const char *s) : s_(s) {} - MoveOnlyNT(MoveOnlyNT&&) = default; - MoveOnlyNT(const MoveOnlyNT&) = delete; - MoveOnlyNT& operator=(MoveOnlyNT&&) = default; - MoveOnlyNT& operator=(const MoveOnlyNT&) = delete; - ~MoveOnlyNT() {} // deliberately non-trivial - friend bool operator==(const MoveOnlyNT& a, const MoveOnlyNT& b) { return a.s_ == b.s_; } - friend bool operator!=(const MoveOnlyNT& a, const MoveOnlyNT& b) { return a.s_ != b.s_; } - - std::string s_; -}; - -TEST(inplace_vector, TrivialTraits) -{ - { - using T = sg14::inplace_vector; - static_assert(std::is_trivially_copyable_v); - static_assert(std::is_trivially_copy_constructible_v); - static_assert(std::is_trivially_move_constructible_v); - static_assert(std::is_trivially_copy_assignable_v); - static_assert(std::is_trivially_move_assignable_v); - static_assert(std::is_trivially_destructible_v); - static_assert(std::is_nothrow_copy_constructible_v); - static_assert(std::is_nothrow_move_constructible_v); - static_assert(std::is_nothrow_copy_assignable_v); - static_assert(std::is_nothrow_move_assignable_v); - static_assert(std::is_nothrow_destructible_v); -#if defined(__cpp_lib_trivially_relocatable) - static_assert(std::is_trivially_relocatable_v); -#endif // __cpp_lib_trivially_relocatable - } - { - using T = sg14::inplace_vector, 10>; - static_assert(!std::is_trivially_copyable_v); - static_assert(!std::is_trivially_copy_constructible_v); - static_assert(!std::is_trivially_move_constructible_v); - static_assert(!std::is_trivially_copy_assignable_v); - static_assert(!std::is_trivially_move_assignable_v); - static_assert(!std::is_trivially_destructible_v); - static_assert(!std::is_nothrow_copy_constructible_v); - static_assert(std::is_nothrow_move_constructible_v); - static_assert(!std::is_nothrow_copy_assignable_v); - static_assert(std::is_nothrow_move_assignable_v); - static_assert(std::is_nothrow_destructible_v); -#if defined(__cpp_lib_trivially_relocatable) - static_assert(std::is_trivially_relocatable_v); -#endif // __cpp_lib_trivially_relocatable - } - { - constexpr bool msvc = !std::is_nothrow_move_constructible_v>; - using T = sg14::inplace_vector, 10>; - static_assert(!std::is_trivially_copyable_v); - static_assert(!std::is_trivially_copy_constructible_v); - static_assert(!std::is_trivially_move_constructible_v); - static_assert(!std::is_trivially_copy_assignable_v); - static_assert(!std::is_trivially_move_assignable_v); - static_assert(!std::is_trivially_destructible_v); - static_assert(!std::is_nothrow_copy_constructible_v); - static_assert(std::is_nothrow_move_constructible_v == !msvc); - static_assert(!std::is_nothrow_copy_assignable_v); - static_assert(std::is_nothrow_move_assignable_v == !msvc); - static_assert(std::is_nothrow_destructible_v); -#if defined(__cpp_lib_trivially_relocatable) - static_assert(std::is_trivially_relocatable_v == msvc); -#endif // __cpp_lib_trivially_relocatable - } - { - using T = sg14::inplace_vector; - static_assert(!std::is_trivially_copyable_v); - static_assert(!std::is_trivially_copy_constructible_v); - static_assert(!std::is_trivially_move_constructible_v); - static_assert(!std::is_trivially_copy_assignable_v); - static_assert(!std::is_trivially_move_assignable_v); - static_assert(!std::is_trivially_destructible_v); - static_assert(!std::is_copy_constructible_v); - static_assert(std::is_nothrow_move_constructible_v); - static_assert(!std::is_copy_assignable_v); - static_assert(std::is_nothrow_move_assignable_v); - static_assert(std::is_nothrow_destructible_v); -#if defined(__cpp_lib_trivially_relocatable) - static_assert(std::is_trivially_relocatable_v); -#endif // __cpp_lib_trivially_relocatable - } - { - using T = sg14::inplace_vector; - static_assert(!std::is_trivially_copyable_v); - static_assert(!std::is_trivially_copy_constructible_v); - static_assert(!std::is_trivially_move_constructible_v); - static_assert(!std::is_trivially_copy_assignable_v); - static_assert(!std::is_trivially_move_assignable_v); - static_assert(!std::is_trivially_destructible_v); - static_assert(!std::is_copy_constructible_v); - static_assert(std::is_nothrow_move_constructible_v); - static_assert(!std::is_copy_assignable_v); - static_assert(std::is_nothrow_move_assignable_v); - static_assert(std::is_nothrow_destructible_v); -#if defined(__cpp_lib_trivially_relocatable) - static_assert(!std::is_trivially_relocatable_v); -#endif // __cpp_lib_trivially_relocatable - } - { - using T = sg14::inplace_vector; - static_assert(std::is_trivially_copyable_v); - static_assert(std::is_trivially_copy_constructible_v); - static_assert(std::is_trivially_move_constructible_v); - static_assert(std::is_trivially_copy_assignable_v); - static_assert(std::is_trivially_move_assignable_v); - static_assert(std::is_trivially_destructible_v); - static_assert(std::is_nothrow_copy_constructible_v); - static_assert(std::is_nothrow_move_constructible_v); - static_assert(std::is_nothrow_copy_assignable_v); - static_assert(std::is_nothrow_move_assignable_v); - static_assert(std::is_nothrow_destructible_v); -#if defined(__cpp_lib_trivially_relocatable) - static_assert(std::is_trivially_relocatable_v); -#endif // __cpp_lib_trivially_relocatable - } - { - using T = sg14::inplace_vector, 0>; - static_assert(std::is_trivially_copyable_v); - static_assert(std::is_trivially_copy_constructible_v); - static_assert(std::is_trivially_move_constructible_v); - static_assert(std::is_trivially_copy_assignable_v); - static_assert(std::is_trivially_move_assignable_v); - static_assert(std::is_trivially_destructible_v); - static_assert(std::is_nothrow_copy_constructible_v); - static_assert(std::is_nothrow_move_constructible_v); - static_assert(std::is_nothrow_copy_assignable_v); - static_assert(std::is_nothrow_move_assignable_v); - static_assert(std::is_nothrow_destructible_v); -#if defined(__cpp_lib_trivially_relocatable) - static_assert(std::is_trivially_relocatable_v); -#endif // __cpp_lib_trivially_relocatable - } - { - using T = sg14::inplace_vector, 0>; - static_assert(std::is_trivially_copyable_v); - static_assert(std::is_trivially_copy_constructible_v); - static_assert(std::is_trivially_move_constructible_v); - static_assert(std::is_trivially_copy_assignable_v); - static_assert(std::is_trivially_move_assignable_v); - static_assert(std::is_trivially_destructible_v); - static_assert(std::is_nothrow_copy_constructible_v); - static_assert(std::is_nothrow_move_constructible_v); - static_assert(std::is_nothrow_copy_assignable_v); - static_assert(std::is_nothrow_move_assignable_v); - static_assert(std::is_nothrow_destructible_v); -#if defined(__cpp_lib_trivially_relocatable) - static_assert(std::is_trivially_relocatable_v); -#endif // __cpp_lib_trivially_relocatable - } - { - using T = sg14::inplace_vector, 0>; - static_assert(std::is_trivially_copyable_v); - static_assert(!std::is_trivially_copy_constructible_v); - static_assert(std::is_trivially_move_constructible_v); - static_assert(!std::is_trivially_copy_assignable_v); - static_assert(std::is_trivially_move_assignable_v); - static_assert(std::is_trivially_destructible_v); - static_assert(!std::is_copy_constructible_v); - static_assert(std::is_nothrow_move_constructible_v); - static_assert(!std::is_copy_assignable_v); - static_assert(std::is_nothrow_move_assignable_v); - static_assert(std::is_nothrow_destructible_v); -#if defined(__cpp_lib_trivially_relocatable) - static_assert(std::is_trivially_relocatable_v); -#endif // __cpp_lib_trivially_relocatable - } -} - -TEST(inplace_vector, PartiallyTrivialTraits) -{ -#if __cpp_concepts >= 202002L - constexpr bool ConditionallyTrivial = true; -#else - constexpr bool ConditionallyTrivial = false; -#endif - { - struct S { - int *p_ = nullptr; - S(int *p) : p_(p) {} - S(const S& s) : p_(s.p_) { *p_ += 1; } - S(S&&) = default; - S& operator=(const S&) = default; - S& operator=(S&&) = default; - ~S() = default; - }; - using T = sg14::inplace_vector; - static_assert(!std::is_trivially_copyable_v); - static_assert(!std::is_trivially_copy_constructible_v); - static_assert(!std::is_trivially_copy_assignable_v); - static_assert(std::is_trivially_move_constructible_v == ConditionallyTrivial); - static_assert(std::is_trivially_move_assignable_v == ConditionallyTrivial); - static_assert(std::is_trivially_destructible_v == ConditionallyTrivial); - T v; - int count = 0; - v.push_back(S(&count)); - v.push_back(S(&count)); - count = 0; - T w = v; - EXPECT_EQ(count, 2); // should have copied two S objects - T x = std::move(v); - EXPECT_EQ(count, 2); // moving them is trivial - EXPECT_EQ(v.size(), 2); - v.clear(); w = v; - EXPECT_EQ(count, 2); // destroying them is trivial - v = x; - EXPECT_EQ(count, 4); // should have copy-constructed two S objects - v = x; - EXPECT_EQ(count, 4); // copy-assigning them is trivial - v = std::move(x); - EXPECT_EQ(count, 4); // move-assigning them is trivial - EXPECT_EQ(x.size(), 2); - x.clear(); x = std::move(v); - EXPECT_EQ(count, 4); // move-constructing them is trivial - EXPECT_EQ(v.size(), 2); - v.clear(); x = std::move(v); - EXPECT_EQ(count, 4); // destroying them is trivial - EXPECT_TRUE(v.empty() && w.empty() && x.empty()); - } - { - struct S { - int *p_ = nullptr; - S(int *p) : p_(p) {} - S(const S& s) = default; - S(S&&) = default; - S& operator=(const S&) { *p_ += 1; return *this; } - S& operator=(S&&) = default; - ~S() = default; - }; - using T = sg14::inplace_vector; - static_assert(!std::is_trivially_copyable_v); - static_assert(std::is_trivially_copy_constructible_v == ConditionallyTrivial); - static_assert(!std::is_trivially_copy_assignable_v); - static_assert(std::is_trivially_move_constructible_v == ConditionallyTrivial); - static_assert(std::is_trivially_move_assignable_v == ConditionallyTrivial); - static_assert(std::is_trivially_destructible_v == ConditionallyTrivial); - T v; - int count = 0; - v.push_back(S(&count)); - v.push_back(S(&count)); - count = 0; - T w = v; - EXPECT_EQ(count, 0); // copying them is trivial - T x = std::move(v); - EXPECT_EQ(count, 0); // moving them is trivial - EXPECT_EQ(v.size(), 2); - v.clear(); w = v; - EXPECT_EQ(count, 0); // destroying them is trivial - v = x; - EXPECT_EQ(count, 0); // copying them is trivial - v = x; - EXPECT_EQ(count, 2); // should have copy-assigned two S objects - v = std::move(x); - EXPECT_EQ(count, 2); // move-assigning them is trivial - EXPECT_EQ(x.size(), 2); - x.clear(); x = std::move(v); - EXPECT_EQ(count, 2); // move-constructing them is trivial - EXPECT_EQ(v.size(), 2); - v.clear(); x = std::move(v); - EXPECT_EQ(count, 2); // destroying them is trivial - EXPECT_TRUE(v.empty() && w.empty() && x.empty()); - } - { - struct S { - int *p_ = nullptr; - S(int *p) : p_(p) {} - S(const S& s) = default; - S(S&&) = default; - S& operator=(const S&) = default; - S& operator=(S&&) = default; - ~S() { *p_ += 1; } - }; - using T = sg14::inplace_vector; - static_assert(!std::is_trivially_copyable_v); - // T's non-trivial dtor prevents `is_trivially_copy_constructible`, - // even though T's copy constructor itself is trivial. - static_assert(!std::is_trivially_copy_constructible_v); - static_assert(!std::is_trivially_copy_assignable_v); - static_assert(!std::is_trivially_move_constructible_v); - static_assert(!std::is_trivially_move_assignable_v); - static_assert(!std::is_trivially_destructible_v); - T v; - int count = 0; - v.push_back(S(&count)); - v.push_back(S(&count)); - count = 0; - T w = v; - EXPECT_EQ(count, 0); // copying them is trivial - T x = std::move(v); - EXPECT_EQ(count, 0); // moving them is trivial - EXPECT_EQ(v.size(), 2); - v.clear(); - EXPECT_EQ(count, 2); // should have destroyed two S objects - w = v; - EXPECT_EQ(count, 4); // should have destroyed two S objects - v = x; - EXPECT_EQ(count, 4); // copying them is trivial - v = x; - EXPECT_EQ(count, 4); // copy-assigning them is trivial - v = std::move(x); - EXPECT_EQ(count, 4); // move-assigning them is trivial - EXPECT_EQ(x.size(), 2); - x.clear(); - EXPECT_EQ(count, 6); // should have destroyed two S objects - x = std::move(v); - EXPECT_EQ(count, 6); // move-constructing them is trivial - EXPECT_EQ(v.size(), 2); - v.clear(); - EXPECT_EQ(count, 8); // should have destroyed two S objects - x = std::move(v); - EXPECT_EQ(count, 10); // should have destroyed two S objects - EXPECT_TRUE(v.empty() && w.empty() && x.empty()); - } -} - -TEST(inplace_vector, ZeroSized) -{ - { - sg14::inplace_vector v; - static_assert(std::is_empty_v); - static_assert(std::is_trivial_v); - static_assert(std::is_trivially_copyable_v); - EXPECT_EQ(v.size(), 0u); - EXPECT_EQ(v.max_size(), 0u); - EXPECT_EQ(v.capacity(), 0u); - EXPECT_EQ(v.try_push_back(42), nullptr); - EXPECT_EQ(v.try_emplace_back(), nullptr); - ASSERT_THROW(v.push_back(42), std::bad_alloc); - ASSERT_THROW(v.emplace_back(), std::bad_alloc); - EXPECT_TRUE(v.empty()); - EXPECT_EQ(v.erase(v.begin(), v.begin()), v.begin()); - int a[1] = {1}; - EXPECT_EQ(v.insert(v.begin(), a, a), v.begin()); - } - { - sg14::inplace_vector v; - static_assert(std::is_empty_v); - static_assert(std::is_trivial_v); - static_assert(std::is_trivially_copyable_v); - EXPECT_EQ(v.size(), 0u); - EXPECT_EQ(v.max_size(), 0u); - EXPECT_EQ(v.capacity(), 0u); - EXPECT_EQ(v.try_push_back("abc"), nullptr); - EXPECT_EQ(v.try_emplace_back("abc", 3), nullptr); - ASSERT_THROW(v.push_back("abc"), std::bad_alloc); - ASSERT_THROW(v.emplace_back("abc", 3), std::bad_alloc); - EXPECT_TRUE(v.empty()); - EXPECT_EQ(v.erase(v.begin(), v.begin()), v.begin()); - std::string a[1] = {"abc"}; - EXPECT_EQ(v.insert(v.begin(), a, a), v.begin()); - } - { - sg14::inplace_vector, 0> v; - static_assert(std::is_empty_v); - static_assert(std::is_trivial_v); - static_assert(std::is_trivially_copyable_v); - EXPECT_EQ(v.size(), 0u); - EXPECT_EQ(v.max_size(), 0u); - EXPECT_EQ(v.capacity(), 0u); - EXPECT_EQ(v.try_push_back({1,2,3}), nullptr); - EXPECT_EQ(v.try_emplace_back(), nullptr); - ASSERT_THROW(v.push_back({1,2,3}), std::bad_alloc); - ASSERT_THROW(v.emplace_back(), std::bad_alloc); - EXPECT_TRUE(v.empty()); - EXPECT_EQ(v.erase(v.begin(), v.begin()), v.begin()); - std::list a[1] = {{1,2,3}}; - EXPECT_EQ(v.insert(v.begin(), a, a), v.begin()); - } - { - sg14::inplace_vector, 0> v; - static_assert(std::is_empty_v); - static_assert(std::is_trivial_v); - static_assert(std::is_trivially_copyable_v); - EXPECT_EQ(v.size(), 0u); - EXPECT_EQ(v.max_size(), 0u); - EXPECT_EQ(v.capacity(), 0u); - EXPECT_EQ(v.try_push_back(nullptr), nullptr); - EXPECT_EQ(v.try_emplace_back(), nullptr); - ASSERT_THROW(v.push_back(nullptr), std::bad_alloc); - ASSERT_THROW(v.emplace_back(), std::bad_alloc); - EXPECT_TRUE(v.empty()); - EXPECT_EQ(v.erase(v.begin(), v.begin()), v.begin()); - std::unique_ptr a[1] = {nullptr}; - EXPECT_EQ(v.insert(v.begin(), std::make_move_iterator(a), std::make_move_iterator(a)), v.begin()); - } -} - -TEST(inplace_vector, Iterators) -{ - { - using V = sg14::inplace_vector; - using I = typename V::iterator; - using CI = typename V::const_iterator; - using RI = typename V::reverse_iterator; - using CRI = typename V::const_reverse_iterator; - static_assert(std::is_same_v); - static_assert(std::is_same_v); - static_assert(std::is_same_v>); - static_assert(std::is_same_v>); - V v(2); - const V cv(2); - static_assert(std::is_same_v); - static_assert(std::is_same_v); - static_assert(std::is_same_v); - static_assert(std::is_same_v); - static_assert(std::is_same_v); - static_assert(std::is_same_v); - static_assert(std::is_same_v); - static_assert(std::is_same_v); - static_assert(std::is_same_v); - static_assert(std::is_same_v); - static_assert(std::is_same_v); - static_assert(std::is_same_v); - EXPECT_EQ(v.end(), v.begin() + 2); - EXPECT_EQ(v.cend(), v.cbegin() + 2); - EXPECT_EQ(v.rend(), v.rbegin() + 2); - EXPECT_EQ(v.crend(), v.crbegin() + 2); - EXPECT_EQ(cv.end(), cv.begin() + 2); - EXPECT_EQ(cv.cend(), cv.cbegin() + 2); - EXPECT_EQ(cv.rend(), cv.rbegin() + 2); - EXPECT_EQ(cv.crend(), cv.crbegin() + 2); - for (int& i : v) EXPECT_EQ(i, 0); - for (const int& i : cv) EXPECT_EQ(i, 0); - } - { - using V = sg14::inplace_vector; - using I = typename V::iterator; - using CI = typename V::const_iterator; - using RI = typename V::reverse_iterator; - using CRI = typename V::const_reverse_iterator; - static_assert(std::is_same_v); - static_assert(std::is_same_v); - static_assert(std::is_same_v>); - static_assert(std::is_same_v>); - V v(0); - const V cv(0); - static_assert(std::is_same_v); - static_assert(std::is_same_v); - static_assert(std::is_same_v); - static_assert(std::is_same_v); - static_assert(std::is_same_v); - static_assert(std::is_same_v); - static_assert(std::is_same_v); - static_assert(std::is_same_v); - static_assert(std::is_same_v); - static_assert(std::is_same_v); - static_assert(std::is_same_v); - static_assert(std::is_same_v); - EXPECT_EQ(v.end(), v.begin() + 0); - EXPECT_EQ(v.cend(), v.cbegin() + 0); - EXPECT_EQ(v.rend(), v.rbegin() + 0); - EXPECT_EQ(v.crend(), v.crbegin() + 0); - EXPECT_EQ(cv.end(), cv.begin() + 0); - EXPECT_EQ(cv.cend(), cv.cbegin() + 0); - EXPECT_EQ(cv.rend(), cv.rbegin() + 0); - EXPECT_EQ(cv.crend(), cv.crbegin() + 0); - for (int& i : v) EXPECT_EQ(i, 0); - for (const int& i : cv) EXPECT_EQ(i, 0); - } -} - -TEST(inplace_vector, Constructors) -{ - { - using V = sg14::inplace_vector; - long a[] = {1,2,3}; - V v1; - EXPECT_TRUE(v1.empty()); - V v2 = {}; - EXPECT_TRUE(v2.empty()); - V v3 = {1,2,3}; - EXPECT_TRUE(std::equal(v3.begin(), v3.end(), a, a+3)); - auto v4 = V(a, a+3); - EXPECT_TRUE(std::equal(v4.begin(), v4.end(), a, a+3)); - auto iss = std::istringstream("1 2 3"); - auto v5 = V(std::istream_iterator(iss), std::istream_iterator()); - EXPECT_TRUE(std::equal(v5.begin(), v5.end(), a, a+3)); - auto v6 = V(3); - EXPECT_EQ(v6, V(3, 0)); - auto v7 = V(3, 42); - EXPECT_EQ(v7, (V{42, 42, 42})); - } - { - using V = sg14::inplace_vector; - const char *a[] = {"1", "2", "3"}; - V v1; - EXPECT_TRUE(v1.empty()); - V v2 = {}; - EXPECT_TRUE(v2.empty()); - V v3 = {"1", "2", "3"}; - EXPECT_TRUE(std::equal(v3.begin(), v3.end(), a, a+3)); - auto v4 = V(a, a+3); - EXPECT_TRUE(std::equal(v4.begin(), v4.end(), a, a+3)); - auto iss = std::istringstream("1 2 3"); - auto v5 = V(std::istream_iterator(iss), std::istream_iterator()); - EXPECT_TRUE(std::equal(v5.begin(), v5.end(), a, a+3)); - auto v6 = V(3); - EXPECT_EQ(v6, V(3, "")); - auto v7 = V(3, "42"); - EXPECT_EQ(v7, (V{"42", "42", "42"})); - } -#if __cpp_lib_ranges >= 201911L && __cpp_lib_ranges_to_container >= 202202L - { - auto iss = std::istringstream("1 2 3 4"); - auto rg = std::views::istream(iss); - auto v1 = sg14::inplace_vector(std::from_range, rg); - EXPECT_EQ(v1, (sg14::inplace_vector{1,2,3,4})); - auto v2 = v1 | std::ranges::to>(); - EXPECT_EQ(v2, (sg14::inplace_vector{1,2,3,4})); - } -#endif // __cpp_lib_ranges >= 201911L && __cpp_lib_ranges_to_container >= 202202L -} - -TEST(inplace_vector, ConstructorsThrow) -{ - { - using V = sg14::inplace_vector; - long a[] = {1,2,3,4,5}; - ASSERT_NO_THROW(V(a, a+3)); - ASSERT_THROW(V(a, a+4), std::bad_alloc); - ASSERT_NO_THROW(V(3)); - ASSERT_THROW(V(4), std::bad_alloc); - ASSERT_NO_THROW(V(3, 42)); - ASSERT_THROW(V(4, 42), std::bad_alloc); - ASSERT_NO_THROW(V({1,2,3})); - ASSERT_THROW(V({1,2,3,4}), std::bad_alloc); -#if __cpp_lib_ranges >= 201911L && __cpp_lib_ranges_to_container >= 202202L - auto iss = std::istringstream("1 2 3 4"); - auto rg = std::views::istream(iss); - ASSERT_THROW(V(std::from_range, rg), std::bad_alloc); -#endif // __cpp_lib_ranges >= 201911L && __cpp_lib_ranges_to_container >= 202202L - } -} - -TEST(inplace_vector, IterPairNonAssignable) -{ - struct NA { - std::string s_; - NA(const char *s) : s_(s) {} - NA(NA&&) = delete; - NA& operator=(NA&&) = delete; - bool operator==(const char *s) const { return s_ == s; } - }; - const char *a[] = {"1", "2", "3"}; - { - auto v = sg14::inplace_vector(a, a+3); - EXPECT_EQ(v, Seq("1", "2", "3")); - } -#if __cpp_lib_ranges >= 201911L && __cpp_lib_ranges_to_container >= 202202L - { - auto v = sg14::inplace_vector(std::from_range, a); - EXPECT_EQ(v, Seq("1", "2", "3")); - } -#endif // __cpp_lib_ranges >= 201911L && __cpp_lib_ranges_to_container >= 202202L -} - -TEST(inplace_vector, Copying) -{ - { - using V = sg14::inplace_vector; - V source = {1,2,3}; - V dest = source; - EXPECT_EQ(dest, (V{1,2,3})); - dest = {4,5}; - EXPECT_EQ(dest, (V{4,5})); - dest = source; - EXPECT_EQ(dest, (V{1,2,3})); - EXPECT_EQ(source, (V{1,2,3})); - } - { - using V = sg14::inplace_vector; - V source = {"1", "2", "3"}; - V dest = source; - EXPECT_EQ(dest, Seq("1", "2", "3")); - dest = {"4", "5"}; - EXPECT_EQ(dest, Seq("4", "5")); - dest = source; - EXPECT_EQ(dest, Seq("1", "2", "3")); - EXPECT_EQ(source, Seq("1", "2", "3")); - } -} - -TEST(inplace_vector, TransfersOfOwnership) -{ - { - // Trivially copyable value_type - using V = sg14::inplace_vector; - V source = {1,2,3}; - static_assert(std::is_trivially_move_constructible_v); - static_assert(std::is_trivially_move_assignable_v); - V dest = std::move(source); - EXPECT_EQ(source.size(), 3u); - EXPECT_EQ(source, (V{1,2,3})); - EXPECT_EQ(dest.size(), 3u); - EXPECT_EQ(dest, (V{1,2,3})); - dest = {1,2}; - dest = std::move(source); - EXPECT_EQ(source.size(), 3u); - EXPECT_EQ(source, (V{1,2,3})); - EXPECT_EQ(dest.size(), 3u); - EXPECT_EQ(dest, (V{1,2,3})); - } - { - // Trivially relocatable (but not trivially copyable) value_type - using V = sg14::inplace_vector, 10>; - V source; - source.push_back(std::make_unique(1)); - source.push_back(std::make_unique(2)); - source.push_back(std::make_unique(3)); - static_assert(!std::is_trivially_move_constructible_v); - static_assert(!std::is_trivially_move_assignable_v); -#if defined(__cpp_lib_trivially_relocatable) - static_assert(std::is_trivially_relocatable_v); - V dest = std::move(source); // move-construct - EXPECT_EQ(source.size(), 0u); - EXPECT_EQ(dest.size(), 3u); - EXPECT_EQ(*dest.front(), 1); - EXPECT_EQ(*dest.back(), 3); - source = V(2); - source[0] = std::make_unique(42); - source = std::move(dest); // move-assign 2 := 3 - EXPECT_EQ(source.size(), 3u); - EXPECT_EQ(dest.size(), 2u); - EXPECT_EQ(dest.front(), nullptr); - EXPECT_EQ(*source.front(), 1); - EXPECT_EQ(*source.back(), 3); - dest = V(9); - dest[0] = std::make_unique(42); - dest[4] = std::make_unique(42); - dest = std::move(source); // move-assign 9 := 3 - EXPECT_EQ(source.size(), 3u); - EXPECT_EQ(source.front(), nullptr); - EXPECT_EQ(dest.size(), 3u); - EXPECT_EQ(*dest.front(), 1); - EXPECT_EQ(*dest.back(), 3); -#else - V dest = std::move(source); // move-construct - EXPECT_EQ(source.size(), 3u); - EXPECT_EQ(source.front(), nullptr); - EXPECT_EQ(source.back(), nullptr); - EXPECT_EQ(dest.size(), 3u); - EXPECT_EQ(*dest.front(), 1); - EXPECT_EQ(*dest.back(), 3); - source = V(2); - source[0] = std::make_unique(42); - source = std::move(dest); // move-assign 2 := 3 - EXPECT_EQ(source.size(), 3u); - EXPECT_EQ(dest.size(), 3u); - EXPECT_EQ(*source.front(), 1); - EXPECT_EQ(*source.back(), 3); - dest = V(9); - dest[0] = std::make_unique(42); - dest[4] = std::make_unique(42); - dest = std::move(source); // move-assign 9 := 3 - EXPECT_EQ(source.size(), 3u); - EXPECT_EQ(dest.size(), 3u); - EXPECT_EQ(*dest.front(), 1); - EXPECT_EQ(*dest.back(), 3); -#endif - } - { - // Non-trivially relocatable value_type - using V = sg14::inplace_vector; - V source; - source.push_back("abc"); - source.push_back("def"); - source.push_back("ghi"); -#if defined(__cpp_lib_trivially_relocatable) - static_assert(!std::is_trivially_relocatable_v); -#endif - V dest = std::move(source); - EXPECT_EQ(source.size(), 3u); - EXPECT_EQ(dest.size(), 3u); - EXPECT_EQ(dest.front(), "abc"); - EXPECT_EQ(dest.back(), "ghi"); - } -} - -TEST(inplace_vector, Clear) -{ - sg14::inplace_vector v = {1,2,3}; - v.clear(); - EXPECT_EQ(v.size(), 0u); - EXPECT_TRUE(v.empty()); -} - -TEST(inplace_vector, Noexceptness) -{ - sg14::inplace_vector v; - static_assert(noexcept(v.size())); - static_assert(noexcept(v.empty())); - static_assert(noexcept(v.data())); - static_assert(noexcept(v.begin())); - static_assert(noexcept(v.end())); - static_assert(noexcept(v.cbegin())); - static_assert(noexcept(v.cend())); - static_assert(noexcept(v.rbegin())); - static_assert(noexcept(v.rend())); - static_assert(noexcept(v.crbegin())); - static_assert(noexcept(v.crend())); - static_assert(noexcept(v.clear())); - std::string lvalue = "abc"; - std::initializer_list il = {"abc", "def"}; - static_assert(!noexcept(v.insert(v.begin(), lvalue))); - static_assert(!noexcept(v.insert(v.begin(), il))); - static_assert(!noexcept(v.insert(v.begin(), il.begin(), il.end()))); - static_assert(!noexcept(v.emplace(v.begin(), "abc", 3))); - static_assert(!noexcept(v.emplace_back("abc", 3))); - static_assert(!noexcept(v.push_back(lvalue))); - static_assert(!noexcept(v.try_emplace_back("abc", 3))); - static_assert(!noexcept(v.try_push_back(lvalue))); - static_assert(!noexcept(v.unchecked_emplace_back("abc", 3))); - static_assert(!noexcept(v.unchecked_push_back(lvalue))); -#if __cpp_lib_ranges >= 201911L && __cpp_lib_ranges_to_container >= 202202L - static_assert(!noexcept(v.assign_range(il))); - static_assert(!noexcept(v.append_range(il))); - static_assert(!noexcept(v.insert_range(v.begin(), il))); -#endif // __cpp_lib_ranges >= 201911L && __cpp_lib_ranges_to_container >= 202202L - - // Lakos rule - static_assert(!noexcept(v.front())); - static_assert(!noexcept(v.back())); - static_assert(!noexcept(v.at(0))); - static_assert(!noexcept(v[0])); - static_assert(!noexcept(v.try_push_back(std::move(lvalue)))); - static_assert(!noexcept(v.unchecked_push_back(std::move(lvalue)))); - static_assert(!noexcept(v.erase(v.begin()))); - static_assert(!noexcept(v.erase(v.begin(), v.end()))); -} - -TEST(inplace_vector, NoexceptnessOfSwap) -{ - using std::swap; - { - std::string lvalue; - sg14::inplace_vector v; - static_assert(noexcept(swap(lvalue, lvalue))); - static_assert(noexcept(v.swap(v))); - static_assert(noexcept(swap(v, v))); - } - { - struct ThrowingSwappable { - explicit ThrowingSwappable() { } - ThrowingSwappable(const ThrowingSwappable&) { } - void operator=(const ThrowingSwappable&) { } - ~ThrowingSwappable() { } - }; - ThrowingSwappable lvalue; - sg14::inplace_vector v; - static_assert(!noexcept(swap(lvalue, lvalue))); - static_assert(!noexcept(v.swap(v))); - static_assert(!noexcept(swap(v, v))); - } -} - -TEST(inplace_vector, InsertSingle) -{ - { - using V = sg14::inplace_vector; - V v = {1, 4, 7}; - int lvalue = 23; - static_assert(std::is_same_v); - static_assert(std::is_same_v); - static_assert(std::is_same_v); - static_assert(std::is_same_v); - auto it = v.insert(v.begin(), 24); - EXPECT_EQ(it, v.begin()); - EXPECT_EQ(v, (V{24, 1, 4, 7})); - it = v.insert(v.begin() + 2, lvalue); - EXPECT_EQ(it, v.begin() + 2); - EXPECT_EQ(v, (V{24, 1, 23, 4, 7})); - ASSERT_THROW(v.insert(v.begin() + 2, 23), std::bad_alloc); - ASSERT_THROW(v.insert(v.end(), lvalue), std::bad_alloc); - it = v.erase(v.begin() + 1); - EXPECT_EQ(it, v.begin() + 1); - EXPECT_EQ(v, (V{24, 23, 4, 7})); - it = v.insert(v.end(), 10); - EXPECT_EQ(it, v.end() - 1); - EXPECT_EQ(v, (V{24, 23, 4, 7, 10})); - EXPECT_EQ(v.size(), 5u); - } - { - using V = sg14::inplace_vector; - V v = {"abc", "def", "ghi"}; - auto it = v.insert(v.begin(), "xyz"); - EXPECT_EQ(it, v.begin()); - EXPECT_EQ(v, (V{"xyz", "abc", "def", "ghi"})); - std::string lvalue = "wxy"; - it = v.insert(v.begin() + 2, lvalue); - EXPECT_EQ(it, v.begin() + 2); - EXPECT_EQ(v, (V{"xyz", "abc", "wxy", "def", "ghi"})); - ASSERT_THROW(v.insert(v.begin() + 2, "wxy"), std::bad_alloc); - ASSERT_THROW(v.insert(v.end(), lvalue), std::bad_alloc); - it = v.erase(v.begin() + 1); - EXPECT_EQ(it, v.begin() + 1); - EXPECT_EQ(v, (V{"xyz", "wxy", "def", "ghi"})); - it = v.insert(v.end(), "jkl"); - EXPECT_EQ(it, v.end() - 1); - EXPECT_EQ(v, (V{"xyz", "wxy", "def", "ghi", "jkl"})); - EXPECT_EQ(v.size(), 5u); - } - { - using V = sg14::inplace_vector, 5>; - V v = {{1,2,3}, {4,5,6}, {7,8,9}}; - auto it = v.insert(v.begin(), {24,25,26}); - EXPECT_EQ(it, v.begin()); - EXPECT_EQ(v, (V{{24,25,26}, {1,2,3}, {4,5,6}, {7,8,9}})); - std::list lvalue = {23,24,25}; - it = v.insert(v.begin() + 2, lvalue); - EXPECT_EQ(it, v.begin() + 2); - EXPECT_EQ(v, (V{{24,25,26}, {1,2,3}, {23,24,25}, {4,5,6}, {7,8,9}})); - ASSERT_THROW(v.insert(v.begin() + 2, {23,24,25}), std::bad_alloc); - ASSERT_THROW(v.insert(v.end(), lvalue), std::bad_alloc); - it = v.erase(v.begin() + 1); - EXPECT_EQ(it, v.begin() + 1); - EXPECT_EQ(v, (V{{24,25,26}, {23,24,25}, {4,5,6}, {7,8,9}})); - it = v.insert(v.end(), {10,11,12}); - EXPECT_EQ(it, v.end() - 1); - EXPECT_EQ(v, (V{{24,25,26}, {23,24,25}, {4,5,6}, {7,8,9}, {10,11,12}})); - EXPECT_EQ(v.size(), 5u); - } - { - using V = sg14::inplace_vector; - MoveOnly a[3] = {"abc", "def", "ghi"}; - V v = V(std::make_move_iterator(a), std::make_move_iterator(a+3)); - auto it = v.insert(v.begin(), "xyz"); - EXPECT_EQ(it, v.begin()); - EXPECT_EQ(*it, "xyz"); - MoveOnly lvalue = "wxy"; - it = v.insert(v.begin() + 2, std::move(lvalue)); - EXPECT_EQ(it, v.begin() + 2); - EXPECT_EQ(v, Seq("xyz", "abc", "wxy", "def", "ghi")); - ASSERT_THROW(v.insert(v.begin() + 2, "wxy"), std::bad_alloc); - it = v.erase(v.begin() + 1); - EXPECT_EQ(it, v.begin() + 1); - EXPECT_EQ(*it, "wxy"); - it = v.insert(v.end(), "jkl"); - EXPECT_EQ(it, v.end() - 1); - EXPECT_EQ(v, Seq("xyz", "wxy", "def", "ghi", "jkl")); - EXPECT_EQ(v.size(), 5u); - } -} - -TEST(inplace_vector, InsertMulti) -{ - { - using V = sg14::inplace_vector; - V v = {1, 2, 3}; - int a[2] = {4, 5}; - static_assert(std::is_same_v); - static_assert(std::is_same_v); - static_assert(std::is_same_v); - static_assert(std::is_same_v); - auto it = v.insert(v.begin(), a, a+2); - EXPECT_EQ(it, v.begin()); - EXPECT_EQ(v, (V{4, 5, 1, 2, 3})); - it = v.insert(v.begin(), a, a); - EXPECT_EQ(it, v.begin()); - EXPECT_EQ(v, (V{4, 5, 1, 2, 3})); - ASSERT_THROW(v.insert(v.begin(), a, a+2), std::bad_alloc); - v.clear(); - it = v.insert(v.begin(), a, a+2); - EXPECT_EQ(it, v.begin()); - it = v.insert(v.end(), {1, 2}); // insert(initializer_list) - EXPECT_EQ(it, v.begin() + 2); - EXPECT_EQ(v, (V{4, 5, 1, 2})); - ASSERT_THROW(v.insert(v.begin() + 2, a, a+2), std::bad_alloc); - EXPECT_LE(v.size(), 5u); - } - { - using V = sg14::inplace_vector; - V v = {"1", "2", "3"}; - const char *a[2] = {"4", "5"}; - auto it = v.insert(v.begin(), a, a+2); - EXPECT_EQ(it, v.begin()); - EXPECT_EQ(v, (V{"4", "5", "1", "2", "3"})); - it = v.insert(v.begin(), a, a); - EXPECT_EQ(it, v.begin()); - EXPECT_EQ(v, (V{"4", "5", "1", "2", "3"})); - ASSERT_THROW(v.insert(v.begin(), a, a+2), std::bad_alloc); - v.clear(); - it = v.insert(v.begin(), a, a+2); - EXPECT_EQ(it, v.begin()); - it = v.insert(v.end(), {"1", "2"}); // insert(initializer_list) - EXPECT_EQ(it, v.begin() + 2); - EXPECT_EQ(v, (V{"4", "5", "1", "2"})); - ASSERT_THROW(v.insert(v.begin() + 2, a, a+2), std::bad_alloc); - EXPECT_LE(v.size(), 5u); - } - { - using V = sg14::inplace_vector; - V v; - v.emplace_back("1"); - v.emplace_back("2"); - v.emplace_back("3"); - const char *a[2] = {"4", "5"}; - auto it = v.insert(v.begin(), a, a+2); - EXPECT_EQ(it, v.begin()); - EXPECT_EQ(v, Seq("4", "5", "1", "2", "3")); - it = v.insert(v.begin(), a, a); - EXPECT_EQ(it, v.begin()); - EXPECT_EQ(v, Seq("4", "5", "1", "2", "3")); - ASSERT_THROW(v.insert(v.begin(), a, a+2), std::bad_alloc); - v.clear(); - it = v.insert(v.begin(), a, a+2); - EXPECT_EQ(it, v.begin()); - MoveOnly il[2] = {"1", "2"}; - it = v.insert(v.end(), std::make_move_iterator(il), std::make_move_iterator(il + 2)); - EXPECT_EQ(it, v.begin() + 2); - EXPECT_EQ(v, Seq("4", "5", "1", "2")); - ASSERT_THROW(v.insert(v.begin() + 2, a, a+2), std::bad_alloc); - EXPECT_LE(v.size(), 5u); - } - { - using V = sg14::inplace_vector; - V v; - v.emplace_back("1"); - v.emplace_back("2"); - v.emplace_back("3"); - const char *a[2] = {"4", "5"}; - auto it = v.insert(v.begin(), a, a+2); - EXPECT_EQ(it, v.begin()); - EXPECT_EQ(v, Seq("4", "5", "1", "2", "3")); - it = v.insert(v.begin(), a, a); - EXPECT_EQ(it, v.begin()); - EXPECT_EQ(v, Seq("4", "5", "1", "2", "3")); - ASSERT_THROW(v.insert(v.begin(), a, a+2), std::bad_alloc); - v.clear(); - it = v.insert(v.begin(), a, a+2); - EXPECT_EQ(it, v.begin()); - MoveOnlyNT il[2] = {"1", "2"}; - it = v.insert(v.end(), std::make_move_iterator(il), std::make_move_iterator(il + 2)); - EXPECT_EQ(it, v.begin() + 2); - EXPECT_EQ(v, Seq("4", "5", "1", "2")); - ASSERT_THROW(v.insert(v.begin() + 2, a, a+2), std::bad_alloc); - EXPECT_LE(v.size(), 5u); - } -} - -TEST(inplace_vector, Assign) -{ - { - using V = sg14::inplace_vector; - V v; - int a[] = {1, 2, 3, 4, 5, 6}; - v.assign(a, a+2); - static_assert(std::is_same_v); - EXPECT_EQ(v, (V{1, 2})); - v.assign(a+2, a+6); - EXPECT_EQ(v, (V{3, 4, 5, 6})); - v.assign(a, a+2); - EXPECT_EQ(v, (V{1, 2})); - ASSERT_THROW(v.assign(a, a+6), std::bad_alloc); - EXPECT_LE(v.size(), 5u); - } - { - using V = sg14::inplace_vector; - V v; - const char *a[] = {"1", "2", "3", "4", "5", "6"}; - v.assign(a, a+2); - EXPECT_EQ(v, Seq("1", "2")); - v.assign(a+2, a+6); - EXPECT_EQ(v, Seq("3", "4", "5", "6")); - v.assign(a, a+2); - EXPECT_EQ(v, Seq("1", "2")); - ASSERT_THROW(v.assign(a, a+6), std::bad_alloc); - EXPECT_LE(v.size(), 5u); - } - { - using V = sg14::inplace_vector; - V v; - auto iss = std::istringstream("1 2"); - v.assign(std::istream_iterator(iss), {}); - EXPECT_EQ(v, (V{1, 2})); - iss = std::istringstream("4 5 6 7"); - v.assign(std::istream_iterator(iss), {}); - EXPECT_EQ(v, (V{4, 5, 6, 7})); - iss = std::istringstream("1 2"); - v.assign(std::istream_iterator(iss), {}); - EXPECT_EQ(v, (V{1, 2})); - iss = std::istringstream("1 2 3 4 5 6"); - ASSERT_THROW(v.assign(std::istream_iterator(iss), {}), std::bad_alloc); - EXPECT_LE(v.size(), 5u); - } - { - using V = sg14::inplace_vector; - const char *a[] = {"1", "2", "3", "4", "5", "6"}; - V v; - v.assign(a, a+2); - EXPECT_EQ(v, Seq("1", "2")); - v.assign(a+2, a+6); - EXPECT_EQ(v, Seq("3", "4", "5", "6")); - v.assign(a, a+2); - EXPECT_EQ(v, Seq("1", "2")); - ASSERT_THROW(v.assign(a, a+6), std::bad_alloc); - EXPECT_LE(v.size(), 5u); - } -} - -TEST(inplace_vector, AssignRange) -{ -#if __cpp_lib_ranges >= 201911L && __cpp_lib_ranges_to_container >= 202202L - { - using V = sg14::inplace_vector; - V v; - v.assign_range(std::vector{1, 2}); - static_assert(std::is_same_v); - EXPECT_EQ(v, (V{1, 2})); - v.assign_range(std::vector{4, 5, 6, 7}); - EXPECT_EQ(v, (V{4, 5, 6, 7})); - v.assign_range(std::vector{1, 2}); - EXPECT_EQ(v, (V{1, 2})); - ASSERT_THROW(v.assign_range(std::vector(6)), std::bad_alloc); - EXPECT_LE(v.size(), 5u); - } - { - using V = sg14::inplace_vector; - V v; - v.assign_range(std::vector{"1", "2"}); - EXPECT_EQ(v, Seq("1", "2")); - v.assign_range(std::vector{"4", "5", "6", "7"}); - EXPECT_EQ(v, Seq("4", "5", "6", "7")); - v.assign_range(std::vector{"1", "2"}); - EXPECT_EQ(v, Seq("1", "2")); - ASSERT_THROW(v.assign_range(std::vector(6)), std::bad_alloc); - EXPECT_LE(v.size(), 5u); - } - { - using V = sg14::inplace_vector; - V v; - auto iss = std::istringstream("1 2"); - v.assign_range(std::views::istream(iss)); - EXPECT_EQ(v, (V{1, 2})); - iss = std::istringstream("4 5 6 7"); - v.assign_range(std::views::istream(iss)); - EXPECT_EQ(v, (V{4, 5, 6, 7})); - iss = std::istringstream("1 2"); - v.assign_range(std::views::istream(iss)); - EXPECT_EQ(v, (V{1, 2})); - iss = std::istringstream("1 2 3 4 5 6"); - ASSERT_THROW(v.assign_range(std::views::istream(iss)), std::bad_alloc); - EXPECT_LE(v.size(), 5u); - } - { - using V = sg14::inplace_vector; - auto iss = std::istringstream("4 5 6 7"); - V v = {"1", "2"}; - v.assign_range(std::views::istream(iss) | std::views::take(3)); - EXPECT_EQ(v, Seq("4", "5", "6")); - iss = std::istringstream("6 7"); - v.assign_range(std::views::istream(iss) | std::views::take(2)); - EXPECT_EQ(v, Seq("6", "7")); - iss = std::istringstream("6 7 8 9 10 11"); - ASSERT_THROW(v.assign_range(std::views::istream(iss) | std::views::take(6)), std::bad_alloc); - EXPECT_LE(v.size(), 5u); - } - { - using V = sg14::inplace_vector; - const char *a[] = {"1", "2", "3", "4", "5", "6"}; - V v; - v.assign_range(a | std::views::take(2)); - EXPECT_EQ(v, Seq("1", "2")); - v.assign_range(a | std::views::drop(2) | std::views::take(4)); - EXPECT_EQ(v, Seq("3", "4", "5", "6")); - v.assign_range(a | std::views::take(2)); - EXPECT_EQ(v, Seq("1", "2")); - ASSERT_THROW(v.assign_range(a), std::bad_alloc); - EXPECT_LE(v.size(), 5u); - } -#if __cpp_lib_ranges_as_rvalue >= 202207L - { - using V = sg14::inplace_vector; - MoveOnly a[] = {"1", "2", "3", "4", "5", "6", "7", "8"}; - V v; - v.assign_range(a | std::views::as_rvalue | std::views::take(2)); - EXPECT_EQ(v, Seq("1", "2")); - v.assign_range(a | std::views::as_rvalue | std::views::drop(2) | std::views::take(4)); - EXPECT_EQ(v, Seq("3", "4", "5", "6")); - v.assign_range(a | std::views::as_rvalue | std::views::drop(6) | std::views::take(2)); - EXPECT_EQ(v, Seq("7", "8")); - ASSERT_THROW(v.assign_range(a | std::views::as_rvalue), std::bad_alloc); - EXPECT_LE(v.size(), 5u); - } -#endif // __cpp_lib_ranges_as_rvalue >= 202207L -#endif // __cpp_lib_ranges >= 201911L && __cpp_lib_ranges_to_container >= 202202L -} - -TEST(inplace_vector, InsertRange) -{ -#if __cpp_lib_ranges >= 201911L && __cpp_lib_ranges_to_container >= 202202L - { - using V = sg14::inplace_vector; - V v = {1, 2}; - auto it = v.insert_range(v.begin() + 1, std::vector{4, 5}); - static_assert(std::is_same_v); - EXPECT_EQ(it, v.begin() + 1); - EXPECT_EQ(v, (V{1, 4, 5, 2})); - ASSERT_THROW(v.insert_range(v.begin() + 1, std::vector{4, 5}), std::bad_alloc); - EXPECT_EQ(v, (V{1, 4, 5, 2})); - } - { - using V = sg14::inplace_vector; - auto iss = std::istringstream("4 5 6 7"); - V v = {1, 2, 3}; - auto it = v.insert_range(v.begin() + 1, std::views::istream(iss) | std::views::take(2)); - static_assert(std::is_same_v); - EXPECT_EQ(it, v.begin() + 1); - EXPECT_EQ(v, (V{1, 4, 5, 2, 3})); - iss = std::istringstream("6 7"); - ASSERT_THROW(v.insert_range(v.begin() + 1, std::views::istream(iss) | std::views::take(2)), std::bad_alloc); - EXPECT_EQ(v, (V{1, 4, 5, 2, 3})); - } - { - using V = sg14::inplace_vector; - auto iss = std::istringstream("4 5 6 7"); - V v = {"1", "2"}; - auto it = v.insert_range(v.begin() + 1, std::views::istream(iss) | std::views::take(2)); - EXPECT_EQ(it, v.begin() + 1); - EXPECT_EQ(v, (V{"1", "4", "5", "2"})); - iss = std::istringstream("6 7"); - ASSERT_THROW(v.insert_range(v.begin() + 1, std::views::istream(iss) | std::views::take(2)), std::bad_alloc); - EXPECT_EQ(v, (V{"1", "4", "5", "2"})); - } -#if __cpp_lib_ranges_as_rvalue >= 202207L - { - using V = sg14::inplace_vector; - MoveOnly a[2] = {"abc", "def"}; - V v; - v.emplace_back("wxy"); - v.emplace_back("xyz"); - auto it = v.insert_range(v.begin() + 1, a | std::views::as_rvalue); - EXPECT_EQ(it, v.begin() + 1); - EXPECT_EQ(v, Seq("wxy", "abc", "def", "xyz")); - ASSERT_THROW(v.insert_range(v.begin() + 1, a | std::views::as_rvalue), std::bad_alloc); - EXPECT_EQ(v, Seq("wxy", "abc", "def", "xyz")); - } -#endif // __cpp_lib_ranges_as_rvalue >= 202207L -#endif // __cpp_lib_ranges >= 201911L && __cpp_lib_ranges_to_container >= 202202L -} - -TEST(inplace_vector, AppendRange) -{ -#if __cpp_lib_ranges >= 201911L && __cpp_lib_ranges_to_container >= 202202L - { - using V = sg14::inplace_vector; - V v = {1, 2}; - v.append_range(std::vector{4, 5}); - static_assert(std::is_same_v{4, 5})), void>); - EXPECT_EQ(v, (V{1, 2, 4, 5})); - ASSERT_THROW(v.append_range(std::vector{4, 5}), std::bad_alloc); - EXPECT_LE(v.size(), 5u); - } - { - using V = sg14::inplace_vector; - auto iss = std::istringstream("4 5 6 7"); - V v = {1, 2, 3}; - v.append_range(std::views::istream(iss) | std::views::take(2)); - EXPECT_EQ(v, (V{1, 2, 3, 4, 5})); - iss = std::istringstream("6 7"); - ASSERT_THROW(v.append_range(std::views::istream(iss) | std::views::take(2)), std::bad_alloc); - EXPECT_LE(v.size(), 5u); - } - { - using V = sg14::inplace_vector; - auto iss = std::istringstream("4 5 6 7"); - V v = {"1", "2"}; - v.append_range(std::views::istream(iss) | std::views::take(2)); - EXPECT_EQ(v, Seq("1", "2", "4", "5")); - iss = std::istringstream("6 7"); - ASSERT_THROW(v.append_range(std::views::istream(iss) | std::views::take(2)), std::bad_alloc); - EXPECT_LE(v.size(), 5u); - } -#if __cpp_lib_ranges_as_rvalue >= 202207L - { - using V = sg14::inplace_vector; - MoveOnly a[2] = {"abc", "def"}; - V v; - v.emplace_back("wxy"); - v.emplace_back("xyz"); - v.append_range(a | std::views::as_rvalue); - EXPECT_EQ(v, Seq("wxy", "xyz", "abc", "def")); - ASSERT_THROW(v.append_range(a | std::views::as_rvalue), std::bad_alloc); - EXPECT_LE(v.size(), 5u); - } -#endif // __cpp_lib_ranges_as_rvalue >= 202207L -#endif // __cpp_lib_ranges >= 201911L && __cpp_lib_ranges_to_container >= 202202L -} - -TEST(inplace_vector, PushBack) -{ - { - using V = sg14::inplace_vector; - V v = {1, 4}; - int lvalue = 23; - static_assert(std::is_same_v); - static_assert(std::is_same_v); - static_assert(std::is_same_v); - EXPECT_EQ(&v.push_back(24), v.data() + 2); - EXPECT_EQ(&v.push_back(lvalue), v.data() + 3); - EXPECT_EQ(&v.emplace_back(), v.data() + 4); - EXPECT_EQ(v, (V{1, 4, 24, 23, 0})); - EXPECT_THROW(v.push_back(24), std::bad_alloc); - EXPECT_THROW(v.push_back(lvalue), std::bad_alloc); - EXPECT_THROW(v.emplace_back(), std::bad_alloc); - EXPECT_EQ(v, (V{1, 4, 24, 23, 0})); // failed push_back has the strong guarantee - - v = {1, 4}; - static_assert(std::is_same_v); - static_assert(std::is_same_v); - static_assert(std::is_same_v); - EXPECT_EQ(v.try_push_back(24), v.data() + 2); - EXPECT_EQ(v.try_push_back(lvalue), v.data() + 3); - EXPECT_EQ(v.try_emplace_back(), v.data() + 4); - EXPECT_EQ(v, (V{1, 4, 24, 23, 0})); - EXPECT_EQ(v.try_push_back(24), nullptr); - EXPECT_EQ(v.try_push_back(lvalue), nullptr); - EXPECT_EQ(v.try_emplace_back(), nullptr); - EXPECT_EQ(v, (V{1, 4, 24, 23, 0})); // failed try_push_back has the strong guarantee - } - { - using V = sg14::inplace_vector; - V v = {"1", "4"}; - std::string lvalue = "23"; - static_assert(std::is_same_v); - static_assert(std::is_same_v); - static_assert(std::is_same_v); - EXPECT_EQ(&v.push_back("24"), v.data() + 2); - EXPECT_EQ(&v.push_back(lvalue), v.data() + 3); - EXPECT_EQ(&v.emplace_back(), v.data() + 4); - EXPECT_EQ(v, (V{"1", "4", "24", "23", ""})); - EXPECT_THROW(v.push_back("24"), std::bad_alloc); - EXPECT_THROW(v.push_back(lvalue), std::bad_alloc); - EXPECT_THROW(v.emplace_back(), std::bad_alloc); - EXPECT_EQ(v, (V{"1", "4", "24", "23", ""})); // failed push_back has the strong guarantee - - v = {"1", "4"}; - static_assert(std::is_same_v); - static_assert(std::is_same_v); - static_assert(std::is_same_v); - EXPECT_EQ(v.try_push_back("24"), v.data() + 2); - EXPECT_EQ(v.try_push_back(lvalue), v.data() + 3); - EXPECT_EQ(v.try_emplace_back(), v.data() + 4); - EXPECT_EQ(v, (V{"1", "4", "24", "23", ""})); - EXPECT_EQ(v.try_push_back("24"), nullptr); - EXPECT_EQ(v.try_push_back(lvalue), nullptr); - EXPECT_EQ(v.try_emplace_back(), nullptr); - EXPECT_EQ(v, (V{"1", "4", "24", "23", ""})); // failed try_push_back has the strong guarantee - } -} - -TEST(inplace_vector, EraseSingle) -{ - { - using V = sg14::inplace_vector; - V v = {1, 2, 3, 4}; - auto it = v.erase(v.begin() + 1); - static_assert(std::is_same_v); - EXPECT_EQ(it, v.begin() + 1); - EXPECT_EQ(v, (V{1, 3, 4})); - it = v.erase(v.begin()); - EXPECT_EQ(it, v.begin()); - EXPECT_EQ(v, (V{3, 4})); - it = v.erase(v.begin() + 1); - EXPECT_EQ(it, v.end()); - EXPECT_EQ(v, V{3}); - it = v.erase(v.begin()); - EXPECT_EQ(it, v.end()); - EXPECT_TRUE(v.empty()); - } - { - using V = sg14::inplace_vector; - V v = {"1", "2", "3", "4"}; - auto it = v.erase(v.begin() + 1); - static_assert(std::is_same_v); - EXPECT_EQ(it, v.begin() + 1); - EXPECT_EQ(v, (V{"1", "3", "4"})); - it = v.erase(v.begin()); - EXPECT_EQ(it, v.begin()); - EXPECT_EQ(v, (V{"3", "4"})); - it = v.erase(v.begin() + 1); - EXPECT_EQ(it, v.end()); - EXPECT_EQ(v, V{"3"}); - it = v.erase(v.begin()); - EXPECT_EQ(it, v.end()); - EXPECT_TRUE(v.empty()); - } - { - using V = sg14::inplace_vector; - const char *a[4] = {"1", "2", "3", "4"}; - V v = V(a, a+4); - auto it = v.erase(v.begin() + 1); - static_assert(std::is_same_v); - EXPECT_EQ(it, v.begin() + 1); - EXPECT_EQ(v.size(), 3u); - it = v.erase(v.begin()); - EXPECT_EQ(it, v.begin()); - EXPECT_EQ(v, V(a+2, a+4)); - it = v.erase(v.begin() + 1); - EXPECT_EQ(it, v.end()); - EXPECT_EQ(v, V(a+2, a+3)); - it = v.erase(v.begin()); - EXPECT_EQ(it, v.end()); - EXPECT_TRUE(v.empty()); - } -} - -TEST(inplace_vector, EraseMulti) -{ - { - using V = sg14::inplace_vector; - V v = {1, 2, 3, 4}; - auto it = v.erase(v.begin() + 1, v.begin() + 3); - static_assert(std::is_same_v); - EXPECT_EQ(it, v.begin() + 1); - EXPECT_EQ(v, (V{1, 4})); - it = v.erase(v.begin(), v.begin()); - EXPECT_EQ(it, v.begin()); - EXPECT_EQ(v, (V{1, 4})); - it = v.erase(v.begin() + 1, v.end()); - EXPECT_EQ(it, v.end()); - EXPECT_EQ(v, V{1}); - it = v.erase(v.begin(), v.end()); - EXPECT_EQ(it, v.end()); - EXPECT_TRUE(v.empty()); - } - { - using V = sg14::inplace_vector; - V v = {"1", "2", "3", "4"}; - auto it = v.erase(v.begin() + 1, v.begin() + 3); - static_assert(std::is_same_v); - EXPECT_EQ(it, v.begin() + 1); - EXPECT_EQ(v, (V{"1", "4"})); - it = v.erase(v.begin(), v.begin()); - EXPECT_EQ(it, v.begin()); - EXPECT_EQ(v, (V{"1", "4"})); - it = v.erase(v.begin() + 1, v.end()); - EXPECT_EQ(it, v.end()); - EXPECT_EQ(v, V{"1"}); - it = v.erase(v.begin(), v.end()); - EXPECT_EQ(it, v.end()); - EXPECT_TRUE(v.empty()); - } - { - using V = sg14::inplace_vector; - const char *a[4] = {"1", "2", "3", "4"}; - V v = V(a, a+4); - auto it = v.erase(v.begin() + 1, v.begin() + 3); - static_assert(std::is_same_v); - EXPECT_EQ(it, v.begin() + 1); - EXPECT_EQ(v, Seq("1", "4")); - it = v.erase(v.begin(), v.begin()); - EXPECT_EQ(it, v.begin()); - EXPECT_EQ(v, Seq("1", "4")); - it = v.erase(v.begin() + 1, v.end()); - EXPECT_EQ(it, v.end()); - EXPECT_EQ(v, Seq("1")); - it = v.erase(v.begin(), v.end()); - EXPECT_EQ(it, v.end()); - EXPECT_TRUE(v.empty()); - } - { - using V = sg14::inplace_vector; - const char *a[4] = {"1", "2", "3", "4"}; - V v = V(a, a+4); - auto it = v.erase(v.begin() + 1, v.begin() + 3); - static_assert(std::is_same_v); - EXPECT_EQ(it, v.begin() + 1); - EXPECT_EQ(v, Seq("1", "4")); - it = v.erase(v.begin(), v.begin()); - EXPECT_EQ(it, v.begin()); - EXPECT_EQ(v, Seq("1", "4")); - it = v.erase(v.begin() + 1, v.end()); - EXPECT_EQ(it, v.end()); - EXPECT_EQ(v, Seq("1")); - it = v.erase(v.begin(), v.end()); - EXPECT_EQ(it, v.end()); - EXPECT_TRUE(v.empty()); - } -} - -TEST(inplace_vector, EraseRemoveIdiom) -{ - sg14::inplace_vector v; - std::mt19937 g; - std::generate_n(std::back_inserter(v), 1000, std::ref(g)); - v.erase(std::remove_if(v.begin(), v.end(), [](auto x) { return x % 100 == 0; }), v.end()); - EXPECT_TRUE(std::none_of(v.begin(), v.end(), [](auto x) { return x % 100 == 0; })); - v.erase(std::remove_if(v.begin(), v.end(), [](auto x) { return x % 100 == 1; }), v.end()); - EXPECT_TRUE(std::none_of(v.begin(), v.end(), [](auto x) { return x % 100 == 1; })); - for (unsigned i = 0; i < 100; ++i) { - v.erase(std::remove_if(v.begin(), v.end(), [&](auto x) { return x % 100 == i; }), v.end()); - EXPECT_TRUE(std::none_of(v.begin(), v.end(), [&](auto x) { return x % 100 == i; })); - } - EXPECT_TRUE(v.empty()); -} - -TEST(inplace_vector, AssignFromInitList) -{ - { - using V = sg14::inplace_vector, 4>; - V v = {{1,2}, {3,4}}; - v = {{5,6}}; - EXPECT_EQ(v, (V{{5,6}})); - v = {{7,8},{9,10},{11,12}}; - EXPECT_EQ(v, (V{{7,8},{9,10},{11,12}})); - ASSERT_THROW((v = {{}, {}, {}, {}, {}, {}}), std::bad_alloc); - static_assert(std::is_same_v); - static_assert(std::is_same_v); - } - { - struct Counter { - int i_ = 0; - Counter(int i): i_(i) {} - Counter(const Counter& rhs) : i_(rhs.i_ + 1) {} - void operator=(const Counter& rhs) { i_ = rhs.i_ + 10; } - }; - sg14::inplace_vector v = { 100 }; - EXPECT_EQ(v[0].i_, 101); // copied from init-list into vector - v = { 200, 300 }; - EXPECT_EQ(v[0].i_, 210); // assigned from init-list into vector - EXPECT_EQ(v[1].i_, 301); // copied from init-list into vector - } -} - -TEST(inplace_vector, Comparison) -{ - sg14::inplace_vector a; - sg14::inplace_vector b; - EXPECT_TRUE((a == b) && (a <= b) && (a >= b)); - EXPECT_FALSE((a != b) || (a < b) || (a > b)); -#if __cpp_impl_three_way_comparison >= 201907L - EXPECT_EQ(a <=> b, std::strong_ordering::equal); -#endif - a = {1,2,3}; - b = {1,2,3}; - EXPECT_TRUE((a == b) && (a <= b) && (a >= b)); - EXPECT_FALSE((a != b) || (a < b) || (a > b)); -#if __cpp_impl_three_way_comparison >= 201907L - EXPECT_EQ(a <=> b, std::strong_ordering::equal); -#endif - a = {1,2,3}; - b = {1}; - EXPECT_TRUE((a != b) && (a > b) && (a >= b)); - EXPECT_TRUE((b != a) && (b < a) && (b <= a)); - EXPECT_FALSE((a == b) || (a < b) || (a <= b)); - EXPECT_FALSE((b == a) || (b > a) || (b >= a)); -#if __cpp_impl_three_way_comparison >= 201907L - EXPECT_EQ(a <=> b, std::strong_ordering::greater); - EXPECT_EQ(b <=> a, std::strong_ordering::less); -#endif - a = {1,3}; - b = {1,2,3}; - EXPECT_TRUE((a != b) && (a > b) && (a >= b)); - EXPECT_TRUE((b != a) && (b < a) && (b <= a)); - EXPECT_FALSE((a == b) || (a < b) || (a <= b)); - EXPECT_FALSE((b == a) || (b > a) || (b >= a)); -#if __cpp_impl_three_way_comparison >= 201907L - EXPECT_EQ(a <=> b, std::strong_ordering::greater); - EXPECT_EQ(b <=> a, std::strong_ordering::less); -#endif - a = {1,2,4}; - b = {1,2,3}; - EXPECT_TRUE((a != b) && (a > b) && (a >= b)); - EXPECT_TRUE((b != a) && (b < a) && (b <= a)); - EXPECT_FALSE((a == b) || (a < b) || (a <= b)); - EXPECT_FALSE((b == a) || (b > a) || (b >= a)); -#if __cpp_impl_three_way_comparison >= 201907L - EXPECT_EQ(a <=> b, std::strong_ordering::greater); - EXPECT_EQ(b <=> a, std::strong_ordering::less); -#endif -} - -TEST(inplace_vector, Reserve) -{ - sg14::inplace_vector v = {1,2,3}; - v.reserve(0); - EXPECT_EQ(v.capacity(), 10u); - v.reserve(5); - EXPECT_EQ(v.capacity(), 10u); - v.reserve(10); - EXPECT_EQ(v.capacity(), 10u); - ASSERT_THROW(v.reserve(11), std::bad_alloc); - v.shrink_to_fit(); - EXPECT_EQ(v.capacity(), 10u); - EXPECT_EQ(v, (sg14::inplace_vector{1,2,3})); -} - -TEST(inplace_vector, Resize) -{ - sg14::inplace_vector v; - v.resize(2); - EXPECT_EQ(v, (sg14::inplace_vector{"", ""})); - v.resize(1, "a"); - EXPECT_EQ(v, (sg14::inplace_vector{""})); - v.resize(3, "b"); - EXPECT_EQ(v, (sg14::inplace_vector{"", "b", "b"})); - v.resize(4); - EXPECT_EQ(v, (sg14::inplace_vector{"", "b", "b", ""})); - v.resize(2, "c"); - EXPECT_EQ(v, (sg14::inplace_vector{"", "b"})); - ASSERT_THROW(v.resize(5), std::bad_alloc); - ASSERT_THROW(v.resize(6, "d"), std::bad_alloc); - EXPECT_EQ(v, (sg14::inplace_vector{"", "b"})); // unchanged -} - -#endif // __cplusplus >= 201703