CaseyCarter created this revision. NOTE: TEST CHANGES ONLY.
These tests will not pass with the current implementation of `variant`, but should give the implementor of LWG2904 a head start. Details: test/std/utilities/variant/variant.variant/variant.assign/copy.pass.cpp: - Make `CopyAssign`'s move operations noexcept so uses in variants continue to prefer copy-and-move over direct construction. - Fix `makeEmpty`: Copy assignment syntax no longer invokes `MakeEmptyT`'s throwing move constructor, move assignment syntax still does. - `test_copy_assignment_sfinae`: `variant<int, CopyOnly>` is now copy assignable. - `test_copy_assignment_different_index`: - `CopyThrows` and `MoveThrows` have potentially-throwing move construction, so they are now copy constructed directly into variants instead of via indirect copy-and-move. - implement new `CopyCannotThrow` and verify that direct copy construction of such in variants is preferred over indirect copy-and-move when neither method throws. test/std/utilities/variant/variant.variant/variant.assign/move.pass.cpp: - `test_move_assignment_sfinae`: `variant<int, MoveOnly>` is now move assignable. test/std/utilities/variant/variant.variant/variant.assign/T.pass.cpp: - `test_T_assignment_performs_construction`: - assigning `ThrowsCtorT` into a variant now constructs a temporary and moves into the variant; the variant keeps its old value if the temporary construction throws. - test that direct construction is preferred to temporary-and-move when neither can throw. - test that direct construction is preferred to temporary-and-move when both can throw. test/support/variant_test_helpers.hpp: test/std/utilities/variant/variant.variant/variant.ctor/copy.pass.cpp: test/std/utilities/variant/variant.variant/variant.ctor/move.pass.cpp: - Fix `makeEmpty`: Copy assignment syntax no longer invokes `MakeEmptyT`'s throwing move constructor, move assignment syntax still does. https://reviews.llvm.org/D32515 Files: test/std/utilities/variant/variant.variant/variant.assign/T.pass.cpp test/std/utilities/variant/variant.variant/variant.assign/copy.pass.cpp test/std/utilities/variant/variant.variant/variant.assign/move.pass.cpp test/std/utilities/variant/variant.variant/variant.ctor/copy.pass.cpp test/std/utilities/variant/variant.variant/variant.ctor/move.pass.cpp test/support/variant_test_helpers.hpp
Index: test/support/variant_test_helpers.hpp =================================================================== --- test/support/variant_test_helpers.hpp +++ test/support/variant_test_helpers.hpp @@ -69,9 +69,9 @@ void makeEmpty(Variant& v) { Variant v2(std::in_place_type<MakeEmptyT>); try { - v = v2; + v = std::move(v2); assert(false); - } catch (...) { + } catch (...) { assert(v.valueless_by_exception()); } } Index: test/std/utilities/variant/variant.variant/variant.ctor/move.pass.cpp =================================================================== --- test/std/utilities/variant/variant.variant/variant.ctor/move.pass.cpp +++ test/std/utilities/variant/variant.variant/variant.ctor/move.pass.cpp @@ -65,7 +65,7 @@ template <class Variant> void makeEmpty(Variant &v) { Variant v2(std::in_place_type<MakeEmptyT>); try { - v = v2; + v = std::move(v2); assert(false); } catch (...) { assert(v.valueless_by_exception()); Index: test/std/utilities/variant/variant.variant/variant.ctor/copy.pass.cpp =================================================================== --- test/std/utilities/variant/variant.variant/variant.ctor/copy.pass.cpp +++ test/std/utilities/variant/variant.variant/variant.ctor/copy.pass.cpp @@ -63,7 +63,7 @@ template <class Variant> void makeEmpty(Variant &v) { Variant v2(std::in_place_type<MakeEmptyT>); try { - v = v2; + v = std::move(v2); assert(false); } catch (...) { assert(v.valueless_by_exception()); Index: test/std/utilities/variant/variant.variant/variant.assign/move.pass.cpp =================================================================== --- test/std/utilities/variant/variant.variant/variant.assign/move.pass.cpp +++ test/std/utilities/variant/variant.variant/variant.assign/move.pass.cpp @@ -117,10 +117,8 @@ static_assert(std::is_move_assignable<V>::value, ""); } { - // variant only provides move assignment when both the move constructor - // and move assignment operator are well formed. using V = std::variant<int, CopyOnly>; - static_assert(!std::is_move_assignable<V>::value, ""); + static_assert(std::is_move_assignable<V>::value, ""); } { using V = std::variant<int, NoCopy>; Index: test/std/utilities/variant/variant.variant/variant.assign/copy.pass.cpp =================================================================== --- test/std/utilities/variant/variant.variant/variant.assign/copy.pass.cpp +++ test/std/utilities/variant/variant.variant/variant.assign/copy.pass.cpp @@ -66,7 +66,7 @@ ++alive; ++copy_construct; } - CopyAssign(CopyAssign &&o) : value(o.value) { + CopyAssign(CopyAssign &&o) noexcept : value(o.value) { o.value = -1; ++alive; ++move_construct; @@ -76,7 +76,7 @@ ++copy_assign; return *this; } - CopyAssign &operator=(CopyAssign &&o) { + CopyAssign &operator=(CopyAssign &&o) noexcept { value = o.value; o.value = -1; ++move_assign; @@ -108,6 +108,17 @@ CopyThrows &operator=(const CopyThrows &) { throw 42; } }; +struct CopyCannotThrow { + static int alive; + CopyCannotThrow() { ++alive; } + CopyCannotThrow(const CopyCannotThrow &) noexcept { ++alive; } + CopyCannotThrow(CopyCannotThrow &&) noexcept { assert(false); } + CopyCannotThrow &operator=(const CopyCannotThrow &) noexcept = default; + CopyCannotThrow &operator=(CopyCannotThrow &&) noexcept { assert(false); return *this; } +}; + +int CopyCannotThrow::alive = 0; + struct MoveThrows { static int alive; MoveThrows() { ++alive; } @@ -139,7 +150,7 @@ template <class Variant> void makeEmpty(Variant &v) { Variant v2(std::in_place_type<MakeEmptyT>); try { - v = v2; + v = std::move(v2); assert(false); } catch (...) { assert(v.valueless_by_exception()); @@ -164,10 +175,8 @@ static_assert(std::is_copy_assignable<V>::value, ""); } { - // variant only provides copy assignment when both the copy and move - // constructors are well formed using V = std::variant<int, CopyOnly>; - static_assert(!std::is_copy_assignable<V>::value, ""); + static_assert(std::is_copy_assignable<V>::value, ""); } { using V = std::variant<int, NoCopy>; @@ -331,34 +340,39 @@ } #ifndef TEST_HAS_NO_EXCEPTIONS { - // Test that if copy construction throws then original value is - // unchanged. + // Test that copy construction is used directly if move construction may throw, + // which results in a valueless variant if copy throws. using V = std::variant<int, CopyThrows, std::string>; V v1(std::in_place_type<std::string>, "hello"); V v2(std::in_place_type<CopyThrows>); try { v1 = v2; assert(false); } catch (...) { /* ... */ } - assert(v1.index() == 2); - assert(std::get<2>(v1) == "hello"); + assert(v1.valueless_by_exception()); } { - // Test that if move construction throws then the variant is left - // valueless by exception. + // Test that copy construction is used directly if move construction may throw. using V = std::variant<int, MoveThrows, std::string>; V v1(std::in_place_type<std::string>, "hello"); V v2(std::in_place_type<MoveThrows>); assert(MoveThrows::alive == 1); - try { - v1 = v2; - assert(false); - } catch (...) { /* ... */ - } - assert(v1.valueless_by_exception()); + v1 = v2; + assert(v1.index() == 1); assert(v2.index() == 1); - assert(MoveThrows::alive == 1); + assert(MoveThrows::alive == 2); + } + { + // Test that direct copy construction is preferred when it cannot throw. + using V = std::variant<int, CopyCannotThrow, std::string>; + V v1(std::in_place_type<std::string>, "hello"); + V v2(std::in_place_type<CopyCannotThrow>); + assert(CopyCannotThrow::alive == 1); + v1 = v2; + assert(v1.index() == 1); + assert(v2.index() == 1); + assert(CopyCannotThrow::alive == 2); } { using V = std::variant<int, CopyThrows, std::string>; Index: test/std/utilities/variant/variant.variant/variant.assign/T.pass.cpp =================================================================== --- test/std/utilities/variant/variant.variant/variant.assign/T.pass.cpp +++ test/std/utilities/variant/variant.variant/variant.assign/T.pass.cpp @@ -61,6 +61,28 @@ } }; +struct MoveCrashes { + int value; + MoveCrashes(int v = 0) noexcept : value{v} {} + MoveCrashes(MoveCrashes &&) noexcept { assert(false); } + MoveCrashes &operator=(MoveCrashes &&) noexcept { assert(false); return *this; } + MoveCrashes &operator=(int v) noexcept { + value = v; + return *this; + } +}; + +struct ThrowsCtorTandMove { + int value; + ThrowsCtorTandMove() : value(0) {} + ThrowsCtorTandMove(int) noexcept(false) { throw 42; } + ThrowsCtorTandMove(ThrowsCtorTandMove &&) noexcept(false) { assert(false); } + ThrowsCtorTandMove &operator=(int v) noexcept { + value = v; + return *this; + } +}; + struct ThrowsAssignT { int value; ThrowsAssignT() : value(0) {} @@ -167,17 +189,39 @@ V v(std::in_place_type<std::string>, "hello"); try { v = 42; + assert(false); } catch (...) { /* ... */ } - assert(v.valueless_by_exception()); + assert(v.index() == 0); + assert(std::get<0>(v) == "hello"); } { using V = std::variant<ThrowsAssignT, std::string>; V v(std::in_place_type<std::string>, "hello"); v = 42; assert(v.index() == 0); assert(std::get<0>(v).value == 42); } + { + // Test that nothrow direct construction is preferred to nothrow move. + using V = std::variant<MoveCrashes, std::string>; + V v(std::in_place_type<std::string>, "hello"); + v = 42; + assert(v.index() == 0); + assert(std::get<0>(v).value == 42); + } + { + // Test that throwing direct construction is preferred to copy-and-move when + // move can throw. + using V = std::variant<ThrowsCtorTandMove, std::string>; + V v(std::in_place_type<std::string>, "hello"); + try { + v = 42; + assert(false); + } catch(...) { /* ... */ + } + assert(v.valueless_by_exception()); + } #endif }
_______________________________________________ cfe-commits mailing list cfe-commits@lists.llvm.org http://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits