diff options
Diffstat (limited to 'absl/types')
-rw-r--r-- | absl/types/BUILD.bazel | 15 | ||||
-rw-r--r-- | absl/types/CMakeLists.txt | 14 | ||||
-rw-r--r-- | absl/types/optional.h | 38 | ||||
-rw-r--r-- | absl/types/variant.h | 2 | ||||
-rw-r--r-- | absl/types/variant_exception_safety_test.cc | 519 |
5 files changed, 567 insertions, 21 deletions
diff --git a/absl/types/BUILD.bazel b/absl/types/BUILD.bazel index 0bdb2f78aee0..f8d53c263bab 100644 --- a/absl/types/BUILD.bazel +++ b/absl/types/BUILD.bazel @@ -223,3 +223,18 @@ cc_test( "@com_google_googletest//:gtest_main", ], ) + +cc_test( + name = "variant_exception_safety_test", + size = "small", + srcs = [ + "variant_exception_safety_test.cc", + ], + copts = ABSL_TEST_COPTS + ABSL_EXCEPTIONS_FLAG, + deps = [ + ":variant", + "//absl/base:exception_safety_testing", + "//absl/memory", + "@com_google_googletest//:gtest_main", + ], +) diff --git a/absl/types/CMakeLists.txt b/absl/types/CMakeLists.txt index f51d126f8160..fbd8374031da 100644 --- a/absl/types/CMakeLists.txt +++ b/absl/types/CMakeLists.txt @@ -29,6 +29,9 @@ absl_header_library( TARGET absl_any PUBLIC_LIBRARIES + absl::bad_any_cast + absl::base + absl::meta absl::utility PRIVATE_COMPILE_FLAGS ${ABSL_EXCEPTIONS_FLAG} @@ -59,7 +62,6 @@ absl_library( SOURCES ${BAD_ANY_CAST_SRC} PUBLIC_LIBRARIES - absl::base absl::any EXPORT_NAME bad_any_cast ) @@ -76,7 +78,11 @@ absl_library( SOURCES ${OPTIONAL_SRC} PUBLIC_LIBRARIES + absl::bad_optional_access absl::base + absl::memory + absl::meta + absl::utility EXPORT_NAME optional ) @@ -143,7 +149,11 @@ absl_test( # test any_exception_safety_test set(ANY_EXCEPTION_SAFETY_TEST_SRC "any_exception_safety_test.cc") -set(ANY_EXCEPTION_SAFETY_TEST_PUBLIC_LIBRARIES absl::any absl::base absl::base_internal_exception_safety_testing) +set(ANY_EXCEPTION_SAFETY_TEST_PUBLIC_LIBRARIES + absl::any + absl::base + absl_base_internal_exception_safety_testing +) absl_test( TARGET diff --git a/absl/types/optional.h b/absl/types/optional.h index 98b29e591d15..4dcf479615bd 100644 --- a/absl/types/optional.h +++ b/absl/types/optional.h @@ -958,7 +958,8 @@ constexpr auto operator==(const optional<T>& x, const optional<U>& y) -> decltype(optional_internal::convertible_to_bool(*x == *y)) { return static_cast<bool>(x) != static_cast<bool>(y) ? false - : static_cast<bool>(x) == false ? true : *x == *y; + : static_cast<bool>(x) == false ? true + : static_cast<bool>(*x == *y); } // Returns: If bool(x) != bool(y), true; otherwise, if bool(x) == false, false; @@ -968,31 +969,32 @@ constexpr auto operator!=(const optional<T>& x, const optional<U>& y) -> decltype(optional_internal::convertible_to_bool(*x != *y)) { return static_cast<bool>(x) != static_cast<bool>(y) ? true - : static_cast<bool>(x) == false ? false : *x != *y; + : static_cast<bool>(x) == false ? false + : static_cast<bool>(*x != *y); } // Returns: If !y, false; otherwise, if !x, true; otherwise *x < *y. template <typename T, typename U> constexpr auto operator<(const optional<T>& x, const optional<U>& y) -> decltype(optional_internal::convertible_to_bool(*x < *y)) { - return !y ? false : !x ? true : *x < *y; + return !y ? false : !x ? true : static_cast<bool>(*x < *y); } // Returns: If !x, false; otherwise, if !y, true; otherwise *x > *y. template <typename T, typename U> constexpr auto operator>(const optional<T>& x, const optional<U>& y) -> decltype(optional_internal::convertible_to_bool(*x > *y)) { - return !x ? false : !y ? true : *x > *y; + return !x ? false : !y ? true : static_cast<bool>(*x > *y); } // Returns: If !x, true; otherwise, if !y, false; otherwise *x <= *y. template <typename T, typename U> constexpr auto operator<=(const optional<T>& x, const optional<U>& y) -> decltype(optional_internal::convertible_to_bool(*x <= *y)) { - return !x ? true : !y ? false : *x <= *y; + return !x ? true : !y ? false : static_cast<bool>(*x <= *y); } // Returns: If !y, true; otherwise, if !x, false; otherwise *x >= *y. template <typename T, typename U> constexpr auto operator>=(const optional<T>& x, const optional<U>& y) -> decltype(optional_internal::convertible_to_bool(*x >= *y)) { - return !y ? true : !x ? false : *x >= *y; + return !y ? true : !x ? false : static_cast<bool>(*x >= *y); } // Comparison with nullopt [optional.nullops] @@ -1054,62 +1056,62 @@ constexpr bool operator>=(nullopt_t, const optional<T>& x) noexcept { template <typename T, typename U> constexpr auto operator==(const optional<T>& x, const U& v) -> decltype(optional_internal::convertible_to_bool(*x == v)) { - return static_cast<bool>(x) ? *x == v : false; + return static_cast<bool>(x) ? static_cast<bool>(*x == v) : false; } template <typename T, typename U> constexpr auto operator==(const U& v, const optional<T>& x) -> decltype(optional_internal::convertible_to_bool(v == *x)) { - return static_cast<bool>(x) ? v == *x : false; + return static_cast<bool>(x) ? static_cast<bool>(v == *x) : false; } template <typename T, typename U> constexpr auto operator!=(const optional<T>& x, const U& v) -> decltype(optional_internal::convertible_to_bool(*x != v)) { - return static_cast<bool>(x) ? *x != v : true; + return static_cast<bool>(x) ? static_cast<bool>(*x != v) : true; } template <typename T, typename U> constexpr auto operator!=(const U& v, const optional<T>& x) -> decltype(optional_internal::convertible_to_bool(v != *x)) { - return static_cast<bool>(x) ? v != *x : true; + return static_cast<bool>(x) ? static_cast<bool>(v != *x) : true; } template <typename T, typename U> constexpr auto operator<(const optional<T>& x, const U& v) -> decltype(optional_internal::convertible_to_bool(*x < v)) { - return static_cast<bool>(x) ? *x < v : true; + return static_cast<bool>(x) ? static_cast<bool>(*x < v) : true; } template <typename T, typename U> constexpr auto operator<(const U& v, const optional<T>& x) -> decltype(optional_internal::convertible_to_bool(v < *x)) { - return static_cast<bool>(x) ? v < *x : false; + return static_cast<bool>(x) ? static_cast<bool>(v < *x) : false; } template <typename T, typename U> constexpr auto operator<=(const optional<T>& x, const U& v) -> decltype(optional_internal::convertible_to_bool(*x <= v)) { - return static_cast<bool>(x) ? *x <= v : true; + return static_cast<bool>(x) ? static_cast<bool>(*x <= v) : true; } template <typename T, typename U> constexpr auto operator<=(const U& v, const optional<T>& x) -> decltype(optional_internal::convertible_to_bool(v <= *x)) { - return static_cast<bool>(x) ? v <= *x : false; + return static_cast<bool>(x) ? static_cast<bool>(v <= *x) : false; } template <typename T, typename U> constexpr auto operator>(const optional<T>& x, const U& v) -> decltype(optional_internal::convertible_to_bool(*x > v)) { - return static_cast<bool>(x) ? *x > v : false; + return static_cast<bool>(x) ? static_cast<bool>(*x > v) : false; } template <typename T, typename U> constexpr auto operator>(const U& v, const optional<T>& x) -> decltype(optional_internal::convertible_to_bool(v > *x)) { - return static_cast<bool>(x) ? v > *x : true; + return static_cast<bool>(x) ? static_cast<bool>(v > *x) : true; } template <typename T, typename U> constexpr auto operator>=(const optional<T>& x, const U& v) -> decltype(optional_internal::convertible_to_bool(*x >= v)) { - return static_cast<bool>(x) ? *x >= v : false; + return static_cast<bool>(x) ? static_cast<bool>(*x >= v) : false; } template <typename T, typename U> constexpr auto operator>=(const U& v, const optional<T>& x) -> decltype(optional_internal::convertible_to_bool(v >= *x)) { - return static_cast<bool>(x) ? v >= *x : true; + return static_cast<bool>(x) ? static_cast<bool>(v >= *x) : true; } } // namespace absl diff --git a/absl/types/variant.h b/absl/types/variant.h index 52a311e37d3b..7ae65abe0e6d 100644 --- a/absl/types/variant.h +++ b/absl/types/variant.h @@ -350,7 +350,7 @@ constexpr const variant_alternative_t<I, variant<Types...>>&& get( // get_if() // // Returns a pointer to the value currently stored within a given variant, if -// present, using either a unique alternative type amonst the variant's set of +// present, using either a unique alternative type amongst the variant's set of // alternative types, or the variant's index value. If such a value does not // exist, returns `nullptr`. // diff --git a/absl/types/variant_exception_safety_test.cc b/absl/types/variant_exception_safety_test.cc new file mode 100644 index 000000000000..377e4afac572 --- /dev/null +++ b/absl/types/variant_exception_safety_test.cc @@ -0,0 +1,519 @@ +// Copyright 2017 The Abseil Authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +#include "absl/types/variant.h" + +#include <iostream> +#include <memory> +#include <utility> +#include <vector> + +#include "gmock/gmock.h" +#include "gtest/gtest.h" +#include "absl/base/internal/exception_safety_testing.h" +#include "absl/memory/memory.h" + +namespace absl { +namespace { + +using ::testing::MakeExceptionSafetyTester; +using ::testing::nothrow_guarantee; +using ::testing::strong_guarantee; +using ::testing::TestThrowingCtor; + +using Thrower = testing::ThrowingValue<>; +using CopyNothrow = testing::ThrowingValue<testing::TypeSpec::kNoThrowCopy>; +using MoveNothrow = testing::ThrowingValue<testing::TypeSpec::kNoThrowMove>; +using ThrowingAlloc = testing::ThrowingAllocator<Thrower>; +using ThrowerVec = std::vector<Thrower, ThrowingAlloc>; +using ThrowingVariant = + absl::variant<Thrower, CopyNothrow, MoveNothrow, ThrowerVec>; + +struct ConversionException {}; + +template <class T> +struct ExceptionOnConversion { + operator T() const { // NOLINT + throw ConversionException(); + } +}; + +// Forces a variant into the valueless by exception state. +void ToValuelessByException(ThrowingVariant& v) { // NOLINT + try { + v.emplace<Thrower>(); + v.emplace<Thrower>(ExceptionOnConversion<Thrower>()); + } catch (ConversionException& /*e*/) { + // This space intentionally left blank. + } +} + +// Check that variant is still in a usable state after an exception is thrown. +testing::AssertionResult CheckInvariants(ThrowingVariant* v) { + using testing::AssertionFailure; + using testing::AssertionSuccess; + + // Try using the active alternative + if (absl::holds_alternative<Thrower>(*v)) { + auto& t = absl::get<Thrower>(*v); + t = Thrower{-100}; + if (t.Get() != -100) { + return AssertionFailure() << "Thrower should be assigned -100"; + } + } else if (absl::holds_alternative<ThrowerVec>(*v)) { + auto& tv = absl::get<ThrowerVec>(*v); + tv.clear(); + tv.emplace_back(-100); + if (tv.size() != 1 || tv[0].Get() != -100) { + return AssertionFailure() << "ThrowerVec should be {Thrower{-100}}"; + } + } else if (absl::holds_alternative<CopyNothrow>(*v)) { + auto& t = absl::get<CopyNothrow>(*v); + t = CopyNothrow{-100}; + if (t.Get() != -100) { + return AssertionFailure() << "CopyNothrow should be assigned -100"; + } + } else if (absl::holds_alternative<MoveNothrow>(*v)) { + auto& t = absl::get<MoveNothrow>(*v); + t = MoveNothrow{-100}; + if (t.Get() != -100) { + return AssertionFailure() << "MoveNothrow should be assigned -100"; + } + } + + // Try making variant valueless_by_exception + if (!v->valueless_by_exception()) ToValuelessByException(*v); + if (!v->valueless_by_exception()) { + return AssertionFailure() << "Variant should be valueless_by_exception"; + } + try { + auto unused = absl::get<Thrower>(*v); + static_cast<void>(unused); + return AssertionFailure() << "Variant should not contain Thrower"; + } catch (absl::bad_variant_access) { + } catch (...) { + return AssertionFailure() << "Unexpected exception throw from absl::get"; + } + + // Try using the variant + v->emplace<Thrower>(100); + if (!absl::holds_alternative<Thrower>(*v) || + absl::get<Thrower>(*v) != Thrower(100)) { + return AssertionFailure() << "Variant should contain Thrower(100)"; + } + v->emplace<ThrowerVec>({Thrower(100)}); + if (!absl::holds_alternative<ThrowerVec>(*v) || + absl::get<ThrowerVec>(*v)[0] != Thrower(100)) { + return AssertionFailure() + << "Variant should contain ThrowerVec{Thrower(100)}"; + } + return AssertionSuccess(); +} + +Thrower ExpectedThrower() { return Thrower(42); } +ThrowerVec ExpectedThrowerVec() { return {Thrower(100), Thrower(200)}; } +ThrowingVariant ValuelessByException() { + ThrowingVariant v; + ToValuelessByException(v); + return v; +} +ThrowingVariant WithThrower() { return Thrower(39); } +ThrowingVariant WithThrowerVec() { + return ThrowerVec{Thrower(1), Thrower(2), Thrower(3)}; +} +ThrowingVariant WithCopyNoThrow() { return CopyNothrow(39); } +ThrowingVariant WithMoveNoThrow() { return MoveNothrow(39); } + +TEST(VariantExceptionSafetyTest, DefaultConstructor) { + TestThrowingCtor<ThrowingVariant>(); +} + +TEST(VariantExceptionSafetyTest, CopyConstructor) { + { + ThrowingVariant v(ExpectedThrower()); + TestThrowingCtor<ThrowingVariant>(v); + } + { + ThrowingVariant v(ExpectedThrowerVec()); + TestThrowingCtor<ThrowingVariant>(v); + } + { + ThrowingVariant v(ValuelessByException()); + TestThrowingCtor<ThrowingVariant>(v); + } +} + +TEST(VariantExceptionSafetyTest, MoveConstructor) { + { + ThrowingVariant v(ExpectedThrower()); + TestThrowingCtor<ThrowingVariant>(std::move(v)); + } + { + ThrowingVariant v(ExpectedThrowerVec()); + TestThrowingCtor<ThrowingVariant>(std::move(v)); + } + { + ThrowingVariant v(ValuelessByException()); + TestThrowingCtor<ThrowingVariant>(std::move(v)); + } +} + +TEST(VariantExceptionSafetyTest, ValueConstructor) { + TestThrowingCtor<ThrowingVariant>(ExpectedThrower()); + TestThrowingCtor<ThrowingVariant>(ExpectedThrowerVec()); +} + +TEST(VariantExceptionSafetyTest, InPlaceTypeConstructor) { + TestThrowingCtor<ThrowingVariant>(absl::in_place_type_t<Thrower>{}, + ExpectedThrower()); + TestThrowingCtor<ThrowingVariant>(absl::in_place_type_t<ThrowerVec>{}, + ExpectedThrowerVec()); +} + +TEST(VariantExceptionSafetyTest, InPlaceIndexConstructor) { + TestThrowingCtor<ThrowingVariant>(absl::in_place_index_t<0>{}, + ExpectedThrower()); + TestThrowingCtor<ThrowingVariant>(absl::in_place_index_t<3>{}, + ExpectedThrowerVec()); +} + +TEST(VariantExceptionSafetyTest, CopyAssign) { + // variant& operator=(const variant& rhs); + // Let j be rhs.index() + { + // - neither *this nor rhs holds a value + const ThrowingVariant rhs = ValuelessByException(); + EXPECT_TRUE(MakeExceptionSafetyTester() + .WithInitialValue(ValuelessByException()) + .WithInvariants(nothrow_guarantee) + .Test([&rhs](ThrowingVariant* lhs) { *lhs = rhs; })); + } + { + // - *this holds a value but rhs does not + const ThrowingVariant rhs = ValuelessByException(); + EXPECT_TRUE(MakeExceptionSafetyTester() + .WithInitialValue(WithThrower()) + .WithInvariants(nothrow_guarantee) + .Test([&rhs](ThrowingVariant* lhs) { *lhs = rhs; })); + } + // - index() == j + { + const ThrowingVariant rhs(ExpectedThrower()); + auto tester = + MakeExceptionSafetyTester() + .WithInitialValue(WithThrower()) + .WithOperation([&rhs](ThrowingVariant* lhs) { *lhs = rhs; }); + EXPECT_TRUE(tester.WithInvariants(CheckInvariants).Test()); + EXPECT_FALSE(tester.WithInvariants(strong_guarantee).Test()); + } + { + const ThrowingVariant rhs(ExpectedThrowerVec()); + auto tester = + MakeExceptionSafetyTester() + .WithInitialValue(WithThrowerVec()) + .WithOperation([&rhs](ThrowingVariant* lhs) { *lhs = rhs; }); + EXPECT_TRUE(tester.WithInvariants(CheckInvariants).Test()); + EXPECT_FALSE(tester.WithInvariants(strong_guarantee).Test()); + } + // libstdc++ std::variant has bugs on copy assignment regarding exception + // safety. +#if !(defined(ABSL_HAVE_STD_VARIANT) && defined(__GLIBCXX__)) + // index() != j + // if is_nothrow_copy_constructible_v<Tj> or + // !is_nothrow_move_constructible<Tj> is true, equivalent to + // emplace<j>(get<j>(rhs)) + { + // is_nothrow_copy_constructible_v<Tj> == true + // should not throw because emplace() invokes Tj's copy ctor + // which should not throw. + const ThrowingVariant rhs(CopyNothrow{}); + EXPECT_TRUE(MakeExceptionSafetyTester() + .WithInitialValue(WithThrower()) + .WithInvariants(nothrow_guarantee) + .Test([&rhs](ThrowingVariant* lhs) { *lhs = rhs; })); + } + { + // is_nothrow_copy_constructible<Tj> == false && + // is_nothrow_move_constructible<Tj> == false + // should provide basic guarantee because emplace() invokes Tj's copy ctor + // which may throw. + const ThrowingVariant rhs(ExpectedThrower()); + auto tester = + MakeExceptionSafetyTester() + .WithInitialValue(WithCopyNoThrow()) + .WithOperation([&rhs](ThrowingVariant* lhs) { *lhs = rhs; }); + EXPECT_TRUE(tester + .WithInvariants(CheckInvariants, + [](ThrowingVariant* lhs) { + return lhs->valueless_by_exception(); + }) + .Test()); + EXPECT_FALSE(tester.WithInvariants(strong_guarantee).Test()); + } +#endif // !(defined(ABSL_HAVE_STD_VARIANT) && defined(__GLIBCXX__)) + { + // is_nothrow_copy_constructible_v<Tj> == false && + // is_nothrow_move_constructible_v<Tj> == true + // should provide strong guarantee because it is equivalent to + // operator=(variant(rhs)) which creates a temporary then invoke the move + // ctor which shouldn't throw. + const ThrowingVariant rhs(MoveNothrow{}); + EXPECT_TRUE(MakeExceptionSafetyTester() + .WithInitialValue(WithThrower()) + .WithInvariants(CheckInvariants, strong_guarantee) + .Test([&rhs](ThrowingVariant* lhs) { *lhs = rhs; })); + } +} + +TEST(VariantExceptionSafetyTest, MoveAssign) { + // variant& operator=(variant&& rhs); + // Let j be rhs.index() + { + // - neither *this nor rhs holds a value + ThrowingVariant rhs = ValuelessByException(); + + EXPECT_TRUE(MakeExceptionSafetyTester() + .WithInitialValue(ValuelessByException()) + .WithInvariants(nothrow_guarantee) + .Test([rhs](ThrowingVariant* lhs) mutable { + *lhs = std::move(rhs); + })); + } + { + // - *this holds a value but rhs does not + ThrowingVariant rhs = ValuelessByException(); + EXPECT_TRUE(MakeExceptionSafetyTester() + .WithInitialValue(WithThrower()) + .WithInvariants(nothrow_guarantee) + .Test([rhs](ThrowingVariant* lhs) mutable { + *lhs = std::move(rhs); + })); + } + { + // - index() == j + // assign get<j>(std::move(rhs)) to the value contained in *this. + // If an exception is thrown during call to Tj's move assignment, the state + // of the contained value is as defined by the exception safety guarantee of + // Tj's move assignment; index() will be j. + ThrowingVariant rhs(ExpectedThrower()); + size_t j = rhs.index(); + // Since Thrower's move assignment has basic guarantee, so should variant's. + auto tester = MakeExceptionSafetyTester() + .WithInitialValue(WithThrower()) + .WithOperation([rhs](ThrowingVariant* lhs) mutable { + *lhs = std::move(rhs); + }); + EXPECT_TRUE(tester + .WithInvariants( + CheckInvariants, + [j](ThrowingVariant* lhs) { return lhs->index() == j; }) + .Test()); + EXPECT_FALSE(tester.WithInvariants(strong_guarantee).Test()); + } + { + // - otherwise (index() != j), equivalent to + // emplace<j>(get<j>(std::move(rhs))) + // - If an exception is thrown during the call to Tj's move construction + // (with j being rhs.index()), the variant will hold no value. + ThrowingVariant rhs(CopyNothrow{}); + EXPECT_TRUE(MakeExceptionSafetyTester() + .WithInitialValue(WithThrower()) + .WithInvariants(CheckInvariants, + [](ThrowingVariant* lhs) { + return lhs->valueless_by_exception(); + }) + .Test([rhs](ThrowingVariant* lhs) mutable { + *lhs = std::move(rhs); + })); + } +} + +TEST(VariantExceptionSafetyTest, ValueAssign) { + // template<class T> variant& operator=(T&& t); + // Let Tj be the type that is selected by overload resolution to be assigned. + { + // If *this holds a Tj, assigns std::forward<T>(t) to the value contained in + // *this. If an exception is thrown during the assignment of + // std::forward<T>(t) to the value contained in *this, the state of the + // contained value and t are as defined by the exception safety guarantee of + // the assignment expression; valueless_by_exception() will be false. + // Since Thrower's copy/move assignment has basic guarantee, so should + // variant's. + Thrower rhs = ExpectedThrower(); + // copy assign + auto copy_tester = + MakeExceptionSafetyTester() + .WithInitialValue(WithThrower()) + .WithOperation([rhs](ThrowingVariant* lhs) { *lhs = rhs; }); + EXPECT_TRUE(copy_tester + .WithInvariants(CheckInvariants, + [](ThrowingVariant* lhs) { + return !lhs->valueless_by_exception(); + }) + .Test()); + EXPECT_FALSE(copy_tester.WithInvariants(strong_guarantee).Test()); + // move assign + auto move_tester = MakeExceptionSafetyTester() + .WithInitialValue(WithThrower()) + .WithOperation([rhs](ThrowingVariant* lhs) mutable { + *lhs = std::move(rhs); + }); + EXPECT_TRUE(move_tester + .WithInvariants(CheckInvariants, + [](ThrowingVariant* lhs) { + return !lhs->valueless_by_exception(); + }) + .Test()); + + EXPECT_FALSE(move_tester.WithInvariants(strong_guarantee).Test()); + } + // Otherwise (*this holds something else), if is_nothrow_constructible_v<Tj, + // T> || !is_nothrow_move_constructible_v<Tj> is true, equivalent to + // emplace<j>(std::forward<T>(t)). + // We simplify the test by letting T = `const Tj&` or `Tj&&`, so we can reuse + // the CopyNothrow and MoveNothrow types. + + // if is_nothrow_constructible_v<Tj, T> + // (i.e. is_nothrow_copy/move_constructible_v<Tj>) is true, emplace() just + // invokes the copy/move constructor and it should not throw. + { + const CopyNothrow rhs; + EXPECT_TRUE(MakeExceptionSafetyTester() + .WithInitialValue(WithThrower()) + .WithInvariants(nothrow_guarantee) + .Test([&rhs](ThrowingVariant* lhs) { *lhs = rhs; })); + } + { + MoveNothrow rhs; + EXPECT_TRUE(MakeExceptionSafetyTester() + .WithInitialValue(WithThrower()) + .WithInvariants(nothrow_guarantee) + .Test([rhs](ThrowingVariant* lhs) mutable { + *lhs = std::move(rhs); + })); + } + // if is_nothrow_constructible_v<Tj, T> == false && + // is_nothrow_move_constructible<Tj> == false + // emplace() invokes the copy/move constructor which may throw so it should + // provide basic guarantee and variant object might not hold a value. + { + Thrower rhs = ExpectedThrower(); + // copy + auto copy_tester = + MakeExceptionSafetyTester() + .WithInitialValue(WithCopyNoThrow()) + .WithOperation([&rhs](ThrowingVariant* lhs) { *lhs = rhs; }); + EXPECT_TRUE(copy_tester + .WithInvariants(CheckInvariants, + [](ThrowingVariant* lhs) { + return lhs->valueless_by_exception(); + }) + .Test()); + EXPECT_FALSE(copy_tester.WithInvariants(strong_guarantee).Test()); + // move + auto move_tester = MakeExceptionSafetyTester() + .WithInitialValue(WithCopyNoThrow()) + .WithOperation([rhs](ThrowingVariant* lhs) mutable { + *lhs = std::move(rhs); + }); + EXPECT_TRUE(move_tester + .WithInvariants(CheckInvariants, + [](ThrowingVariant* lhs) { + return lhs->valueless_by_exception(); + }) + .Test()); + EXPECT_FALSE(move_tester.WithInvariants(strong_guarantee).Test()); + } + // Otherwise (if is_nothrow_constructible_v<Tj, T> == false && + // is_nothrow_move_constructible<Tj> == true), + // equivalent to operator=(variant(std::forward<T>(t))) + // This should have strong guarantee because it creates a temporary variant + // and operator=(variant&&) invokes Tj's move ctor which doesn't throw. + // libstdc++ std::variant has bugs on conversion assignment regarding + // exception safety. +#if !(defined(ABSL_HAVE_STD_VARIANT) && defined(__GLIBCXX__)) + { + MoveNothrow rhs; + EXPECT_TRUE(MakeExceptionSafetyTester() + .WithInitialValue(WithThrower()) + .WithInvariants(CheckInvariants, strong_guarantee) + .Test([&rhs](ThrowingVariant* lhs) { *lhs = rhs; })); + } +#endif // !(defined(ABSL_HAVE_STD_VARIANT) && defined(__GLIBCXX__)) +} + +TEST(VariantExceptionSafetyTest, Emplace) { + // If an exception during the initialization of the contained value, the + // variant might not hold a value. The standard requires emplace() to provide + // only basic guarantee. + { + Thrower args = ExpectedThrower(); + auto tester = MakeExceptionSafetyTester() + .WithInitialValue(WithThrower()) + .WithOperation([&args](ThrowingVariant* v) { + v->emplace<Thrower>(args); + }); + EXPECT_TRUE(tester + .WithInvariants(CheckInvariants, + [](ThrowingVariant* v) { + return v->valueless_by_exception(); + }) + .Test()); + EXPECT_FALSE(tester.WithInvariants(strong_guarantee).Test()); + } +} + +TEST(VariantExceptionSafetyTest, Swap) { + // if both are valueless_by_exception(), no effect + { + ThrowingVariant rhs = ValuelessByException(); + EXPECT_TRUE( + MakeExceptionSafetyTester() + .WithInitialValue(ValuelessByException()) + .WithInvariants(nothrow_guarantee) + .Test([rhs](ThrowingVariant* lhs) mutable { lhs->swap(rhs); })); + } + // if index() == rhs.index(), calls swap(get<i>(*this), get<i>(rhs)) + // where i is index(). + { + ThrowingVariant rhs = ExpectedThrower(); + EXPECT_TRUE( + MakeExceptionSafetyTester() + .WithInitialValue(WithThrower()) + .WithInvariants(CheckInvariants) + .Test([rhs](ThrowingVariant* lhs) mutable { lhs->swap(rhs); })); + } + // Otherwise, exchanges the value of rhs and *this. The exception safety + // involves variant in moved-from state which is not specified in the + // standard, and since swap is 3-step it's impossible for it to provide a + // overall strong guarantee. So, we are only checking basic guarantee here. + { + ThrowingVariant rhs = ExpectedThrower(); + EXPECT_TRUE( + MakeExceptionSafetyTester() + .WithInitialValue(WithCopyNoThrow()) + .WithInvariants(CheckInvariants) + .Test([rhs](ThrowingVariant* lhs) mutable { lhs->swap(rhs); })); + } + { + ThrowingVariant rhs = ExpectedThrower(); + EXPECT_TRUE( + MakeExceptionSafetyTester() + .WithInitialValue(WithCopyNoThrow()) + .WithInvariants(CheckInvariants) + .Test([rhs](ThrowingVariant* lhs) mutable { rhs.swap(*lhs); })); + } +} + +} // namespace +} // namespace absl |