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 all 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
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