Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

<expected>: Make copy/move assignment operators of expected propagate triviality #4271

Merged
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
62 changes: 54 additions & 8 deletions stl/inc/expected
Original file line number Diff line number Diff line change
Expand Up @@ -182,6 +182,18 @@ struct _Check_expected_argument : true_type {
"T must not be a (possibly cv-qualified) specialization of unexpected. (N4950 [expected.object.general]/2)");
};

template <class _Ty, class _Err>
concept _Expected_binary_copy_assignable =
is_copy_assignable_v<_Ty> && is_copy_constructible_v<_Ty> //
&& is_copy_assignable_v<_Err> && is_copy_constructible_v<_Err>
&& (is_nothrow_move_constructible_v<_Ty> || is_nothrow_move_constructible_v<_Err>);

template <class _Ty, class _Err>
concept _Expected_binary_move_assignable =
is_move_assignable_v<_Ty> && is_move_constructible_v<_Ty> //
&& is_move_assignable_v<_Err> && is_move_constructible_v<_Err>
&& (is_nothrow_move_constructible_v<_Ty> || is_nothrow_move_constructible_v<_Err>);

_EXPORT_STD template <class _Ty, class _Err>
class expected {
private:
Expand Down Expand Up @@ -390,9 +402,7 @@ public:
constexpr expected& operator=(const expected& _Other) noexcept(
is_nothrow_copy_constructible_v<_Ty> && is_nothrow_copy_constructible_v<_Err>
&& is_nothrow_copy_assignable_v<_Ty> && is_nothrow_copy_assignable_v<_Err>) // strengthened
requires is_copy_assignable_v<_Ty> && is_copy_constructible_v<_Ty> //
&& is_copy_assignable_v<_Err> && is_copy_constructible_v<_Err>
&& (is_nothrow_move_constructible_v<_Ty> || is_nothrow_move_constructible_v<_Err>)
requires _Expected_binary_copy_assignable<_Ty, _Err>
{
if (_Has_value && _Other._Has_value) {
_Value = _Other._Value;
Expand All @@ -408,12 +418,19 @@ public:
return *this;
}

expected& operator=(const expected&)
requires _Expected_binary_copy_assignable<_Ty, _Err> //
&& is_trivially_copy_constructible_v<_Ty> && is_trivially_copy_assignable_v<_Ty>
&& is_trivially_destructible_v<_Ty>
// clang-format off
&& is_trivially_copy_constructible_v<_Err> && is_trivially_copy_assignable_v<_Err>
&& is_trivially_destructible_v<_Err> = default;
// clang-format on

constexpr expected& operator=(expected&& _Other) noexcept(
is_nothrow_move_constructible_v<_Ty> && is_nothrow_move_constructible_v<_Err>
&& is_nothrow_move_assignable_v<_Ty> && is_nothrow_move_assignable_v<_Err>)
requires is_move_assignable_v<_Ty> && is_move_constructible_v<_Ty> //
&& is_move_assignable_v<_Err> && is_move_constructible_v<_Err>
&& (is_nothrow_move_constructible_v<_Ty> || is_nothrow_move_constructible_v<_Err>)
requires _Expected_binary_move_assignable<_Ty, _Err>
{
if (_Has_value && _Other._Has_value) {
_Value = _STD move(_Other._Value);
Expand All @@ -429,6 +446,15 @@ public:
return *this;
}

expected& operator=(expected&&)
requires _Expected_binary_move_assignable<_Ty, _Err> //
&& is_trivially_move_constructible_v<_Ty> && is_trivially_move_assignable_v<_Ty>
&& is_trivially_destructible_v<_Ty>
// clang-format off
&& is_trivially_move_constructible_v<_Err> && is_trivially_move_assignable_v<_Err>
&& is_trivially_destructible_v<_Err> = default;
// clang-format on

template <class _Uty = _Ty>
requires (!is_same_v<remove_cvref_t<_Uty>, expected> && !_Is_specialization_v<remove_cvref_t<_Uty>, unexpected>
&& is_constructible_v<_Ty, _Uty> && is_assignable_v<_Ty&, _Uty>
Expand Down Expand Up @@ -1162,6 +1188,12 @@ private:
bool _Has_value;
};

template <class _Err>
concept _Expected_unary_copy_assignable = is_copy_assignable_v<_Err> && is_copy_constructible_v<_Err>;

template <class _Err>
concept _Expected_unary_move_assignable = is_move_assignable_v<_Err> && is_move_constructible_v<_Err>;

template <class _Ty, class _Err>
requires is_void_v<_Ty>
class expected<_Ty, _Err> {
Expand Down Expand Up @@ -1278,7 +1310,7 @@ public:
// [expected.void.assign]
constexpr expected& operator=(const expected& _Other) noexcept(
is_nothrow_copy_constructible_v<_Err> && is_nothrow_copy_assignable_v<_Err>) // strengthened
requires is_copy_assignable_v<_Err> && is_copy_constructible_v<_Err>
requires _Expected_unary_copy_assignable<_Err>
{
if (_Has_value && _Other._Has_value) {
// nothing to do
Expand All @@ -1297,9 +1329,16 @@ public:
return *this;
}

expected& operator=(const expected&)
requires _Expected_unary_copy_assignable<_Err>
// clang-format off
&& is_trivially_copy_constructible_v<_Err> && is_trivially_copy_assignable_v<_Err>
&& is_trivially_destructible_v<_Err> = default;
// clang-format on

constexpr expected& operator=(expected&& _Other) noexcept(
is_nothrow_move_constructible_v<_Err> && is_nothrow_move_assignable_v<_Err>)
requires is_move_assignable_v<_Err> && is_move_constructible_v<_Err>
requires _Expected_unary_move_assignable<_Err>
{
if (_Has_value && _Other._Has_value) {
// nothing to do
Expand All @@ -1318,6 +1357,13 @@ public:
return *this;
}

expected& operator=(expected&&)
requires _Expected_unary_move_assignable<_Err>
// clang-format off
&& is_trivially_move_constructible_v<_Err> && is_trivially_move_assignable_v<_Err>
&& is_trivially_destructible_v<_Err> = default;
// clang-format on

template <class _UErr>
requires is_constructible_v<_Err, const _UErr&> && is_assignable_v<_Err&, const _UErr&>
constexpr expected& operator=(const unexpected<_UErr>& _Other) noexcept(
Expand Down
174 changes: 174 additions & 0 deletions tests/std/tests/P0323R12_expected/test.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@ using namespace std;
enum class IsDefaultConstructible : bool { Not, Yes };
enum class IsTriviallyCopyConstructible : bool { Not, Yes };
enum class IsTriviallyMoveConstructible : bool { Not, Yes };
enum class IsTriviallyCopyAssignable : bool { Not, Yes };
enum class IsTriviallyMoveAssignable : bool { Not, Yes };
enum class IsTriviallyDestructible : bool { Not, Yes };

enum class IsNothrowConstructible : bool { Not, Yes };
Expand Down Expand Up @@ -1186,6 +1188,177 @@ namespace test_expected {
test_assignment<NCC::Yes, NMC::Yes, NCA::Yes, NMA::Yes>();
}

// per LWG-4026, see also LLVM-74768
template <class PODType, IsTriviallyCopyConstructible CopyCtorTriviality,
IsTriviallyMoveConstructible MoveCtorTriviality, IsTriviallyCopyAssignable CopyAssignTriviality,
IsTriviallyMoveAssignable MoveAssignTriviality, IsTriviallyDestructible DtorTriviality>
struct TrivialityTester {
PODType val{};

TrivialityTester() = default;

constexpr explicit TrivialityTester(PODType v) noexcept : val{v} {}

constexpr TrivialityTester(const TrivialityTester& other) noexcept : val{other.val} {}
constexpr TrivialityTester(const TrivialityTester&)
requires (CopyCtorTriviality == IsTriviallyCopyConstructible::Yes)
= default;

constexpr TrivialityTester(TrivialityTester&& other) noexcept : val{other.val} {}
TrivialityTester(TrivialityTester&&)
requires (MoveCtorTriviality == IsTriviallyMoveConstructible::Yes)
= default;

constexpr TrivialityTester& operator=(const TrivialityTester& other) noexcept {
val = other.val;
return *this;
}
TrivialityTester& operator=(const TrivialityTester&)
requires (CopyAssignTriviality == IsTriviallyCopyAssignable::Yes)
= default;

constexpr TrivialityTester& operator=(TrivialityTester&& other) noexcept {
val = other.val;
return *this;
}
TrivialityTester& operator=(TrivialityTester&&)
requires (MoveAssignTriviality == IsTriviallyMoveAssignable::Yes)
= default;

constexpr ~TrivialityTester() {}
~TrivialityTester()
requires (DtorTriviality == IsTriviallyDestructible::Yes)
= default;
};

template <class Val1, IsTriviallyCopyConstructible CopyCtorTriviality,
IsTriviallyMoveConstructible MoveCtorTriviality, IsTriviallyCopyAssignable CopyAssignTriviality,
IsTriviallyMoveAssignable MoveAssignTriviality, IsTriviallyDestructible DtorTriviality>
constexpr void test_triviality_of_assignment_binary() {
using Val2 = TrivialityTester<char, CopyCtorTriviality, MoveCtorTriviality, CopyAssignTriviality,
MoveAssignTriviality, DtorTriviality>;
using E = expected<Val1, Val2>;

static_assert(is_trivially_copy_assignable_v<E>
== (is_trivially_copy_constructible_v<Val1> && is_trivially_copy_assignable_v<Val1>
&& is_trivially_destructible_v<Val1> && is_trivially_copy_constructible_v<Val2>
&& is_trivially_copy_assignable_v<Val2> && is_trivially_destructible_v<Val2>) );
static_assert(is_trivially_move_assignable_v<E>
== (is_trivially_move_constructible_v<Val1> && is_trivially_move_assignable_v<Val1>
&& is_trivially_destructible_v<Val1> && is_trivially_move_constructible_v<Val2>
&& is_trivially_move_assignable_v<Val2> && is_trivially_destructible_v<Val2>) );

{
E e1{Val1{42}};
E e2{unexpect, Val2{'^'}};
e1 = e2;
assert(!e1.has_value());
assert(e1.error().val == '^');
}
{
E e1{Val1{42}};
E e2{unexpect, Val2{'^'}};
e1 = move(e2);
assert(!e1.has_value());
assert(e1.error().val == '^');
}
{
E e1{Val1{42}};
E e2{unexpect, Val2{'^'}};
e2 = e1;
assert(e2.has_value());
assert(e2.value().val == 42);
}
{
E e1{Val1{42}};
E e2{unexpect, Val2{'^'}};
e2 = move(e1);
assert(e2.has_value());
assert(e2.value().val == 42);
}
}

template <IsTriviallyCopyConstructible CopyCtorTriviality, IsTriviallyMoveConstructible MoveCtorTriviality,
IsTriviallyCopyAssignable CopyAssignTriviality, IsTriviallyMoveAssignable MoveAssignTriviality,
IsTriviallyDestructible DtorTriviality>
constexpr void test_triviality_of_assignment() {
using Val = TrivialityTester<int, CopyCtorTriviality, MoveCtorTriviality, CopyAssignTriviality,
MoveAssignTriviality, DtorTriviality>;
using E = expected<void, Val>;

static_assert(is_trivially_copy_assignable_v<E>
== (is_trivially_copy_constructible_v<Val> && is_trivially_copy_assignable_v<Val>
&& is_trivially_destructible_v<Val>) );
static_assert(is_trivially_move_assignable_v<E>
== (is_trivially_move_constructible_v<Val> && is_trivially_move_assignable_v<Val>
&& is_trivially_destructible_v<Val>) );

{
E e1{};
E e2{unexpect, Val{42}};
e1 = e2;
assert(!e1.has_value());
assert(e1.error().val == 42);
}
{
E e1{};
E e2{unexpect, Val{42}};
e1 = move(e2);
assert(!e1.has_value());
assert(e1.error().val == 42);
}

// Test trivially copyable cases.
test_triviality_of_assignment_binary<Val, IsTriviallyCopyConstructible::Yes, IsTriviallyMoveConstructible::Yes,
IsTriviallyCopyAssignable::Yes, IsTriviallyMoveAssignable::Yes, IsTriviallyDestructible::Yes>();

// Test non-trivial copy constructors.
test_triviality_of_assignment_binary<Val, IsTriviallyCopyConstructible::Not, IsTriviallyMoveConstructible::Yes,
IsTriviallyCopyAssignable::Yes, IsTriviallyMoveAssignable::Yes, IsTriviallyDestructible::Yes>();

// Test non-trivial move constructors.
test_triviality_of_assignment_binary<Val, IsTriviallyCopyConstructible::Yes, IsTriviallyMoveConstructible::Not,
IsTriviallyCopyAssignable::Yes, IsTriviallyMoveAssignable::Yes, IsTriviallyDestructible::Yes>();

// Test non-trivial copy assignment operators.
test_triviality_of_assignment_binary<Val, IsTriviallyCopyConstructible::Yes, IsTriviallyMoveConstructible::Yes,
IsTriviallyCopyAssignable::Not, IsTriviallyMoveAssignable::Yes, IsTriviallyDestructible::Yes>();

// Test non-trivial move assignment operators.
test_triviality_of_assignment_binary<Val, IsTriviallyCopyConstructible::Yes, IsTriviallyMoveConstructible::Yes,
IsTriviallyCopyAssignable::Yes, IsTriviallyMoveAssignable::Not, IsTriviallyDestructible::Yes>();

// Test non-trivial destructors.
test_triviality_of_assignment_binary<Val, IsTriviallyCopyConstructible::Yes, IsTriviallyMoveConstructible::Yes,
IsTriviallyCopyAssignable::Yes, IsTriviallyMoveAssignable::Yes, IsTriviallyDestructible::Not>();
}

constexpr void test_triviality_of_assignment_all() {
// Test trivially copyable cases.
test_triviality_of_assignment<IsTriviallyCopyConstructible::Yes, IsTriviallyMoveConstructible::Yes,
IsTriviallyCopyAssignable::Yes, IsTriviallyMoveAssignable::Yes, IsTriviallyDestructible::Yes>();

// Test non-trivial copy constructors.
test_triviality_of_assignment<IsTriviallyCopyConstructible::Not, IsTriviallyMoveConstructible::Yes,
IsTriviallyCopyAssignable::Yes, IsTriviallyMoveAssignable::Yes, IsTriviallyDestructible::Yes>();

// Test non-trivial move constructors.
test_triviality_of_assignment<IsTriviallyCopyConstructible::Yes, IsTriviallyMoveConstructible::Not,
IsTriviallyCopyAssignable::Yes, IsTriviallyMoveAssignable::Yes, IsTriviallyDestructible::Yes>();

// Test non-trivial copy assignment operators.
test_triviality_of_assignment<IsTriviallyCopyConstructible::Yes, IsTriviallyMoveConstructible::Yes,
IsTriviallyCopyAssignable::Not, IsTriviallyMoveAssignable::Yes, IsTriviallyDestructible::Yes>();

// Test non-trivial move assignment operators.
test_triviality_of_assignment<IsTriviallyCopyConstructible::Yes, IsTriviallyMoveConstructible::Yes,
IsTriviallyCopyAssignable::Yes, IsTriviallyMoveAssignable::Not, IsTriviallyDestructible::Yes>();

// Test non-trivial destructors.
test_triviality_of_assignment<IsTriviallyCopyConstructible::Yes, IsTriviallyMoveConstructible::Yes,
IsTriviallyCopyAssignable::Yes, IsTriviallyMoveAssignable::Yes, IsTriviallyDestructible::Not>();
}

constexpr void test_emplace() noexcept {
struct payload_emplace {
constexpr payload_emplace(bool& destructor_called) noexcept : _destructor_called(destructor_called) {}
Expand Down Expand Up @@ -2020,6 +2193,7 @@ namespace test_expected {
test_special_members();
test_constructors();
test_assignment();
test_triviality_of_assignment_all(); // per LWG-4026, see also LLVM-74768
test_emplace();
test_swap();
test_access();
Expand Down