From 56155e7c2d9061c4fbfff788cacbff3f519b6aa6 Mon Sep 17 00:00:00 2001 From: Arthur O'Dwyer Date: Sun, 3 Dec 2023 15:04:28 -0500 Subject: [PATCH] [inplace_vector] Fix special members for partially-trivial T And use conditionally trivial special members, which aren't supported by Clang trunk yet, but are supported by GCC. --- include/sg14/inplace_vector.h | 20 +++-- test/inplace_vector_test.cpp | 144 ++++++++++++++++++++++++++++++++++ 2 files changed, 158 insertions(+), 6 deletions(-) diff --git a/include/sg14/inplace_vector.h b/include/sg14/inplace_vector.h index 1ef3d7d..0afb4e4 100644 --- a/include/sg14/inplace_vector.h +++ b/include/sg14/inplace_vector.h @@ -124,9 +124,9 @@ struct SG14_INPLACE_VECTOR_TRIVIALLY_RELOCATABLE_IF(std::is_trivially_relocatabl } } constexpr void operator=(const ipvbase& rhs) - noexcept(std::is_nothrow_copy_assignable_v) + noexcept(std::is_nothrow_copy_constructible_v && std::is_nothrow_copy_assignable_v) { - if constexpr (std::is_trivially_copy_assignable_v) { + if constexpr (std::is_trivially_copy_constructible_v && std::is_trivially_copy_assignable_v && std::is_trivially_destructible_v) { std::memmove(this, std::addressof(rhs), sizeof(ipvbase)); } else if (this == std::addressof(rhs)) { // do nothing @@ -141,9 +141,9 @@ struct SG14_INPLACE_VECTOR_TRIVIALLY_RELOCATABLE_IF(std::is_trivially_relocatabl } } constexpr void operator=(ipvbase&& rhs) - noexcept(std::is_nothrow_move_assignable_v) + noexcept(std::is_nothrow_move_constructible_v && std::is_nothrow_move_assignable_v) { - if constexpr (std::is_trivially_move_assignable_v) { + if constexpr (std::is_trivially_move_constructible_v && std::is_trivially_move_assignable_v && std::is_trivially_destructible_v) { std::memmove(this, std::addressof(rhs), sizeof(ipvbase)); } else if (this == std::addressof(rhs)) { // do nothing @@ -165,6 +165,13 @@ struct SG14_INPLACE_VECTOR_TRIVIALLY_RELOCATABLE_IF(std::is_trivially_relocatabl } } +#if __cpp_concepts >= 202002L + ipvbase(const ipvbase&) requires std::is_trivially_copy_constructible_v = default; + ipvbase(ipvbase&&) requires std::is_trivially_move_constructible_v = default; + void operator=(const ipvbase&) requires std::is_trivially_copy_constructible_v && std::is_trivially_copy_assignable_v && std::is_trivially_destructible_v = default; + void operator=(ipvbase&&) requires std::is_trivially_move_constructible_v && std::is_trivially_move_assignable_v && std::is_trivially_destructible_v = default; +#endif // __cpp_concepts >= 202002L + #if __cplusplus >= 202002L constexpr #endif // __cplusplus >= 202002L @@ -198,7 +205,8 @@ template using ipvbase_t = std::conditional_t< N == 0, ipvbase_zero, std::conditional_t< - std::is_trivially_copyable_v, ipvbase_trivial, ipvbase + std::is_trivially_copyable_v, + ipvbase_trivial, ipvbase > >; @@ -221,13 +229,13 @@ class inplace_vector : ipvbase_assignable, ipvbase_t { // [containers.sequences.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() = default; constexpr inplace_vector(std::initializer_list il) : inplace_vector(il.begin(), il.end()) { } constexpr explicit inplace_vector(size_t n) { if (n > N) { diff --git a/test/inplace_vector_test.cpp b/test/inplace_vector_test.cpp index f0da575..1d9bb92 100644 --- a/test/inplace_vector_test.cpp +++ b/test/inplace_vector_test.cpp @@ -207,6 +207,150 @@ TEST(inplace_vector, TrivialTraits) } } +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); + static_assert(std::is_trivially_copy_constructible_v == ConditionallyTrivial); + static_assert(std::is_trivially_copy_assignable_v == ConditionallyTrivial); + 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); + 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) { {