Skip to content

Commit

Permalink
[inplace_vector] Fix special members for partially-trivial T
Browse files Browse the repository at this point in the history
And use conditionally trivial special members, which aren't supported
by Clang trunk yet, but are supported by MSVC.
  • Loading branch information
Quuxplusone committed Dec 4, 2023
1 parent ab2a31b commit 0eea95d
Show file tree
Hide file tree
Showing 2 changed files with 173 additions and 16 deletions.
41 changes: 26 additions & 15 deletions include/sg14/inplace_vector.h
Original file line number Diff line number Diff line change
Expand Up @@ -93,25 +93,25 @@ struct SG14_INPLACE_VECTOR_TRIVIALLY_RELOCATABLE_IF(std::is_trivially_relocatabl
constexpr void set_size_(size_t n) { size_ = n; }

constexpr explicit ipvbase() noexcept {}
constexpr ipvbase(const ipvbase& rhs)
ipvbase(const ipvbase& rhs)
noexcept(std::is_nothrow_copy_constructible_v<T>)
{
if constexpr (std::is_trivially_copy_constructible_v<T>) {
std::memmove(this, std::addressof(rhs), sizeof(ipvbase));
std::memmove((void*)this, (const void*)std::addressof(rhs), sizeof(ipvbase));
} else {
std::uninitialized_copy_n(rhs.data_, rhs.size_, data_);
size_ = rhs.size_;
}
}
constexpr ipvbase(ipvbase&& rhs)
ipvbase(ipvbase&& rhs)
noexcept(std::is_nothrow_move_constructible_v<T>
#if defined(__cpp_lib_trivially_relocatable)
|| std::is_trivially_relocatable_v<T>
#endif // __cpp_lib_trivially_relocatable
)
{
if constexpr (std::is_trivially_move_constructible_v<T>) {
std::memmove(this, std::addressof(rhs), sizeof(ipvbase));
std::memmove((void*)this, (const void*)std::addressof(rhs), sizeof(ipvbase));
#if defined(__cpp_lib_trivially_relocatable)
} else if constexpr (std::is_trivially_relocatable_v<T>) {
std::uninitialized_relocate_n(rhs.data_, rhs.size_, data_);
Expand All @@ -123,11 +123,11 @@ struct SG14_INPLACE_VECTOR_TRIVIALLY_RELOCATABLE_IF(std::is_trivially_relocatabl
size_ = rhs.size_;
}
}
constexpr void operator=(const ipvbase& rhs)
noexcept(std::is_nothrow_copy_assignable_v<T>)
void operator=(const ipvbase& rhs)
noexcept(std::is_nothrow_copy_constructible_v<T> && std::is_nothrow_copy_assignable_v<T>)
{
if constexpr (std::is_trivially_copy_assignable_v<T>) {
std::memmove(this, std::addressof(rhs), sizeof(ipvbase));
if constexpr (std::is_trivially_copy_constructible_v<T> && std::is_trivially_copy_assignable_v<T> && std::is_trivially_destructible_v<T>) {
std::memmove((void*)this, (const void*)std::addressof(rhs), sizeof(ipvbase));
} else if (this == std::addressof(rhs)) {
// do nothing
} else if (rhs.size_ <= size_) {
Expand All @@ -140,11 +140,11 @@ struct SG14_INPLACE_VECTOR_TRIVIALLY_RELOCATABLE_IF(std::is_trivially_relocatabl
size_ = rhs.size_;
}
}
constexpr void operator=(ipvbase&& rhs)
noexcept(std::is_nothrow_move_assignable_v<T>)
void operator=(ipvbase&& rhs)
noexcept(std::is_nothrow_move_constructible_v<T> && std::is_nothrow_move_assignable_v<T>)
{
if constexpr (std::is_trivially_move_assignable_v<T>) {
std::memmove(this, std::addressof(rhs), sizeof(ipvbase));
if constexpr (std::is_trivially_move_constructible_v<T> && std::is_trivially_move_assignable_v<T> && std::is_trivially_destructible_v<T>) {
std::memmove((void*)this, (const void*)std::addressof(rhs), sizeof(ipvbase));
} else if (this == std::addressof(rhs)) {
// do nothing
} else if (rhs.size_ <= size_) {
Expand All @@ -165,6 +165,14 @@ 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<T> = default;
ipvbase(ipvbase&&) requires std::is_trivially_move_constructible_v<T> = default;
ipvbase& operator=(const ipvbase&) requires std::is_trivially_copy_constructible_v<T> && std::is_trivially_copy_assignable_v<T> && std::is_trivially_destructible_v<T> = default;
ipvbase& operator=(ipvbase&&) requires std::is_trivially_move_constructible_v<T> && std::is_trivially_move_assignable_v<T> && std::is_trivially_destructible_v<T> = default;
~ipvbase() requires std::is_trivially_destructible_v<T> = default;
#endif // __cpp_concepts >= 202002L

#if __cplusplus >= 202002L
constexpr
#endif // __cplusplus >= 202002L
Expand Down Expand Up @@ -196,9 +204,12 @@ struct ipvbase_trivial {

template<class T, size_t N>
using ipvbase_t = std::conditional_t<
N == 0, ipvbase_zero<T>,
N == 0,
ipvbase_zero<T>,
std::conditional_t<
std::is_trivially_copyable_v<T>, ipvbase_trivial<T, N>, ipvbase<T, N>
std::is_trivially_copyable_v<T>,
ipvbase_trivial<T, N>,
ipvbase<T, N>
>
>;

Expand All @@ -221,13 +232,13 @@ class inplace_vector : ipvbase_assignable<T>, ipvbase_t<T, N> {

// [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<value_type> il) { assign(il.begin(), il.end()); return *this; }

constexpr inplace_vector() = default;
constexpr inplace_vector(std::initializer_list<value_type> il) : inplace_vector(il.begin(), il.end()) { }
constexpr explicit inplace_vector(size_t n) {
if (n > N) {
Expand Down
148 changes: 147 additions & 1 deletion test/inplace_vector_test.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -97,7 +97,7 @@ TEST(inplace_vector, TrivialTraits)
static_assert(!std::is_nothrow_copy_constructible_v<T>);
static_assert(std::is_nothrow_move_constructible_v<T> == !msvc);
static_assert(!std::is_nothrow_copy_assignable_v<T>);
static_assert(std::is_nothrow_move_assignable_v<T>);
static_assert(std::is_nothrow_move_assignable_v<T> == !msvc);
static_assert(std::is_nothrow_destructible_v<T>);
#if defined(__cpp_lib_trivially_relocatable)
static_assert(std::is_trivially_relocatable_v<T> == msvc);
Expand Down Expand Up @@ -207,6 +207,152 @@ 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<S, 10>;
static_assert(!std::is_trivially_copyable_v<T>);
static_assert(!std::is_trivially_copy_constructible_v<T>);
static_assert(!std::is_trivially_copy_assignable_v<T>);
static_assert(std::is_trivially_move_constructible_v<T> == ConditionallyTrivial);
static_assert(std::is_trivially_move_assignable_v<T> == ConditionallyTrivial);
static_assert(std::is_trivially_destructible_v<T> == 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<S, 10>;
static_assert(!std::is_trivially_copyable_v<T>);
static_assert(std::is_trivially_copy_constructible_v<T> == ConditionallyTrivial);
static_assert(!std::is_trivially_copy_assignable_v<T>);
static_assert(std::is_trivially_move_constructible_v<T> == ConditionallyTrivial);
static_assert(std::is_trivially_move_assignable_v<T> == ConditionallyTrivial);
static_assert(std::is_trivially_destructible_v<T> == 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<S, 10>;
static_assert(!std::is_trivially_copyable_v<T>);
// 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<T>);
static_assert(!std::is_trivially_copy_assignable_v<T>);
static_assert(!std::is_trivially_move_constructible_v<T>);
static_assert(!std::is_trivially_move_assignable_v<T>);
static_assert(!std::is_trivially_destructible_v<T>);
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)
{
{
Expand Down

0 comments on commit 0eea95d

Please sign in to comment.