Skip to content

Commit

Permalink
<expected>: Make copy/move assignment operators of expected propa…
Browse files Browse the repository at this point in the history
…gate triviality (#4271)

Co-authored-by: Stephan T. Lavavej <stl@nuwen.net>
  • Loading branch information
frederick-vs-ja and StephanTLavavej authored Jan 25, 2024
1 parent 63aeb77 commit 065e5ff
Show file tree
Hide file tree
Showing 2 changed files with 219 additions and 8 deletions.
60 changes: 52 additions & 8 deletions stl/inc/expected
Original file line number Diff line number Diff line change
Expand Up @@ -182,6 +182,28 @@ 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>);

template <class _Type> // used with both _Ty and _Err
concept _Trivially_copy_constructible_assignable_destructible =
is_trivially_copy_constructible_v<_Type> && is_trivially_copy_assignable_v<_Type>
&& is_trivially_destructible_v<_Type>;

template <class _Type> // used with both _Ty and _Err
concept _Trivially_move_constructible_assignable_destructible =
is_trivially_move_constructible_v<_Type> && is_trivially_move_assignable_v<_Type>
&& is_trivially_destructible_v<_Type>;

_EXPORT_STD template <class _Ty, class _Err>
class expected {
private:
Expand Down Expand Up @@ -390,9 +412,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 +428,16 @@ public:
return *this;
}

expected& operator=(const expected&)
requires _Expected_binary_copy_assignable<_Ty, _Err>
&& _Trivially_copy_constructible_assignable_destructible<_Ty>
&& _Trivially_copy_constructible_assignable_destructible<_Err>
= default;

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 +453,12 @@ public:
return *this;
}

expected& operator=(expected&&)
requires _Expected_binary_move_assignable<_Ty, _Err>
&& _Trivially_move_constructible_assignable_destructible<_Ty>
&& _Trivially_move_constructible_assignable_destructible<_Err>
= default;

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 +1192,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 +1314,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 +1333,13 @@ public:
return *this;
}

expected& operator=(const expected&)
requires _Expected_unary_copy_assignable<_Err> && _Trivially_copy_constructible_assignable_destructible<_Err>
= default;

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 +1358,10 @@ public:
return *this;
}

expected& operator=(expected&&)
requires _Expected_unary_move_assignable<_Err> && _Trivially_move_constructible_assignable_destructible<_Err>
= default;

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
167 changes: 167 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,170 @@ namespace test_expected {
test_assignment<NCC::Yes, NMC::Yes, NCA::Yes, NMA::Yes>();
}

// Only test the triviality scenarios that occur in practice.
template <IsTriviallyCopyConstructible CC, IsTriviallyMoveConstructible MC, IsTriviallyCopyAssignable CA,
IsTriviallyMoveAssignable MA, IsTriviallyDestructible D>
struct TrivialityScenario {
static constexpr auto CopyCtorTriviality = CC;
static constexpr auto MoveCtorTriviality = MC;
static constexpr auto CopyAssignTriviality = CA;
static constexpr auto MoveAssignTriviality = MA;
static constexpr auto DtorTriviality = D;
};

// No operations are trivial.
using TrivialityScenario1 = TrivialityScenario<IsTriviallyCopyConstructible::Not, IsTriviallyMoveConstructible::Not,
IsTriviallyCopyAssignable::Not, IsTriviallyMoveAssignable::Not, IsTriviallyDestructible::Not>;

// Only destruction is trivial.
using TrivialityScenario2 = TrivialityScenario<IsTriviallyCopyConstructible::Not, IsTriviallyMoveConstructible::Not,
IsTriviallyCopyAssignable::Not, IsTriviallyMoveAssignable::Not, IsTriviallyDestructible::Yes>;

// Only destruction and move construction are trivial.
using TrivialityScenario3 = TrivialityScenario<IsTriviallyCopyConstructible::Not, IsTriviallyMoveConstructible::Yes,
IsTriviallyCopyAssignable::Not, IsTriviallyMoveAssignable::Not, IsTriviallyDestructible::Yes>;

// Only destruction and move construction/assignment are trivial.
using TrivialityScenario4 = TrivialityScenario<IsTriviallyCopyConstructible::Not, IsTriviallyMoveConstructible::Yes,
IsTriviallyCopyAssignable::Not, IsTriviallyMoveAssignable::Yes, IsTriviallyDestructible::Yes>;

// Only destruction and copy/move construction are trivial.
using TrivialityScenario5 = TrivialityScenario<IsTriviallyCopyConstructible::Yes, IsTriviallyMoveConstructible::Yes,
IsTriviallyCopyAssignable::Not, IsTriviallyMoveAssignable::Not, IsTriviallyDestructible::Yes>;

// All operations are trivial.
using TrivialityScenario6 = TrivialityScenario<IsTriviallyCopyConstructible::Yes, IsTriviallyMoveConstructible::Yes,
IsTriviallyCopyAssignable::Yes, IsTriviallyMoveAssignable::Yes, IsTriviallyDestructible::Yes>;

// per LWG-4026, see also LLVM-74768
template <class PODType, class Scenario>
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 (Scenario::CopyCtorTriviality == IsTriviallyCopyConstructible::Yes)
= default;

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

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

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

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

template <class Val1, class OtherScenario>
constexpr void test_triviality_of_assignment_binary() {
using Val2 = TrivialityTester<char, OtherScenario>;
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 <class Scenario>
constexpr void test_triviality_of_assignment() {
using Val = TrivialityTester<int, Scenario>;
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_triviality_of_assignment_binary<Val, TrivialityScenario1>();
test_triviality_of_assignment_binary<Val, TrivialityScenario2>();
test_triviality_of_assignment_binary<Val, TrivialityScenario3>();
test_triviality_of_assignment_binary<Val, TrivialityScenario4>();
test_triviality_of_assignment_binary<Val, TrivialityScenario5>();
test_triviality_of_assignment_binary<Val, TrivialityScenario6>();
}

constexpr void test_triviality_of_assignment_all() {
test_triviality_of_assignment<TrivialityScenario1>();
test_triviality_of_assignment<TrivialityScenario2>();
test_triviality_of_assignment<TrivialityScenario3>();
test_triviality_of_assignment<TrivialityScenario4>();
test_triviality_of_assignment<TrivialityScenario5>();
test_triviality_of_assignment<TrivialityScenario6>();
}

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 +2186,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

0 comments on commit 065e5ff

Please sign in to comment.