Update (non-copy, non-move) assign operators.
Fix perfect-forwarded assign operator to look at condition to
decide whether it should participate in overload resolution.
Add Optional<U> copy- and move-like assign operators.
For that implementation, OptionalBase's copy-/move-assign
operators are slightly refactored.
BUG=784732
TEST=Ran trybot.
Change-Id: I69db9def857a1cce8e7b05f0c6e11922ee8d95db
Reviewed-on: https://chromium-review.googlesource.com/856539
Reviewed-by: danakj <danakj@chromium.org>
Commit-Queue: Hidehiko Abe <hidehiko@chromium.org>
Cr-Commit-Position: refs/heads/master@{#538699}
CrOS-Libchrome-Original-Commit: 40ee4aee34e39b711f266bbe3e03f4c033b9cf71
diff --git a/base/optional.h b/base/optional.h
index 9599baf..d65881b 100644
--- a/base/optional.h
+++ b/base/optional.h
@@ -239,37 +239,37 @@
~OptionalBase() = default;
OptionalBase& operator=(const OptionalBase& other) {
- if (!other.storage_.is_populated_) {
- FreeIfNeeded();
- return *this;
- }
-
- InitOrAssign(other.storage_.value_);
+ CopyAssign(other);
return *this;
}
OptionalBase& operator=(OptionalBase&& other) {
- if (!other.storage_.is_populated_) {
- FreeIfNeeded();
- return *this;
- }
-
- InitOrAssign(std::move(other.storage_.value_));
+ MoveAssign(std::move(other));
return *this;
}
- void InitOrAssign(const T& value) {
- if (!storage_.is_populated_)
- storage_.Init(value);
+ template <typename U>
+ void CopyAssign(const OptionalBase<U>& other) {
+ if (other.storage_.is_populated_)
+ InitOrAssign(other.storage_.value_);
else
- storage_.value_ = value;
+ FreeIfNeeded();
}
- void InitOrAssign(T&& value) {
- if (!storage_.is_populated_)
- storage_.Init(std::move(value));
+ template <typename U>
+ void MoveAssign(OptionalBase<U>&& other) {
+ if (other.storage_.is_populated_)
+ InitOrAssign(std::move(other.storage_.value_));
else
- storage_.value_ = std::move(value);
+ FreeIfNeeded();
+ }
+
+ template <typename U>
+ void InitOrAssign(U&& value) {
+ if (storage_.is_populated_)
+ storage_.value_ = std::forward<U>(value);
+ else
+ storage_.Init(std::forward<U>(value));
}
void FreeIfNeeded() {
@@ -339,7 +339,7 @@
MoveAssignable& operator=(MoveAssignable&&) = delete;
};
-// Helper to conditionally enable converting constructors.
+// Helper to conditionally enable converting constructors and assign operators.
template <typename T, typename U>
struct IsConvertibleFromOptional
: std::integral_constant<
@@ -353,6 +353,16 @@
std::is_convertible<Optional<U>&&, T>::value ||
std::is_convertible<const Optional<U>&&, T>::value> {};
+template <typename T, typename U>
+struct IsAssignableFromOptional
+ : std::integral_constant<
+ bool,
+ IsConvertibleFromOptional<T, U>::value ||
+ std::is_assignable<T&, Optional<U>&>::value ||
+ std::is_assignable<T&, const Optional<U>&>::value ||
+ std::is_assignable<T&, Optional<U>&&>::value ||
+ std::is_assignable<T&, const Optional<U>&&>::value> {};
+
// Forward compatibility for C++20.
template <typename T>
using RemoveCvRefT = std::remove_cv_t<std::remove_reference_t<T>>;
@@ -486,14 +496,42 @@
return *this;
}
- template <class U>
- typename std::enable_if<std::is_same<std::decay_t<U>, T>::value,
- Optional&>::type
+ // Perfect-forwarded assignment.
+ template <typename U>
+ std::enable_if_t<
+ !std::is_same<internal::RemoveCvRefT<U>, Optional<T>>::value &&
+ std::is_constructible<T, U>::value &&
+ std::is_assignable<T&, U>::value &&
+ (!std::is_scalar<T>::value ||
+ !std::is_same<std::decay_t<U>, T>::value),
+ Optional&>
operator=(U&& value) {
InitOrAssign(std::forward<U>(value));
return *this;
}
+ // Copy assign the state of other.
+ template <typename U>
+ std::enable_if_t<!internal::IsAssignableFromOptional<T, U>::value &&
+ std::is_constructible<T, const U&>::value &&
+ std::is_assignable<T&, const U&>::value,
+ Optional&>
+ operator=(const Optional<U>& other) {
+ CopyAssign(other);
+ return *this;
+ }
+
+ // Move assign the state of other.
+ template <typename U>
+ std::enable_if_t<!internal::IsAssignableFromOptional<T, U>::value &&
+ std::is_constructible<T, U>::value &&
+ std::is_assignable<T&, U>::value,
+ Optional&>
+ operator=(Optional<U>&& other) {
+ MoveAssign(std::move(other));
+ return *this;
+ }
+
constexpr const T* operator->() const {
DCHECK(storage_.is_populated_);
return &value();
@@ -605,8 +643,10 @@
private:
// Accessing template base class's protected member needs explicit
// declaration to do so.
+ using internal::OptionalBase<T>::CopyAssign;
using internal::OptionalBase<T>::FreeIfNeeded;
using internal::OptionalBase<T>::InitOrAssign;
+ using internal::OptionalBase<T>::MoveAssign;
using internal::OptionalBase<T>::storage_;
};
diff --git a/base/optional_unittest.cc b/base/optional_unittest.cc
index b098c0e..17c9ee4 100644
--- a/base/optional_unittest.cc
+++ b/base/optional_unittest.cc
@@ -649,6 +649,37 @@
EXPECT_TRUE(!!b);
EXPECT_EQ(a->foo(), b->foo());
}
+
+ // Converting assignment.
+ {
+ Optional<int> a(in_place, 1);
+ Optional<double> b;
+ b = a;
+
+ EXPECT_TRUE(!!a);
+ EXPECT_TRUE(!!b);
+ EXPECT_EQ(1, a.value());
+ EXPECT_EQ(1.0, b.value());
+ }
+
+ {
+ Optional<int> a(in_place, 42);
+ Optional<double> b(in_place, 1);
+ b = a;
+
+ EXPECT_TRUE(!!a);
+ EXPECT_TRUE(!!b);
+ EXPECT_EQ(42, a.value());
+ EXPECT_EQ(42.0, b.value());
+ }
+
+ {
+ Optional<int> a;
+ Optional<double> b(in_place, 1);
+ b = a;
+ EXPECT_FALSE(!!a);
+ EXPECT_FALSE(!!b);
+ }
}
TEST(OptionalTest, AssignObject_rvalue) {
@@ -717,6 +748,36 @@
EXPECT_TRUE(!!b);
EXPECT_EQ(42, b->foo());
}
+
+ // Converting assignment.
+ {
+ Optional<int> a(in_place, 1);
+ Optional<double> b;
+ b = std::move(a);
+
+ EXPECT_TRUE(!!a);
+ EXPECT_TRUE(!!b);
+ EXPECT_EQ(1.0, b.value());
+ }
+
+ {
+ Optional<int> a(in_place, 42);
+ Optional<double> b(in_place, 1);
+ b = std::move(a);
+
+ EXPECT_TRUE(!!a);
+ EXPECT_TRUE(!!b);
+ EXPECT_EQ(42.0, b.value());
+ }
+
+ {
+ Optional<int> a;
+ Optional<double> b(in_place, 1);
+ b = std::move(a);
+
+ EXPECT_FALSE(!!a);
+ EXPECT_FALSE(!!b);
+ }
}
TEST(OptionalTest, AssignNull) {
@@ -745,6 +806,190 @@
}
}
+TEST(OptionalTest, AssignOverload) {
+ struct Test1 {
+ enum class State {
+ CONSTRUCTED,
+ MOVED,
+ };
+ State state = State::CONSTRUCTED;
+ };
+
+ // Here, Optional<Test2> can be assigned from Optioanl<Test1>.
+ // In case of move, marks MOVED to Test1 instance.
+ struct Test2 {
+ enum class State {
+ DEFAULT_CONSTRUCTED,
+ COPY_CONSTRUCTED_FROM_TEST1,
+ MOVE_CONSTRUCTED_FROM_TEST1,
+ COPY_ASSIGNED_FROM_TEST1,
+ MOVE_ASSIGNED_FROM_TEST1,
+ };
+
+ Test2() = default;
+ explicit Test2(const Test1& test1)
+ : state(State::COPY_CONSTRUCTED_FROM_TEST1) {}
+ explicit Test2(Test1&& test1) : state(State::MOVE_CONSTRUCTED_FROM_TEST1) {
+ test1.state = Test1::State::MOVED;
+ }
+ Test2& operator=(const Test1& test1) {
+ state = State::COPY_ASSIGNED_FROM_TEST1;
+ return *this;
+ }
+ Test2& operator=(Test1&& test1) {
+ state = State::MOVE_ASSIGNED_FROM_TEST1;
+ test1.state = Test1::State::MOVED;
+ return *this;
+ }
+
+ State state = State::DEFAULT_CONSTRUCTED;
+ };
+
+ {
+ Optional<Test1> a(in_place);
+ Optional<Test2> b;
+
+ b = a;
+ EXPECT_TRUE(!!a);
+ EXPECT_TRUE(!!b);
+ EXPECT_EQ(Test1::State::CONSTRUCTED, a->state);
+ EXPECT_EQ(Test2::State::COPY_CONSTRUCTED_FROM_TEST1, b->state);
+ }
+
+ {
+ Optional<Test1> a(in_place);
+ Optional<Test2> b(in_place);
+
+ b = a;
+ EXPECT_TRUE(!!a);
+ EXPECT_TRUE(!!b);
+ EXPECT_EQ(Test1::State::CONSTRUCTED, a->state);
+ EXPECT_EQ(Test2::State::COPY_ASSIGNED_FROM_TEST1, b->state);
+ }
+
+ {
+ Optional<Test1> a(in_place);
+ Optional<Test2> b;
+
+ b = std::move(a);
+ EXPECT_TRUE(!!a);
+ EXPECT_TRUE(!!b);
+ EXPECT_EQ(Test1::State::MOVED, a->state);
+ EXPECT_EQ(Test2::State::MOVE_CONSTRUCTED_FROM_TEST1, b->state);
+ }
+
+ {
+ Optional<Test1> a(in_place);
+ Optional<Test2> b(in_place);
+
+ b = std::move(a);
+ EXPECT_TRUE(!!a);
+ EXPECT_TRUE(!!b);
+ EXPECT_EQ(Test1::State::MOVED, a->state);
+ EXPECT_EQ(Test2::State::MOVE_ASSIGNED_FROM_TEST1, b->state);
+ }
+
+ // Similar to Test2, but Test3 also has copy/move ctor and assign operators
+ // from Optional<Test1>, too. In this case, for a = b where a is
+ // Optional<Test3> and b is Optional<Test1>,
+ // Optional<T>::operator=(U&&) where U is Optional<Test1> should be used
+ // rather than Optional<T>::operator=(Optional<U>&&) where U is Test1.
+ struct Test3 {
+ enum class State {
+ DEFAULT_CONSTRUCTED,
+ COPY_CONSTRUCTED_FROM_TEST1,
+ MOVE_CONSTRUCTED_FROM_TEST1,
+ COPY_CONSTRUCTED_FROM_OPTIONAL_TEST1,
+ MOVE_CONSTRUCTED_FROM_OPTIONAL_TEST1,
+ COPY_ASSIGNED_FROM_TEST1,
+ MOVE_ASSIGNED_FROM_TEST1,
+ COPY_ASSIGNED_FROM_OPTIONAL_TEST1,
+ MOVE_ASSIGNED_FROM_OPTIONAL_TEST1,
+ };
+
+ Test3() = default;
+ explicit Test3(const Test1& test1)
+ : state(State::COPY_CONSTRUCTED_FROM_TEST1) {}
+ explicit Test3(Test1&& test1) : state(State::MOVE_CONSTRUCTED_FROM_TEST1) {
+ test1.state = Test1::State::MOVED;
+ }
+ explicit Test3(const Optional<Test1>& test1)
+ : state(State::COPY_CONSTRUCTED_FROM_OPTIONAL_TEST1) {}
+ explicit Test3(Optional<Test1>&& test1)
+ : state(State::MOVE_CONSTRUCTED_FROM_OPTIONAL_TEST1) {
+ // In the following senarios, given |test1| should always have value.
+ DCHECK(test1.has_value());
+ test1->state = Test1::State::MOVED;
+ }
+ Test3& operator=(const Test1& test1) {
+ state = State::COPY_ASSIGNED_FROM_TEST1;
+ return *this;
+ }
+ Test3& operator=(Test1&& test1) {
+ state = State::MOVE_ASSIGNED_FROM_TEST1;
+ test1.state = Test1::State::MOVED;
+ return *this;
+ }
+ Test3& operator=(const Optional<Test1>& test1) {
+ state = State::COPY_ASSIGNED_FROM_OPTIONAL_TEST1;
+ return *this;
+ }
+ Test3& operator=(Optional<Test1>&& test1) {
+ state = State::MOVE_ASSIGNED_FROM_OPTIONAL_TEST1;
+ // In the following senarios, given |test1| should always have value.
+ DCHECK(test1.has_value());
+ test1->state = Test1::State::MOVED;
+ return *this;
+ }
+
+ State state = State::DEFAULT_CONSTRUCTED;
+ };
+
+ {
+ Optional<Test1> a(in_place);
+ Optional<Test3> b;
+
+ b = a;
+ EXPECT_TRUE(!!a);
+ EXPECT_TRUE(!!b);
+ EXPECT_EQ(Test1::State::CONSTRUCTED, a->state);
+ EXPECT_EQ(Test3::State::COPY_CONSTRUCTED_FROM_OPTIONAL_TEST1, b->state);
+ }
+
+ {
+ Optional<Test1> a(in_place);
+ Optional<Test3> b(in_place);
+
+ b = a;
+ EXPECT_TRUE(!!a);
+ EXPECT_TRUE(!!b);
+ EXPECT_EQ(Test1::State::CONSTRUCTED, a->state);
+ EXPECT_EQ(Test3::State::COPY_ASSIGNED_FROM_OPTIONAL_TEST1, b->state);
+ }
+
+ {
+ Optional<Test1> a(in_place);
+ Optional<Test3> b;
+
+ b = std::move(a);
+ EXPECT_TRUE(!!a);
+ EXPECT_TRUE(!!b);
+ EXPECT_EQ(Test1::State::MOVED, a->state);
+ EXPECT_EQ(Test3::State::MOVE_CONSTRUCTED_FROM_OPTIONAL_TEST1, b->state);
+ }
+
+ {
+ Optional<Test1> a(in_place);
+ Optional<Test3> b(in_place);
+
+ b = std::move(a);
+ EXPECT_TRUE(!!a);
+ EXPECT_TRUE(!!b);
+ EXPECT_EQ(Test1::State::MOVED, a->state);
+ EXPECT_EQ(Test3::State::MOVE_ASSIGNED_FROM_OPTIONAL_TEST1, b->state);
+ }
+}
+
TEST(OptionalTest, OperatorStar) {
{
Optional<float> a(0.1f);