AK: Dont trivially move Optional<T> when T is not trivially destructible

This mirrors what `std::optional` is doing.

Co-Authored-By: Dan Klishch <danilklishch@gmail.com>
This commit is contained in:
Hendiadyoin1
2025-04-10 00:36:05 +02:00
committed by Andrew Kaster
parent 3264385dc9
commit f2f18d9025
2 changed files with 33 additions and 2 deletions

View File

@@ -135,7 +135,9 @@ public:
requires(!IsMoveConstructible<T> || !IsDestructible<T>)
= delete;
ALWAYS_INLINE constexpr Optional& operator=(Optional&& other)
requires(IsMoveAssignable<T> && IsMoveConstructible<T> && !IsTriviallyMoveAssignable<T>)
requires(
IsMoveAssignable<T> && IsMoveConstructible<T>
&& (!IsTriviallyMoveAssignable<T> || !IsTriviallyMoveConstructible<T> || !IsTriviallyDestructible<T>))
{
if (this == &other)
return *this;
@@ -153,7 +155,9 @@ public:
}
ALWAYS_INLINE constexpr Optional& operator=(Optional&& other)
requires(IsMoveConstructible<T> && !IsMoveAssignable<T>)
requires(
IsMoveConstructible<T> && !IsMoveAssignable<T>
&& (!IsTriviallyMoveConstructible<T> || !IsTriviallyDestructible<T>))
{
clear();

View File

@@ -332,6 +332,33 @@ TEST_CASE(uninitialized_constructor)
EXPECT(!opt.value().m_default_constructed);
}
TEST_CASE(non_trivial_destructor_is_called_on_move_assignment)
{
static int foo_destruction_count = 0;
struct Foo {
Foo() { }
Foo(Foo&&) = default;
~Foo()
{
++foo_destruction_count;
}
Foo& operator=(Foo&&) = default;
};
static_assert(!IsTriviallyMoveAssignable<Optional<Foo>>);
Optional<Foo> foo = Foo {}; // 1. The immediate value needs to be destroyed
Optional<Foo> foo2;
foo = AK::move(foo2); // 2. The move releases the value, which destroys the moved-from stored value
EXPECT_EQ(foo_destruction_count, 2);
// As Optional<Foo> does not trivially move, moved-from values are empty
// Ignoring the fact that we are touching a moved from value here
EXPECT_EQ(foo.has_value(), false);
}
consteval bool test_constexpr()
{
Optional<int> none;