Skip to content

Commit 065e5ff

Browse files
<expected>: Make copy/move assignment operators of expected propagate triviality (#4271)
Co-authored-by: Stephan T. Lavavej <stl@nuwen.net>
1 parent 63aeb77 commit 065e5ff

File tree

2 files changed

+219
-8
lines changed

2 files changed

+219
-8
lines changed

stl/inc/expected

+52-8
Original file line numberDiff line numberDiff line change
@@ -182,6 +182,28 @@ struct _Check_expected_argument : true_type {
182182
"T must not be a (possibly cv-qualified) specialization of unexpected. (N4950 [expected.object.general]/2)");
183183
};
184184

185+
template <class _Ty, class _Err>
186+
concept _Expected_binary_copy_assignable =
187+
is_copy_assignable_v<_Ty> && is_copy_constructible_v<_Ty> //
188+
&& is_copy_assignable_v<_Err> && is_copy_constructible_v<_Err>
189+
&& (is_nothrow_move_constructible_v<_Ty> || is_nothrow_move_constructible_v<_Err>);
190+
191+
template <class _Ty, class _Err>
192+
concept _Expected_binary_move_assignable =
193+
is_move_assignable_v<_Ty> && is_move_constructible_v<_Ty> //
194+
&& is_move_assignable_v<_Err> && is_move_constructible_v<_Err>
195+
&& (is_nothrow_move_constructible_v<_Ty> || is_nothrow_move_constructible_v<_Err>);
196+
197+
template <class _Type> // used with both _Ty and _Err
198+
concept _Trivially_copy_constructible_assignable_destructible =
199+
is_trivially_copy_constructible_v<_Type> && is_trivially_copy_assignable_v<_Type>
200+
&& is_trivially_destructible_v<_Type>;
201+
202+
template <class _Type> // used with both _Ty and _Err
203+
concept _Trivially_move_constructible_assignable_destructible =
204+
is_trivially_move_constructible_v<_Type> && is_trivially_move_assignable_v<_Type>
205+
&& is_trivially_destructible_v<_Type>;
206+
185207
_EXPORT_STD template <class _Ty, class _Err>
186208
class expected {
187209
private:
@@ -390,9 +412,7 @@ public:
390412
constexpr expected& operator=(const expected& _Other) noexcept(
391413
is_nothrow_copy_constructible_v<_Ty> && is_nothrow_copy_constructible_v<_Err>
392414
&& is_nothrow_copy_assignable_v<_Ty> && is_nothrow_copy_assignable_v<_Err>) // strengthened
393-
requires is_copy_assignable_v<_Ty> && is_copy_constructible_v<_Ty> //
394-
&& is_copy_assignable_v<_Err> && is_copy_constructible_v<_Err>
395-
&& (is_nothrow_move_constructible_v<_Ty> || is_nothrow_move_constructible_v<_Err>)
415+
requires _Expected_binary_copy_assignable<_Ty, _Err>
396416
{
397417
if (_Has_value && _Other._Has_value) {
398418
_Value = _Other._Value;
@@ -408,12 +428,16 @@ public:
408428
return *this;
409429
}
410430

431+
expected& operator=(const expected&)
432+
requires _Expected_binary_copy_assignable<_Ty, _Err>
433+
&& _Trivially_copy_constructible_assignable_destructible<_Ty>
434+
&& _Trivially_copy_constructible_assignable_destructible<_Err>
435+
= default;
436+
411437
constexpr expected& operator=(expected&& _Other) noexcept(
412438
is_nothrow_move_constructible_v<_Ty> && is_nothrow_move_constructible_v<_Err>
413439
&& is_nothrow_move_assignable_v<_Ty> && is_nothrow_move_assignable_v<_Err>)
414-
requires is_move_assignable_v<_Ty> && is_move_constructible_v<_Ty> //
415-
&& is_move_assignable_v<_Err> && is_move_constructible_v<_Err>
416-
&& (is_nothrow_move_constructible_v<_Ty> || is_nothrow_move_constructible_v<_Err>)
440+
requires _Expected_binary_move_assignable<_Ty, _Err>
417441
{
418442
if (_Has_value && _Other._Has_value) {
419443
_Value = _STD move(_Other._Value);
@@ -429,6 +453,12 @@ public:
429453
return *this;
430454
}
431455

456+
expected& operator=(expected&&)
457+
requires _Expected_binary_move_assignable<_Ty, _Err>
458+
&& _Trivially_move_constructible_assignable_destructible<_Ty>
459+
&& _Trivially_move_constructible_assignable_destructible<_Err>
460+
= default;
461+
432462
template <class _Uty = _Ty>
433463
requires (!is_same_v<remove_cvref_t<_Uty>, expected> && !_Is_specialization_v<remove_cvref_t<_Uty>, unexpected>
434464
&& is_constructible_v<_Ty, _Uty> && is_assignable_v<_Ty&, _Uty>
@@ -1162,6 +1192,12 @@ private:
11621192
bool _Has_value;
11631193
};
11641194

1195+
template <class _Err>
1196+
concept _Expected_unary_copy_assignable = is_copy_assignable_v<_Err> && is_copy_constructible_v<_Err>;
1197+
1198+
template <class _Err>
1199+
concept _Expected_unary_move_assignable = is_move_assignable_v<_Err> && is_move_constructible_v<_Err>;
1200+
11651201
template <class _Ty, class _Err>
11661202
requires is_void_v<_Ty>
11671203
class expected<_Ty, _Err> {
@@ -1278,7 +1314,7 @@ public:
12781314
// [expected.void.assign]
12791315
constexpr expected& operator=(const expected& _Other) noexcept(
12801316
is_nothrow_copy_constructible_v<_Err> && is_nothrow_copy_assignable_v<_Err>) // strengthened
1281-
requires is_copy_assignable_v<_Err> && is_copy_constructible_v<_Err>
1317+
requires _Expected_unary_copy_assignable<_Err>
12821318
{
12831319
if (_Has_value && _Other._Has_value) {
12841320
// nothing to do
@@ -1297,9 +1333,13 @@ public:
12971333
return *this;
12981334
}
12991335

1336+
expected& operator=(const expected&)
1337+
requires _Expected_unary_copy_assignable<_Err> && _Trivially_copy_constructible_assignable_destructible<_Err>
1338+
= default;
1339+
13001340
constexpr expected& operator=(expected&& _Other) noexcept(
13011341
is_nothrow_move_constructible_v<_Err> && is_nothrow_move_assignable_v<_Err>)
1302-
requires is_move_assignable_v<_Err> && is_move_constructible_v<_Err>
1342+
requires _Expected_unary_move_assignable<_Err>
13031343
{
13041344
if (_Has_value && _Other._Has_value) {
13051345
// nothing to do
@@ -1318,6 +1358,10 @@ public:
13181358
return *this;
13191359
}
13201360

1361+
expected& operator=(expected&&)
1362+
requires _Expected_unary_move_assignable<_Err> && _Trivially_move_constructible_assignable_destructible<_Err>
1363+
= default;
1364+
13211365
template <class _UErr>
13221366
requires is_constructible_v<_Err, const _UErr&> && is_assignable_v<_Err&, const _UErr&>
13231367
constexpr expected& operator=(const unexpected<_UErr>& _Other) noexcept(

tests/std/tests/P0323R12_expected/test.cpp

+167
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,8 @@ using namespace std;
1717
enum class IsDefaultConstructible : bool { Not, Yes };
1818
enum class IsTriviallyCopyConstructible : bool { Not, Yes };
1919
enum class IsTriviallyMoveConstructible : bool { Not, Yes };
20+
enum class IsTriviallyCopyAssignable : bool { Not, Yes };
21+
enum class IsTriviallyMoveAssignable : bool { Not, Yes };
2022
enum class IsTriviallyDestructible : bool { Not, Yes };
2123

2224
enum class IsNothrowConstructible : bool { Not, Yes };
@@ -1186,6 +1188,170 @@ namespace test_expected {
11861188
test_assignment<NCC::Yes, NMC::Yes, NCA::Yes, NMA::Yes>();
11871189
}
11881190

1191+
// Only test the triviality scenarios that occur in practice.
1192+
template <IsTriviallyCopyConstructible CC, IsTriviallyMoveConstructible MC, IsTriviallyCopyAssignable CA,
1193+
IsTriviallyMoveAssignable MA, IsTriviallyDestructible D>
1194+
struct TrivialityScenario {
1195+
static constexpr auto CopyCtorTriviality = CC;
1196+
static constexpr auto MoveCtorTriviality = MC;
1197+
static constexpr auto CopyAssignTriviality = CA;
1198+
static constexpr auto MoveAssignTriviality = MA;
1199+
static constexpr auto DtorTriviality = D;
1200+
};
1201+
1202+
// No operations are trivial.
1203+
using TrivialityScenario1 = TrivialityScenario<IsTriviallyCopyConstructible::Not, IsTriviallyMoveConstructible::Not,
1204+
IsTriviallyCopyAssignable::Not, IsTriviallyMoveAssignable::Not, IsTriviallyDestructible::Not>;
1205+
1206+
// Only destruction is trivial.
1207+
using TrivialityScenario2 = TrivialityScenario<IsTriviallyCopyConstructible::Not, IsTriviallyMoveConstructible::Not,
1208+
IsTriviallyCopyAssignable::Not, IsTriviallyMoveAssignable::Not, IsTriviallyDestructible::Yes>;
1209+
1210+
// Only destruction and move construction are trivial.
1211+
using TrivialityScenario3 = TrivialityScenario<IsTriviallyCopyConstructible::Not, IsTriviallyMoveConstructible::Yes,
1212+
IsTriviallyCopyAssignable::Not, IsTriviallyMoveAssignable::Not, IsTriviallyDestructible::Yes>;
1213+
1214+
// Only destruction and move construction/assignment are trivial.
1215+
using TrivialityScenario4 = TrivialityScenario<IsTriviallyCopyConstructible::Not, IsTriviallyMoveConstructible::Yes,
1216+
IsTriviallyCopyAssignable::Not, IsTriviallyMoveAssignable::Yes, IsTriviallyDestructible::Yes>;
1217+
1218+
// Only destruction and copy/move construction are trivial.
1219+
using TrivialityScenario5 = TrivialityScenario<IsTriviallyCopyConstructible::Yes, IsTriviallyMoveConstructible::Yes,
1220+
IsTriviallyCopyAssignable::Not, IsTriviallyMoveAssignable::Not, IsTriviallyDestructible::Yes>;
1221+
1222+
// All operations are trivial.
1223+
using TrivialityScenario6 = TrivialityScenario<IsTriviallyCopyConstructible::Yes, IsTriviallyMoveConstructible::Yes,
1224+
IsTriviallyCopyAssignable::Yes, IsTriviallyMoveAssignable::Yes, IsTriviallyDestructible::Yes>;
1225+
1226+
// per LWG-4026, see also LLVM-74768
1227+
template <class PODType, class Scenario>
1228+
struct TrivialityTester {
1229+
PODType val{};
1230+
1231+
TrivialityTester() = default;
1232+
1233+
constexpr explicit TrivialityTester(PODType v) noexcept : val{v} {}
1234+
1235+
constexpr TrivialityTester(const TrivialityTester& other) noexcept : val{other.val} {}
1236+
constexpr TrivialityTester(const TrivialityTester&)
1237+
requires (Scenario::CopyCtorTriviality == IsTriviallyCopyConstructible::Yes)
1238+
= default;
1239+
1240+
constexpr TrivialityTester(TrivialityTester&& other) noexcept : val{other.val} {}
1241+
TrivialityTester(TrivialityTester&&)
1242+
requires (Scenario::MoveCtorTriviality == IsTriviallyMoveConstructible::Yes)
1243+
= default;
1244+
1245+
constexpr TrivialityTester& operator=(const TrivialityTester& other) noexcept {
1246+
val = other.val;
1247+
return *this;
1248+
}
1249+
TrivialityTester& operator=(const TrivialityTester&)
1250+
requires (Scenario::CopyAssignTriviality == IsTriviallyCopyAssignable::Yes)
1251+
= default;
1252+
1253+
constexpr TrivialityTester& operator=(TrivialityTester&& other) noexcept {
1254+
val = other.val;
1255+
return *this;
1256+
}
1257+
TrivialityTester& operator=(TrivialityTester&&)
1258+
requires (Scenario::MoveAssignTriviality == IsTriviallyMoveAssignable::Yes)
1259+
= default;
1260+
1261+
constexpr ~TrivialityTester() {}
1262+
~TrivialityTester()
1263+
requires (Scenario::DtorTriviality == IsTriviallyDestructible::Yes)
1264+
= default;
1265+
};
1266+
1267+
template <class Val1, class OtherScenario>
1268+
constexpr void test_triviality_of_assignment_binary() {
1269+
using Val2 = TrivialityTester<char, OtherScenario>;
1270+
using E = expected<Val1, Val2>;
1271+
1272+
static_assert(is_trivially_copy_assignable_v<E>
1273+
== (is_trivially_copy_constructible_v<Val1> && is_trivially_copy_assignable_v<Val1>
1274+
&& is_trivially_destructible_v<Val1> && is_trivially_copy_constructible_v<Val2>
1275+
&& is_trivially_copy_assignable_v<Val2> && is_trivially_destructible_v<Val2>) );
1276+
static_assert(is_trivially_move_assignable_v<E>
1277+
== (is_trivially_move_constructible_v<Val1> && is_trivially_move_assignable_v<Val1>
1278+
&& is_trivially_destructible_v<Val1> && is_trivially_move_constructible_v<Val2>
1279+
&& is_trivially_move_assignable_v<Val2> && is_trivially_destructible_v<Val2>) );
1280+
1281+
{
1282+
E e1{Val1{42}};
1283+
E e2{unexpect, Val2{'^'}};
1284+
e1 = e2;
1285+
assert(!e1.has_value());
1286+
assert(e1.error().val == '^');
1287+
}
1288+
{
1289+
E e1{Val1{42}};
1290+
E e2{unexpect, Val2{'^'}};
1291+
e1 = move(e2);
1292+
assert(!e1.has_value());
1293+
assert(e1.error().val == '^');
1294+
}
1295+
{
1296+
E e1{Val1{42}};
1297+
E e2{unexpect, Val2{'^'}};
1298+
e2 = e1;
1299+
assert(e2.has_value());
1300+
assert(e2.value().val == 42);
1301+
}
1302+
{
1303+
E e1{Val1{42}};
1304+
E e2{unexpect, Val2{'^'}};
1305+
e2 = move(e1);
1306+
assert(e2.has_value());
1307+
assert(e2.value().val == 42);
1308+
}
1309+
}
1310+
1311+
template <class Scenario>
1312+
constexpr void test_triviality_of_assignment() {
1313+
using Val = TrivialityTester<int, Scenario>;
1314+
using E = expected<void, Val>;
1315+
1316+
static_assert(is_trivially_copy_assignable_v<E>
1317+
== (is_trivially_copy_constructible_v<Val> && is_trivially_copy_assignable_v<Val>
1318+
&& is_trivially_destructible_v<Val>) );
1319+
static_assert(is_trivially_move_assignable_v<E>
1320+
== (is_trivially_move_constructible_v<Val> && is_trivially_move_assignable_v<Val>
1321+
&& is_trivially_destructible_v<Val>) );
1322+
1323+
{
1324+
E e1{};
1325+
E e2{unexpect, Val{42}};
1326+
e1 = e2;
1327+
assert(!e1.has_value());
1328+
assert(e1.error().val == 42);
1329+
}
1330+
{
1331+
E e1{};
1332+
E e2{unexpect, Val{42}};
1333+
e1 = move(e2);
1334+
assert(!e1.has_value());
1335+
assert(e1.error().val == 42);
1336+
}
1337+
1338+
test_triviality_of_assignment_binary<Val, TrivialityScenario1>();
1339+
test_triviality_of_assignment_binary<Val, TrivialityScenario2>();
1340+
test_triviality_of_assignment_binary<Val, TrivialityScenario3>();
1341+
test_triviality_of_assignment_binary<Val, TrivialityScenario4>();
1342+
test_triviality_of_assignment_binary<Val, TrivialityScenario5>();
1343+
test_triviality_of_assignment_binary<Val, TrivialityScenario6>();
1344+
}
1345+
1346+
constexpr void test_triviality_of_assignment_all() {
1347+
test_triviality_of_assignment<TrivialityScenario1>();
1348+
test_triviality_of_assignment<TrivialityScenario2>();
1349+
test_triviality_of_assignment<TrivialityScenario3>();
1350+
test_triviality_of_assignment<TrivialityScenario4>();
1351+
test_triviality_of_assignment<TrivialityScenario5>();
1352+
test_triviality_of_assignment<TrivialityScenario6>();
1353+
}
1354+
11891355
constexpr void test_emplace() noexcept {
11901356
struct payload_emplace {
11911357
constexpr payload_emplace(bool& destructor_called) noexcept : _destructor_called(destructor_called) {}
@@ -2020,6 +2186,7 @@ namespace test_expected {
20202186
test_special_members();
20212187
test_constructors();
20222188
test_assignment();
2189+
test_triviality_of_assignment_all(); // per LWG-4026, see also LLVM-74768
20232190
test_emplace();
20242191
test_swap();
20252192
test_access();

0 commit comments

Comments
 (0)