Skip to content

Commit

Permalink
Assignment 2
Browse files Browse the repository at this point in the history
  • Loading branch information
pavel-kirienko committed Mar 2, 2024
1 parent 2284558 commit 1ef585f
Show file tree
Hide file tree
Showing 2 changed files with 330 additions and 5 deletions.
325 changes: 325 additions & 0 deletions cetlvast/suites/unittest/test_pf17_variant.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -654,6 +654,7 @@ struct test_assignment_1

static void test_matching_assignment_throwing()
{
#if __cpp_exceptions
using cetl::pf17::variant;
using cetl::pf17::get;
using cetlvast::smf_policies::policy_nontrivial;
Expand Down Expand Up @@ -691,6 +692,7 @@ struct test_assignment_1
}
EXPECT_EQ((T::dtor_policy_value == policy_nontrivial) ? 1 : 0, dtor1); // Assignment did not succeed.
EXPECT_EQ((T::dtor_policy_value == policy_nontrivial) ? 1 : 0, dtor2);
#endif
}

static void test_nonmatching_copy_noexcept_move_noexcept()
Expand Down Expand Up @@ -972,3 +974,326 @@ TYPED_TEST(test_smf_policy_combinations, assignment_1)
{
test_assignment_1<TypeParam>::test();
}

// --------------------------------------------------------------------------------------------

/// For the move assignment to work, every T in Ts shall be both
/// (move-constructible or copy-constructible) and (move-assignable or copy-assignable).
/// Notes:
/// - A type does not have to implement move assignment operator in order to satisfy MoveAssignable:
/// a copy assignment operator that takes its parameter by value or as a const Type&, will bind to rvalue argument.
/// - A type does not have to implement a move constructor to satisfy MoveConstructible:
/// a copy constructor that takes a const T& argument can bind rvalue expressions.
template <typename T,
std::uint8_t CopyCtorPolicy = T::copy_ctor_policy_value,
std::uint8_t CopyAssignmentPolicy = T::copy_assignment_policy_value,
std::uint8_t MoveCtorPolicy = T::move_ctor_policy_value,
std::uint8_t MoveAssignmentPolicy = T::move_assignment_policy_value>
struct test_assignment_2
{
static void test_matching_assignment_noexcept()
{
using cetl::pf17::variant;
using cetl::pf17::get;
using cetlvast::smf_policies::policy_nontrivial;
using cetlvast::smf_policies::policy_deleted;
std::uint32_t dtor1 = 0;
std::uint32_t dtor2 = 0;
{
variant<T, std::int64_t> v1;
variant<T, std::int64_t> v2;
get<T>(v1).configure_destruction_counter(&dtor1);
get<T>(v2).configure_destruction_counter(&dtor2);
v2 = std::move(v1); // Invoke copy assignment.
EXPECT_FALSE(v1.valueless_by_exception()); // NOLINT(*-use-after-move)
EXPECT_FALSE(v2.valueless_by_exception());
// Check v1 counters.
EXPECT_EQ(0U, get<T>(v1).get_copy_ctor_count());
EXPECT_EQ(0U, get<T>(v1).get_move_ctor_count());
EXPECT_EQ(0U, get<T>(v1).get_copy_assignment_count());
EXPECT_EQ(0U, get<T>(v1).get_move_assignment_count());
EXPECT_EQ(0, dtor1);
// Check v2 counters.
EXPECT_EQ(0U, get<T>(v2).get_copy_ctor_count());
EXPECT_EQ(0U, get<T>(v2).get_move_ctor_count());
EXPECT_EQ(((T::copy_assignment_policy_value == policy_nontrivial) &&
(T::move_assignment_policy_value == policy_deleted))
? 1
: 0,
get<T>(v2).get_copy_assignment_count());
EXPECT_EQ((T::move_assignment_policy_value == policy_nontrivial) ? 1 : 0,
get<T>(v2).get_move_assignment_count());
EXPECT_EQ(0, dtor2);
}
EXPECT_EQ((T::dtor_policy_value == policy_nontrivial) ? 2 : 0, dtor1);
EXPECT_EQ(0, dtor2); // This is because of the assignment.
}

static void test_matching_assignment_throwing()
{
#if __cpp_exceptions
using cetl::pf17::variant;
using cetl::pf17::get;
using cetlvast::smf_policies::policy_deleted;
using cetlvast::smf_policies::policy_nontrivial;
struct U : T
{
U() = default;
U(const U&) noexcept = default;
U(U&&) noexcept = default;
U& operator=(const U&) noexcept = default;
U& operator=(U&&) // NOLINT(*-noexcept-move-constructor)
{
throw std::exception();
}
};
std::uint32_t dtor1 = 0;
std::uint32_t dtor2 = 0;
{
variant<U, std::int64_t> v1;
variant<U, std::int64_t> v2;
get<U>(v1).configure_destruction_counter(&dtor1);
get<U>(v2).configure_destruction_counter(&dtor2);
EXPECT_ANY_THROW(v2 = std::move(v1)); // Invoke move assignment. It will throw.
// Destination does not become valueless despite the exception.
EXPECT_FALSE(v1.valueless_by_exception()); // NOLINT(*-use-after-move)
EXPECT_FALSE(v2.valueless_by_exception());
// Check v1 counters.
EXPECT_EQ(0U, get<U>(v1).get_copy_ctor_count());
EXPECT_EQ(0U, get<U>(v1).get_move_ctor_count());
EXPECT_EQ(0U, get<U>(v1).get_copy_assignment_count());
EXPECT_EQ(0U, get<U>(v1).get_move_assignment_count());
EXPECT_EQ(0U, dtor1);
// Check v2 counters.
EXPECT_EQ(0U, get<U>(v2).get_copy_ctor_count());
EXPECT_EQ(0U, get<U>(v2).get_move_ctor_count());
EXPECT_EQ(0U, get<U>(v2).get_copy_assignment_count()); // Assignment did not succeed.
EXPECT_EQ(0U, get<U>(v2).get_move_assignment_count()); // Assignment did not succeed.
EXPECT_EQ(0U, dtor2);
}
EXPECT_EQ((T::dtor_policy_value == policy_nontrivial) ? 1 : 0, dtor1); // Assignment did not succeed.
EXPECT_EQ((T::dtor_policy_value == policy_nontrivial) ? 1 : 0, dtor2);
#endif
}

static void test_nonmatching_noexcept()
{
using cetl::pf17::variant;
using cetl::pf17::get;
using cetl::pf17::in_place_type;
using cetlvast::smf_policies::policy_deleted;
using cetlvast::smf_policies::policy_nontrivial;
struct U : T
{};
std::uint32_t dtor1 = 0;
std::uint32_t dtor2 = 0;
{
variant<U, T> v1(in_place_type<U>);
variant<U, T> v2(in_place_type<T>);
get<U>(v1).configure_destruction_counter(&dtor1);
get<T>(v2).configure_destruction_counter(&dtor2);
v2 = std::move(v1); // Invoke move construction.
EXPECT_FALSE(v1.valueless_by_exception()); // NOLINT(*-use-after-move)
EXPECT_FALSE(v2.valueless_by_exception());
EXPECT_EQ(0, v1.index());
EXPECT_EQ(0, v2.index());
// Check v1 counters.
EXPECT_EQ(0U, get<U>(v1).get_copy_ctor_count());
EXPECT_EQ(0U, get<U>(v1).get_move_ctor_count());
EXPECT_EQ(0U, get<U>(v1).get_copy_assignment_count());
EXPECT_EQ(0U, get<U>(v1).get_move_assignment_count());
EXPECT_EQ(0U, dtor1);
// Check v2 counters.
EXPECT_EQ(((T::copy_ctor_policy_value == policy_nontrivial) &&
(T::move_ctor_policy_value == policy_deleted))
? 1
: 0,
get<U>(v2).get_copy_ctor_count());
EXPECT_EQ((T::move_ctor_policy_value == policy_nontrivial) ? 1 : 0, get<U>(v2).get_move_ctor_count());
EXPECT_EQ(0U, get<U>(v2).get_copy_assignment_count());
EXPECT_EQ(0U, get<U>(v2).get_move_assignment_count());
EXPECT_EQ((T::dtor_policy_value == policy_nontrivial) ? 1 : 0, dtor2); // T destroyed.
}
EXPECT_EQ((T::dtor_policy_value == policy_nontrivial) ? 2 : 0, dtor1);
EXPECT_EQ((T::dtor_policy_value == policy_nontrivial) ? 1 : 0, dtor2);
}

static void test_nonmatching_throwing()
{
#if __cpp_exceptions
using cetl::pf17::variant;
using cetl::pf17::variant_npos;
using cetl::pf17::get;
using cetl::pf17::in_place_type;
using cetlvast::smf_policies::policy_deleted;
using cetlvast::smf_policies::policy_nontrivial;
struct U : T
{
U() = default;
U(const U&) noexcept = default;
U(U&& other) // NOLINT(*-noexcept-move-constructor)
: T(std::move(other)) // This resolves either to the move ctor or copy ctor!
{
throw std::exception();
}
U& operator=(const U&) noexcept = default;
U& operator=(U&&) noexcept = default;
};
std::uint32_t dtor1 = 0;
std::uint32_t dtor2 = 0;
{
variant<U, T> v1(in_place_type<U>);
variant<U, T> v2(in_place_type<T>);
get<U>(v1).configure_destruction_counter(&dtor1);
get<T>(v2).configure_destruction_counter(&dtor2);
EXPECT_ANY_THROW(v2 = std::move(v1)); // Invoke move construction.
EXPECT_FALSE(v1.valueless_by_exception()); // NOLINT(*-use-after-move)
EXPECT_TRUE(v2.valueless_by_exception()); // v2 is valueless because the move ctor of U throws.
EXPECT_EQ(0, v1.index());
EXPECT_EQ(variant_npos, v2.index());
// Check v1 counters.
EXPECT_EQ(0U, get<U>(v1).get_copy_ctor_count());
EXPECT_EQ(0U, get<U>(v1).get_move_ctor_count());
EXPECT_EQ(0U, get<U>(v1).get_copy_assignment_count());
EXPECT_EQ(0U, get<U>(v1).get_move_assignment_count());
// The dtor counter is 1 because the base which does the counting is already constructed by the time
// the exception is thrown, hence its destructor is invoked, which increments the counter.
EXPECT_EQ((T::dtor_policy_value == policy_nontrivial) ? 1 : 0, dtor1);
// v2 counters cannot be checked because it is valueless, except for the dtor counter.
EXPECT_EQ((T::dtor_policy_value == policy_nontrivial) ? 1 : 0, dtor2);
}
EXPECT_EQ((T::dtor_policy_value == policy_nontrivial) ? 2 : 0, dtor1);
EXPECT_EQ((T::dtor_policy_value == policy_nontrivial) ? 1 : 0, dtor2);
#endif
}

static void test_valueless()
{
#if __cpp_exceptions
using cetl::pf17::variant;
using cetl::pf17::get;
using cetl::pf17::in_place_type;
using cetlvast::smf_policies::policy_deleted;
using cetlvast::smf_policies::policy_nontrivial;
struct U : T
{
U()
{
throw std::exception();
}
};
std::uint32_t dtor1 = 0;
std::uint32_t dtor2 = 0;
{
variant<T, U> v1;
variant<T, U> v2;
// Make v1 valueless because the ctor of U throws.
EXPECT_ANY_THROW(v1.template emplace<U>());
EXPECT_TRUE(v1.valueless_by_exception());
EXPECT_FALSE(v2.valueless_by_exception());
// Move valueless into non-valueless.
v2 = std::move(v1);
EXPECT_TRUE(v1.valueless_by_exception()); // NOLINT(*-use-after-move)
EXPECT_TRUE(v2.valueless_by_exception());
// Move valueless into valueless.
v1 = std::move(v2);
EXPECT_TRUE(v1.valueless_by_exception());
EXPECT_TRUE(v2.valueless_by_exception()); // NOLINT(*-use-after-move)
// Make v2 non-valueless, then move that into v1.
v2.template emplace<T>();
get<T>(v2).configure_destruction_counter(&dtor2);
v1 = std::move(v2);
get<T>(v1).configure_destruction_counter(&dtor1);
EXPECT_FALSE(v1.valueless_by_exception());
EXPECT_FALSE(v2.valueless_by_exception()); // NOLINT(*-use-after-move)
EXPECT_EQ(0, dtor1);
EXPECT_EQ(0, dtor2);
EXPECT_EQ(0, v1.index());
EXPECT_EQ(0, v2.index());
EXPECT_EQ(((T::copy_ctor_policy_value == policy_nontrivial) &&
(T::move_ctor_policy_value == policy_deleted))
? 1
: 0,
get<T>(v1).get_copy_ctor_count());
EXPECT_EQ((T::move_ctor_policy_value == policy_nontrivial) ? 1 : 0, get<T>(v1).get_move_ctor_count());
EXPECT_EQ(0, get<T>(v1).get_copy_assignment_count());
EXPECT_EQ(0, get<T>(v1).get_move_assignment_count());
EXPECT_EQ(0, get<T>(v2).get_copy_ctor_count());
EXPECT_EQ(0, get<T>(v2).get_move_ctor_count());
EXPECT_EQ(0, get<T>(v2).get_copy_assignment_count());
EXPECT_EQ(0, get<T>(v2).get_move_assignment_count());
}
EXPECT_EQ((T::dtor_policy_value == policy_nontrivial) ? 1 : 0, dtor1);
EXPECT_EQ((T::dtor_policy_value == policy_nontrivial) ? 1 : 0, dtor2);
#endif
}

static void test()
{
test_matching_assignment_noexcept();
test_matching_assignment_throwing();
test_nonmatching_noexcept();
test_nonmatching_throwing();
test_valueless();
}
};
template <typename T, std::uint8_t CopyAssignmentPolicy, std::uint8_t MoveAssignmentPolicy>
struct test_assignment_2<T,
cetlvast::smf_policies::policy_deleted,
CopyAssignmentPolicy,
cetlvast::smf_policies::policy_deleted,
MoveAssignmentPolicy>
{
static_assert(std::is_copy_assignable<T>::value == (CopyAssignmentPolicy != cetlvast::smf_policies::policy_deleted),
"");
static_assert(!std::is_copy_constructible<T>::value, "");
static_assert(std::is_move_assignable<T>::value, ""); // requires either copy or move assignment
static_assert(!std::is_move_constructible<T>::value, "");
static_assert(!std::is_copy_constructible<cetl::pf17::variant<T>>::value, "");
static_assert(!std::is_copy_assignable<cetl::pf17::variant<T>>::value, "");
static_assert(!std::is_move_constructible<cetl::pf17::variant<T>>::value, "");
static_assert(!std::is_move_assignable<cetl::pf17::variant<T>>::value, "");
static void test() {}
};
template <typename T, std::uint8_t CopyCtorPolicy, std::uint8_t MoveCtorPolicy>
struct test_assignment_2<T,
CopyCtorPolicy,
cetlvast::smf_policies::policy_deleted,
MoveCtorPolicy,
cetlvast::smf_policies::policy_deleted>
{
static_assert(!std::is_copy_assignable<T>::value, "");
static_assert(std::is_copy_constructible<T>::value == (CopyCtorPolicy != cetlvast::smf_policies::policy_deleted),
"");
static_assert(!std::is_move_assignable<T>::value, "");
static_assert(std::is_move_constructible<T>::value, ""); // requires either copy or move ctor
static_assert(std::is_copy_constructible<cetl::pf17::variant<T>>::value ==
(CopyCtorPolicy != cetlvast::smf_policies::policy_deleted),
"");
static_assert(!std::is_copy_assignable<cetl::pf17::variant<T>>::value, "");
static_assert(std::is_move_constructible<cetl::pf17::variant<T>>::value, ""); // requires either copy or move ctor
static_assert(!std::is_move_assignable<cetl::pf17::variant<T>>::value, "");
static void test() {}
};
template <typename T>
struct test_assignment_2<T,
cetlvast::smf_policies::policy_deleted,
cetlvast::smf_policies::policy_deleted,
cetlvast::smf_policies::policy_deleted,
cetlvast::smf_policies::policy_deleted>
{
static_assert(!std::is_copy_assignable<T>::value, "");
static_assert(!std::is_copy_constructible<T>::value, "");
static_assert(!std::is_move_assignable<T>::value, "");
static_assert(!std::is_move_constructible<T>::value, "");
static_assert(!std::is_copy_constructible<cetl::pf17::variant<T>>::value, "");
static_assert(!std::is_copy_assignable<cetl::pf17::variant<T>>::value, "");
static_assert(!std::is_move_constructible<cetl::pf17::variant<T>>::value, "");
static_assert(!std::is_move_assignable<cetl::pf17::variant<T>>::value, "");
static void test() {}
};

TYPED_TEST(test_smf_policy_combinations, assignment_2)
{
test_assignment_2<TypeParam>::test();
}
10 changes: 5 additions & 5 deletions include/cetl/pf17/variant.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -427,15 +427,15 @@ struct base_move_construction<types<Ts...>, smf_deleted> : base_copy_constructio
/// - Otherwise, if the alternative held by rhs is either nothrow copy constructible or not
/// nothrow move constructible (as determined by std::is_nothrow_copy_constructible and
/// std::is_nothrow_move_constructible, respectively), equivalent to
/// this->emplace<rhs.index()>(*std::get_if<rhs.index()>(std::addressof(rhs))).
/// `this->emplace<rhs.index()>(*std::get_if<rhs.index()>(std::addressof(rhs)))`.
/// *this may become valueless_by_exception if an exception is thrown on the copy-construction
/// inside emplace.
///
/// - Otherwise, equivalent to this->operator=(variant(rhs)).
/// - Otherwise, equivalent to `this->operator=(variant(rhs))`.
///
/// The meaning of the last two cases is that we want to minimize the likelihood of the valueless outcome.
/// If T is nothrow copyable, we simply invoke the copy ctor via construct<>(); otherwise, if T is not nothrow move
/// constructible, there's no way to do it better so we do the same thing -- invoke the copy ctor via construct<>().
/// If T is nothrow copyable, we simply invoke the copy ctor via `construct<>()`; otherwise, if T is not nothrow move
/// constructible, there's no way to do it better so we do the same thing -- invoke the copy ctor via `construct<>()`.
/// However, if T is nothrow move constructible, we can do better by creating a temporary copy on the side,
/// which can throw safely without the risk of making this valueless, and then (if that succeeded) we
/// nothrow-move it into this.
Expand Down Expand Up @@ -550,7 +550,7 @@ struct base_move_assignment<types<Ts...>, smf_nontrivial> : base_copy_assignment
// needs to be replaced. If an exception is thrown, *this becomes valueless inside construct().
other.chronomorphize([this, &other](const auto index) {
assert(index.value == other.m_index);
this->construct<index.value>(std::move(other.template as<index.value>()));
this->template construct<index.value>(std::move(other.template as<index.value>()));
});
}
else
Expand Down

0 comments on commit 1ef585f

Please sign in to comment.