- 07191b0f52301e1e4a790e236f7b7c2fd90561ae Disambiguates computed return type of absl::optional logi... by Abseil Team <absl-team@google.com>

- acd95f8ec4e6ec1587cb198c7f40af3c81094d92 Release container benchmarks. by Alex Strelnikov <strel@google.com>
  - 80f596b6b7c5e06453e778c16527d5a0e85f8413 Allow absl::base_internal::AtomicHook to have a default v... by Derek Mauro <dmauro@google.com>
  - 8402631546af8bcbd4acdf897d0cdfb805ad544a Release thread_identity benchmark. by Alex Strelnikov <strel@google.com>
  - 6dcb1e90fefb8556ce4654983d3a73c7585b4b99 Fix spelling error in variant.h by Abseil Team <absl-team@google.com>
  - faa8a81e1442018c0d400b09a595a5be55074715 Run tests from CMake.  The CI is currently Linux only, fo... by Jon Cohen <cohenjon@google.com>
  - 745ed6db574f931f2ec3a88e964fb03a5f22f816 Internal change. by Derek Mauro <dmauro@google.com>
  - 23facd7d1c5f43ac8181b016ee4acc5955f048c1 absl::variant exception safety test. by Xiaoyi Zhang <zhangxy@google.com>
  - c18e21e7cf8f6e83ae9d90e536e886409dd6cf68 Reinstate the syntax check on time-zone abbreviations now... by Abseil Team <absl-team@google.com>
  - da469f4314f0c820665a2b5b9477af9462b23e42 Import CCTZ changes to internal copy. by Shaindel Schwartz <shaindel@google.com>
  - 44ea35843517be03ab256b69449ccfea64352621 Import CCTZ changes to internal copy. by Abseil Team <absl-team@google.com>
  - 55d1105312687c6093950fac831c7540f49045b5 Import CCTZ changes to internal copy. by Greg Falcon <gfalcon@google.com>
  - 58d7965ad274406410b6d833213eca04d41c6867 Add zoneinfo as a data dependency to the //absl/time tests. by Shaindel Schwartz <shaindel@google.com>
  - 6acc50146f9ff29015bfaaa5bf9900691f839da5 Change benchmark target type from cc_test to cc_binary. by Alex Strelnikov <strel@google.com>
  - db3fbdae8f9f285a466f7a070326b1ce43b6a0dd Update WORKSPACE for C++ microbenchmarks and release algo... by Alex Strelnikov <strel@google.com>
  - 0869ae168255242af651853ed01719166d8cebf6 Update to Bazel version 0.13.0. by Abseil Team <absl-team@google.com>
  - e507dd53ab788964207fdf27d31b72a33c296fab Add missing include of cstdio by Abseil Team <absl-team@google.com>

GitOrigin-RevId: 07191b0f52301e1e4a790e236f7b7c2fd90561ae
Change-Id: I90994cf2b438fbec894724dcd9b90882281eef56
This commit is contained in:
Abseil Team 2018-05-04 09:58:56 -07:00 committed by vslashg
parent 9613678332
commit 26b789f9a5
35 changed files with 1583 additions and 119 deletions

View file

@ -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",
],
)

View file

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

View file

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

View file

@ -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`.
//

View file

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