Update StatusOr to support lvalue reference value types. Some implementation notes: - RValue references are not supported right now. It complicates the implementation further and there doesn't seem to be a need for it. It might be done in the future. - Any kind of reference-to-reference conversion only allows those that do not require temporaries to be materialized. Eg `StatusOr<int&>` can convert to `StatusOr<const int&>`, but it can't convert to `StatusOr<const double&>`. - `operator*`/`value()`/`value_or()` always return the reference type, regardless of qualifications of the StatusOr. PiperOrigin-RevId: 776150069 Change-Id: I8446e7f76f6227f24e4de4b9490d20a8156ee8ab
diff --git a/absl/status/internal/statusor_internal.h b/absl/status/internal/statusor_internal.h index 029fdee..a653181 100644 --- a/absl/status/internal/statusor_internal.h +++ b/absl/status/internal/statusor_internal.h
@@ -90,17 +90,34 @@ struct IsDirectInitializationAmbiguous<T, absl::StatusOr<V>> : public IsConstructibleOrConvertibleFromStatusOr<T, V> {}; +// Checks whether the conversion from U to T can be done without dangling +// temporaries. +// REQUIRES: T and U are references. +template <typename T, typename U> +using IsReferenceConversionValid = absl::conjunction< // + std::is_reference<T>, std::is_reference<U>, + // The references are convertible. This checks for + // lvalue/rvalue compatibility. + std::is_convertible<U, T>, + // The pointers are convertible. This checks we don't have + // a temporary. + std::is_convertible<std::remove_reference_t<U>*, + std::remove_reference_t<T>*>>; + // Checks against the constraints of the direction initialization, i.e. when // `StatusOr<T>::StatusOr(U&&)` should participate in overload resolution. template <typename T, typename U> using IsDirectInitializationValid = absl::disjunction< // Short circuits if T is basically U. - std::is_same<T, absl::remove_cvref_t<U>>, - absl::negation<absl::disjunction< - std::is_same<absl::StatusOr<T>, absl::remove_cvref_t<U>>, - std::is_same<absl::Status, absl::remove_cvref_t<U>>, - std::is_same<absl::in_place_t, absl::remove_cvref_t<U>>, - IsDirectInitializationAmbiguous<T, U>>>>; + std::is_same<T, absl::remove_cvref_t<U>>, // + std::conditional_t< + std::is_reference_v<T>, // + IsReferenceConversionValid<T, U>, + absl::negation<absl::disjunction< + std::is_same<absl::StatusOr<T>, absl::remove_cvref_t<U>>, + std::is_same<absl::Status, absl::remove_cvref_t<U>>, + std::is_same<absl::in_place_t, absl::remove_cvref_t<U>>, + IsDirectInitializationAmbiguous<T, U>>>>>; // This trait detects whether `StatusOr<T>::operator=(U&&)` is ambiguous, which // is equivalent to whether all the following conditions are met: @@ -140,7 +157,9 @@ template <bool Explicit, typename T, typename U, bool Lifetimebound> using IsConstructionValid = absl::conjunction< Equality<Lifetimebound, - type_traits_internal::IsLifetimeBoundAssignment<T, U>>, + absl::disjunction< + std::is_reference<T>, + type_traits_internal::IsLifetimeBoundAssignment<T, U>>>, IsDirectInitializationValid<T, U&&>, std::is_constructible<T, U&&>, Equality<!Explicit, std::is_convertible<U&&, T>>, absl::disjunction< @@ -156,8 +175,13 @@ template <typename T, typename U, bool Lifetimebound> using IsAssignmentValid = absl::conjunction< Equality<Lifetimebound, - type_traits_internal::IsLifetimeBoundAssignment<T, U>>, - std::is_constructible<T, U&&>, std::is_assignable<T&, U&&>, + absl::disjunction< + std::is_reference<T>, + type_traits_internal::IsLifetimeBoundAssignment<T, U>>>, + std::conditional_t<std::is_reference_v<T>, + IsReferenceConversionValid<T, U&&>, + absl::conjunction<std::is_constructible<T, U&&>, + std::is_assignable<T&, U&&>>>, absl::disjunction< std::is_same<T, absl::remove_cvref_t<U>>, absl::conjunction< @@ -178,6 +202,9 @@ typename UQ> using IsConstructionFromStatusOrValid = absl::conjunction< absl::negation<std::is_same<T, U>>, + // If `T` is a reference, then U must be a compatible one. + absl::disjunction<absl::negation<std::is_reference<T>>, + IsReferenceConversionValid<T, U>>, Equality<Lifetimebound, type_traits_internal::IsLifetimeBoundAssignment<T, U>>, std::is_constructible<T, UQ>, @@ -193,6 +220,16 @@ absl::negation<IsConstructibleOrConvertibleOrAssignableFromStatusOr< T, absl::remove_cvref_t<U>>>>; +template <typename T, typename U, bool Lifetimebound> +using IsValueOrValid = absl::conjunction< + // If `T` is a reference, then U must be a compatible one. + absl::disjunction<absl::negation<std::is_reference<T>>, + IsReferenceConversionValid<T, U>>, + Equality<Lifetimebound, + absl::disjunction< + std::is_reference<T>, + type_traits_internal::IsLifetimeBoundAssignment<T, U>>>>; + class Helper { public: // Move type-agnostic error handling to the .cc. @@ -209,6 +246,26 @@ new (p) T(std::forward<Args>(args)...); } +template <typename T> +class Reference { + public: + constexpr explicit Reference(T ref ABSL_ATTRIBUTE_LIFETIME_BOUND) + : payload_(std::addressof(ref)) {} + + Reference(const Reference&) = default; + Reference& operator=(const Reference&) = default; + Reference& operator=(T value) { + payload_ = std::addressof(value); + return *this; + } + + operator T() const { return static_cast<T>(*payload_); } // NOLINT + T get() const { return *this; } + + private: + std::remove_reference_t<T>* absl_nonnull payload_; +}; + // Helper base class to hold the data and all operations. // We move all this to a base class to allow mixing with the appropriate // TraitsBase specialization. @@ -217,6 +274,14 @@ template <typename U> friend class StatusOrData; + decltype(auto) MaybeMoveData() { + if constexpr (std::is_reference_v<T>) { + return data_.get(); + } else { + return std::move(data_); + } + } + public: StatusOrData() = delete; @@ -231,7 +296,7 @@ StatusOrData(StatusOrData&& other) noexcept { if (other.ok()) { - MakeValue(std::move(other.data_)); + MakeValue(other.MaybeMoveData()); MakeStatus(); } else { MakeStatus(std::move(other.status_)); @@ -251,7 +316,7 @@ template <typename U> explicit StatusOrData(StatusOrData<U>&& other) { if (other.ok()) { - MakeValue(std::move(other.data_)); + MakeValue(other.MaybeMoveData()); MakeStatus(); } else { MakeStatus(std::move(other.status_)); @@ -264,13 +329,6 @@ MakeStatus(); } - explicit StatusOrData(const T& value) : data_(value) { - MakeStatus(); - } - explicit StatusOrData(T&& value) : data_(std::move(value)) { - MakeStatus(); - } - template <typename U, absl::enable_if_t<std::is_constructible<absl::Status, U&&>::value, int> = 0> @@ -290,7 +348,7 @@ StatusOrData& operator=(StatusOrData&& other) { if (this == &other) return *this; if (other.ok()) - Assign(std::move(other.data_)); + Assign(other.MaybeMoveData()); else AssignStatus(std::move(other.status_)); return *this; @@ -299,7 +357,9 @@ ~StatusOrData() { if (ok()) { status_.~Status(); - data_.~T(); + if constexpr (!std::is_trivially_destructible_v<T>) { + data_.~T(); + } } else { status_.~Status(); } @@ -340,11 +400,13 @@ // When T is const, we need some non-const object we can cast to void* for // the placement new. dummy_ is that object. Dummy dummy_; - T data_; + std::conditional_t<std::is_reference_v<T>, Reference<T>, T> data_; }; void Clear() { - if (ok()) data_.~T(); + if constexpr (!std::is_trivially_destructible_v<T>) { + if (ok()) data_.~T(); + } } void EnsureOk() const { @@ -359,7 +421,8 @@ // argument. template <typename... Arg> void MakeValue(Arg&&... arg) { - internal_statusor::PlacementNew<T>(&dummy_, std::forward<Arg>(arg)...); + internal_statusor::PlacementNew<decltype(data_)>(&dummy_, + std::forward<Arg>(arg)...); } // Construct the status (ie. status_) through placement new with the passed @@ -369,6 +432,22 @@ internal_statusor::PlacementNew<Status>(&status_, std::forward<Args>(args)...); } + + template <typename U> + T ValueOrImpl(U&& default_value) const& { + if (ok()) { + return data_; + } + return std::forward<U>(default_value); + } + + template <typename U> + T ValueOrImpl(U&& default_value) && { + if (ok()) { + return std::move(data_); + } + return std::forward<U>(default_value); + } }; // Helper base classes to allow implicitly deleted constructors and assignment @@ -411,8 +490,9 @@ MoveCtorBase& operator=(MoveCtorBase&&) = default; }; -template <typename T, bool = std::is_copy_constructible<T>::value&& - std::is_copy_assignable<T>::value> +template <typename T, bool = (std::is_copy_constructible<T>::value && + std::is_copy_assignable<T>::value) || + std::is_reference_v<T>> struct CopyAssignBase { CopyAssignBase() = default; CopyAssignBase(const CopyAssignBase&) = default; @@ -430,8 +510,9 @@ CopyAssignBase& operator=(CopyAssignBase&&) = default; }; -template <typename T, bool = std::is_move_constructible<T>::value&& - std::is_move_assignable<T>::value> +template <typename T, bool = (std::is_move_constructible<T>::value && + std::is_move_assignable<T>::value) || + std::is_reference_v<T>> struct MoveAssignBase { MoveAssignBase() = default; MoveAssignBase(const MoveAssignBase&) = default;
diff --git a/absl/status/status_matchers_test.cc b/absl/status/status_matchers_test.cc index b8ccaa4..51a5f27 100644 --- a/absl/status/status_matchers_test.cc +++ b/absl/status/status_matchers_test.cc
@@ -18,6 +18,7 @@ #include "absl/status/status_matchers.h" #include <string> +#include <vector> #include "gmock/gmock.h" #include "gtest/gtest-spi.h" @@ -31,9 +32,12 @@ using ::absl_testing::IsOk; using ::absl_testing::IsOkAndHolds; using ::absl_testing::StatusIs; +using ::testing::ElementsAre; using ::testing::Eq; using ::testing::Gt; using ::testing::MatchesRegex; +using ::testing::Not; +using ::testing::Ref; TEST(StatusMatcherTest, StatusIsOk) { EXPECT_THAT(absl::OkStatus(), IsOk()); } @@ -158,4 +162,23 @@ "ungueltig"); } +TEST(StatusMatcherTest, ReferencesWork) { + int i = 17; + int j = 19; + EXPECT_THAT(absl::StatusOr<int&>(i), IsOkAndHolds(17)); + EXPECT_THAT(absl::StatusOr<int&>(i), Not(IsOkAndHolds(19))); + EXPECT_THAT(absl::StatusOr<const int&>(i), IsOkAndHolds(17)); + + // Reference testing works as expected. + EXPECT_THAT(absl::StatusOr<int&>(i), IsOkAndHolds(Ref(i))); + EXPECT_THAT(absl::StatusOr<int&>(i), Not(IsOkAndHolds(Ref(j)))); + + // Try a more complex one. + std::vector<std::string> vec = {"A", "B", "C"}; + EXPECT_THAT(absl::StatusOr<std::vector<std::string>&>(vec), + IsOkAndHolds(ElementsAre("A", "B", "C"))); + EXPECT_THAT(absl::StatusOr<std::vector<std::string>&>(vec), + Not(IsOkAndHolds(ElementsAre("A", "X", "C")))); +} + } // namespace
diff --git a/absl/status/statusor.h b/absl/status/statusor.h index 25c6288..d2d16d5 100644 --- a/absl/status/statusor.h +++ b/absl/status/statusor.h
@@ -194,6 +194,11 @@ private internal_statusor::MoveCtorBase<T>, private internal_statusor::CopyAssignBase<T>, private internal_statusor::MoveAssignBase<T> { +#ifndef SWIG + static_assert(!std::is_rvalue_reference_v<T>, + "rvalue references are not yet supported."); +#endif // SWIG + template <typename U> friend class StatusOr; @@ -397,7 +402,7 @@ typename std::enable_if< internal_statusor::IsAssignmentValid<T, U, true>::value, int>::type = 0> - StatusOr& operator=(U&& v ABSL_ATTRIBUTE_LIFETIME_BOUND) { + StatusOr& operator=(U&& v ABSL_INTERNAL_ATTRIBUTE_CAPTURED_BY(this)) { this->Assign(std::forward<U>(v)); return *this; } @@ -520,8 +525,10 @@ // REQUIRES: `this->ok() == true`, otherwise the behavior is undefined. // // Use `this->ok()` to verify that there is a current value. - const T* absl_nonnull operator->() const ABSL_ATTRIBUTE_LIFETIME_BOUND; - T* absl_nonnull operator->() ABSL_ATTRIBUTE_LIFETIME_BOUND; + std::remove_reference_t<const T>* absl_nonnull operator->() const + ABSL_ATTRIBUTE_LIFETIME_BOUND; + std::remove_reference_t<T>* absl_nonnull operator->() + ABSL_ATTRIBUTE_LIFETIME_BOUND; // StatusOr<T>::value_or() // @@ -536,10 +543,34 @@ // // Unlike with `value`, calling `std::move()` on the result of `value_or` will // still trigger a copy. - template <typename U> - T value_or(U&& default_value) const&; - template <typename U> - T value_or(U&& default_value) &&; + template < + typename U, + std::enable_if_t<internal_statusor::IsValueOrValid<T, U&&, false>::value, + int> = 0> + T value_or(U&& default_value) const& { + return this->ValueOrImpl(std::forward<U>(default_value)); + } + template < + typename U, + std::enable_if_t<internal_statusor::IsValueOrValid<T, U&&, false>::value, + int> = 0> + T value_or(U&& default_value) && { + return std::move(*this).ValueOrImpl(std::forward<U>(default_value)); + } + template < + typename U, + std::enable_if_t<internal_statusor::IsValueOrValid<T, U&&, true>::value, + int> = 0> + T value_or(U&& default_value ABSL_ATTRIBUTE_LIFETIME_BOUND) const& { + return this->ValueOrImpl(std::forward<U>(default_value)); + } + template < + typename U, + std::enable_if_t<internal_statusor::IsValueOrValid<T, U&&, true>::value, + int> = 0> + T value_or(U&& default_value ABSL_ATTRIBUTE_LIFETIME_BOUND) && { + return std::move(*this).ValueOrImpl(std::forward<U>(default_value)); + } // StatusOr<T>::IgnoreError() // @@ -760,33 +791,13 @@ } template <typename T> -const T* absl_nonnull StatusOr<T>::operator->() const { - this->EnsureOk(); - return &this->data_; +std::remove_reference_t<const T>* absl_nonnull StatusOr<T>::operator->() const { + return std::addressof(**this); } template <typename T> -T* absl_nonnull StatusOr<T>::operator->() { - this->EnsureOk(); - return &this->data_; -} - -template <typename T> -template <typename U> -T StatusOr<T>::value_or(U&& default_value) const& { - if (ok()) { - return this->data_; - } - return std::forward<U>(default_value); -} - -template <typename T> -template <typename U> -T StatusOr<T>::value_or(U&& default_value) && { - if (ok()) { - return std::move(this->data_); - } - return std::forward<U>(default_value); +std::remove_reference_t<T>* absl_nonnull StatusOr<T>::operator->() { + return std::addressof(**this); } template <typename T>
diff --git a/absl/status/statusor_test.cc b/absl/status/statusor_test.cc index 17a3384..0004453 100644 --- a/absl/status/statusor_test.cc +++ b/absl/status/statusor_test.cc
@@ -16,6 +16,7 @@ #include <array> #include <cstddef> +#include <cstdint> #include <initializer_list> #include <map> #include <memory> @@ -1799,4 +1800,306 @@ EXPECT_THAT(absl::StrCat(print_me), error_matcher); } +TEST(StatusOr, SupportsReferenceTypes) { + int i = 1; + absl::StatusOr<int&> s = i; + EXPECT_EQ(&i, &*s); + *s = 10; + EXPECT_EQ(i, 10); +} + +TEST(StatusOr, ReferenceFromStatus) { + int i = 10; + absl::StatusOr<int&> s = i; + s = absl::InternalError("foo"); + EXPECT_EQ(s.status().message(), "foo"); + + absl::StatusOr<int&> s2 = absl::InternalError("foo2"); + EXPECT_EQ(s2.status().message(), "foo2"); +} + +TEST(StatusOr, SupportReferenceValueConstructor) { + int i = 1; + absl::StatusOr<int&> s = i; + absl::StatusOr<const int&> cs = i; + absl::StatusOr<const int&> cs2 = std::move(i); // `T&&` to `const T&` is ok. + + EXPECT_EQ(&i, &*s); + EXPECT_EQ(&i, &*cs); + + Derived d; + absl::StatusOr<const Base1&> b = d; + EXPECT_EQ(&d, &*b); + + // We disallow constructions that cause temporaries. + EXPECT_FALSE((std::is_constructible_v<absl::StatusOr<const int&>, double>)); + EXPECT_FALSE( + (std::is_constructible_v<absl::StatusOr<const int&>, const double&>)); + EXPECT_FALSE( + (std::is_constructible_v<absl::StatusOr<const absl::string_view&>, + std::string>)); + + // We disallow constructions with wrong reference. + EXPECT_FALSE((std::is_constructible_v<absl::StatusOr<int&>, int&&>)); + EXPECT_FALSE((std::is_constructible_v<absl::StatusOr<int&>, const int&>)); +} + +TEST(StatusOr, SupportReferenceConvertingConstructor) { + int i = 1; + absl::StatusOr<int&> s = i; + absl::StatusOr<const int&> cs = s; + + EXPECT_EQ(&i, &*s); + EXPECT_EQ(&i, &*cs); + + // The other direction is not allowed. + EXPECT_FALSE((std::is_constructible_v<absl::StatusOr<int&>, + absl::StatusOr<const int&>>)); + + Derived d; + absl::StatusOr<const Base1&> b = absl::StatusOr<const Derived&>(d); + EXPECT_EQ(&d, &*b); + + // The other direction is not allowed. + EXPECT_FALSE((std::is_constructible_v<absl::StatusOr<const Derived&>, + absl::StatusOr<const Base1&>>)); + + // We disallow conversions that cause temporaries. + EXPECT_FALSE((std::is_constructible_v<absl::StatusOr<const int&>, + absl::StatusOr<int>>)); + EXPECT_FALSE((std::is_constructible_v<absl::StatusOr<const int&>, + absl::StatusOr<double>>)); + EXPECT_FALSE((std::is_constructible_v<absl::StatusOr<const int&>, + absl::StatusOr<const double&>>)); + EXPECT_FALSE((std::is_constructible_v<absl::StatusOr<const double&>, + absl::StatusOr<const int&>>)); + EXPECT_FALSE( + (std::is_constructible_v<absl::StatusOr<const absl::string_view&>, + absl::StatusOr<std::string>>)); + + // We disallow constructions with wrong reference. + EXPECT_FALSE((std::is_constructible_v<absl::StatusOr<int&>, + absl::StatusOr<const int&>>)); +} + +TEST(StatusOr, SupportReferenceValueAssignment) { + int i = 1; + absl::StatusOr<int&> s = i; + absl::StatusOr<const int&> cs; + cs = i; + absl::StatusOr<const int&> cs2; + cs2 = std::move(i); // `T&&` to `const T&` is ok. + + EXPECT_EQ(&i, &*s); + EXPECT_EQ(&i, &*cs); + + Derived d; + absl::StatusOr<const Base1&> b; + b = d; + EXPECT_EQ(&d, &*b); + + // We disallow constructions that cause temporaries. + EXPECT_FALSE((std::is_assignable_v<absl::StatusOr<const int&>, double>)); + EXPECT_FALSE( + (std::is_assignable_v<absl::StatusOr<const int&>, const double&>)); + EXPECT_FALSE((std::is_assignable_v<absl::StatusOr<const absl::string_view&>, + std::string>)); + + // We disallow constructions with wrong reference. + EXPECT_FALSE((std::is_assignable_v<absl::StatusOr<int&>, int&&>)); + EXPECT_FALSE((std::is_assignable_v<absl::StatusOr<int&>, const int&>)); +} + +TEST(StatusOr, SupportReferenceConvertingAssignment) { + int i = 1; + absl::StatusOr<int&> s; + s = i; + absl::StatusOr<const int&> cs; + cs = s; + + EXPECT_EQ(&i, &*s); + EXPECT_EQ(&i, &*cs); + + // The other direction is not allowed. + EXPECT_FALSE( + (std::is_assignable_v<absl::StatusOr<int&>, absl::StatusOr<const int&>>)); + + Derived d; + absl::StatusOr<const Base1&> b; + b = absl::StatusOr<const Derived&>(d); + EXPECT_EQ(&d, &*b); + + // The other direction is not allowed. + EXPECT_FALSE((std::is_assignable_v<absl::StatusOr<const Derived&>, + absl::StatusOr<const Base1&>>)); + + // We disallow conversions that cause temporaries. + EXPECT_FALSE((std::is_assignable_v<absl::StatusOr<const int&>, + absl::StatusOr<const double&>>)); + EXPECT_FALSE((std::is_assignable_v<absl::StatusOr<const int&>, + absl::StatusOr<double>>)); + EXPECT_FALSE((std::is_assignable_v<absl::StatusOr<const absl::string_view&>, + absl::StatusOr<std::string>>)); + + // We disallow constructions with wrong reference. + EXPECT_FALSE( + (std::is_assignable_v<absl::StatusOr<int&>, absl::StatusOr<const int&>>)); +} + +TEST(StatusOr, SupportReferenceToNonReferenceConversions) { + int i = 17; + absl::StatusOr<int&> si = i; + absl::StatusOr<float> sf = si; + EXPECT_THAT(sf, IsOkAndHolds(17.)); + + i = 20; + sf = si; + EXPECT_THAT(sf, IsOkAndHolds(20.)); + + EXPECT_THAT(absl::StatusOr<int64_t>(absl::StatusOr<int&>(i)), + IsOkAndHolds(20)); + EXPECT_THAT(absl::StatusOr<int64_t>(absl::StatusOr<const int&>(i)), + IsOkAndHolds(20)); + + std::string str = "str"; + absl::StatusOr<std::string> sos = absl::StatusOr<std::string&>(str); + EXPECT_THAT(sos, IsOkAndHolds("str")); + str = "str2"; + EXPECT_THAT(sos, IsOkAndHolds("str")); + sos = absl::StatusOr<std::string&>(str); + EXPECT_THAT(sos, IsOkAndHolds("str2")); + + absl::StatusOr<absl::string_view> sosv = absl::StatusOr<std::string&>(str); + EXPECT_THAT(sosv, IsOkAndHolds("str2")); + str = "str3"; + sosv = absl::StatusOr<std::string&>(str); + EXPECT_THAT(sosv, IsOkAndHolds("str3")); + + absl::string_view view = "view"; + // This way it is constructible, but not convertible because + // string_view->string is explicit + EXPECT_THAT( + absl::StatusOr<std::string>(absl::StatusOr<absl::string_view&>(view)), + IsOkAndHolds("view")); +#if defined(ABSL_USES_STD_STRING_VIEW) + // The assignment doesn't work with normal absl::string_view because + // std::string doesn't know about it. + sos = absl::StatusOr<absl::string_view&>(view); + EXPECT_THAT(sos, IsOkAndHolds("view")); +#endif + + EXPECT_FALSE((std::is_convertible_v<absl::StatusOr<absl::string_view&>, + absl::StatusOr<std::string>>)); +} + +TEST(StatusOr, ReferenceOperatorStarAndArrow) { + std::string str = "Foo"; + absl::StatusOr<std::string&> s = str; + s->assign("Bar"); + EXPECT_EQ(str, "Bar"); + + *s = "Baz"; + EXPECT_EQ(str, "Baz"); + + const absl::StatusOr<std::string&> cs = str; + // Even if the StatusOr is const, the reference it gives is non-const so we + // can still assign. + *cs = "Finally"; + EXPECT_EQ(str, "Finally"); + + cs->clear(); + EXPECT_EQ(cs.value(), str); + EXPECT_EQ(str, ""); +} + +TEST(StatusOr, ReferenceValueOr) { + int i = 17; + absl::StatusOr<int&> si = i; + + int other = 20; + EXPECT_EQ(&i, &si.value_or(other)); + + si = absl::UnknownError(""); + EXPECT_EQ(&other, &si.value_or(other)); + + absl::StatusOr<const int&> csi = i; + EXPECT_EQ(&i, &csi.value_or(1)); + + const auto value_or_call = [](auto&& sor, auto&& v) + -> decltype(std::forward<decltype(sor)>(sor).value_or( + std::forward<decltype(v)>(v))) {}; + using Probe = decltype(value_or_call); + // Just to verify that Probe works as expected in the good cases. + EXPECT_TRUE((std::is_invocable_v<Probe, absl::StatusOr<const int&>, int&&>)); + // Causes temporary conversion. + EXPECT_FALSE( + (std::is_invocable_v<Probe, absl::StatusOr<const int&>, double&&>)); + // Const invalid. + EXPECT_FALSE((std::is_invocable_v<Probe, absl::StatusOr<int&>, const int&>)); +} + +TEST(StatusOr, ReferenceAssignmentFromStatusOr) { + std::vector<int> v = {1, 2, 3}; + absl::StatusOr<int&> si = v[0]; + absl::StatusOr<int&> si2 = v[1]; + + EXPECT_THAT(v, ElementsAre(1, 2, 3)); + EXPECT_THAT(si, IsOkAndHolds(1)); + EXPECT_THAT(si2, IsOkAndHolds(2)); + + // This rebinds the reference. + si = si2; + EXPECT_THAT(v, ElementsAre(1, 2, 3)); + EXPECT_THAT(si, IsOkAndHolds(2)); + EXPECT_THAT(si2, IsOkAndHolds(2)); + EXPECT_EQ(&*si, &*si2); +} + +TEST(StatusOr, ReferenceAssignFromReference) { + std::vector<int> v = {1, 2, 3}; + absl::StatusOr<int&> si = v[0]; + + EXPECT_THAT(v, ElementsAre(1, 2, 3)); + EXPECT_THAT(si, IsOkAndHolds(1)); + + // This rebinds the reference. + si = v[2]; + EXPECT_THAT(v, ElementsAre(1, 2, 3)); + EXPECT_THAT(si, IsOkAndHolds(3)); + EXPECT_EQ(&*si, &v[2]); +} + +template <typename Expected, typename T> +void TestReferenceDeref() { + static_assert(std::is_same_v<Expected, decltype(*std::declval<T>())>); + static_assert(std::is_same_v<Expected, decltype(std::declval<T>().value())>); +} + +TEST(StatusOr, ReferenceTypeIsMaintainedOnDeref) { + TestReferenceDeref<int&, absl::StatusOr<int&>&>(); + TestReferenceDeref<int&, absl::StatusOr<int&>&&>(); + TestReferenceDeref<int&, const absl::StatusOr<int&>&>(); + TestReferenceDeref<int&, const absl::StatusOr<int&>&&>(); + + TestReferenceDeref<const int&, absl::StatusOr<const int&>&>(); + TestReferenceDeref<const int&, absl::StatusOr<const int&>&&>(); + TestReferenceDeref<const int&, const absl::StatusOr<const int&>&>(); + TestReferenceDeref<const int&, const absl::StatusOr<const int&>&&>(); + + struct Struct { + int value; + }; + EXPECT_TRUE( + (std::is_same_v< + int&, decltype((std::declval<absl::StatusOr<Struct&>>()->value))>)); + EXPECT_TRUE( + (std::is_same_v< + int&, + decltype((std::declval<const absl::StatusOr<Struct&>>()->value))>)); + EXPECT_TRUE( + (std::is_same_v< + const int&, + decltype((std::declval<absl::StatusOr<const Struct&>>()->value))>)); +} + } // namespace