Skip to content

Commit 2a2d6aa

Browse files
sbenzaquencopybara-github
authored andcommitted
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
1 parent 76a4803 commit 2a2d6aa

File tree

4 files changed

+476
-58
lines changed

4 files changed

+476
-58
lines changed

absl/status/internal/statusor_internal.h

Lines changed: 108 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -90,17 +90,34 @@ template <typename T, typename V>
9090
struct IsDirectInitializationAmbiguous<T, absl::StatusOr<V>>
9191
: public IsConstructibleOrConvertibleFromStatusOr<T, V> {};
9292

93+
// Checks whether the conversion from U to T can be done without dangling
94+
// temporaries.
95+
// REQUIRES: T and U are references.
96+
template <typename T, typename U>
97+
using IsReferenceConversionValid = absl::conjunction< //
98+
std::is_reference<T>, std::is_reference<U>,
99+
// The references are convertible. This checks for
100+
// lvalue/rvalue compatibility.
101+
std::is_convertible<U, T>,
102+
// The pointers are convertible. This checks we don't have
103+
// a temporary.
104+
std::is_convertible<std::remove_reference_t<U>*,
105+
std::remove_reference_t<T>*>>;
106+
93107
// Checks against the constraints of the direction initialization, i.e. when
94108
// `StatusOr<T>::StatusOr(U&&)` should participate in overload resolution.
95109
template <typename T, typename U>
96110
using IsDirectInitializationValid = absl::disjunction<
97111
// Short circuits if T is basically U.
98-
std::is_same<T, absl::remove_cvref_t<U>>,
99-
absl::negation<absl::disjunction<
100-
std::is_same<absl::StatusOr<T>, absl::remove_cvref_t<U>>,
101-
std::is_same<absl::Status, absl::remove_cvref_t<U>>,
102-
std::is_same<absl::in_place_t, absl::remove_cvref_t<U>>,
103-
IsDirectInitializationAmbiguous<T, U>>>>;
112+
std::is_same<T, absl::remove_cvref_t<U>>, //
113+
std::conditional_t<
114+
std::is_reference_v<T>, //
115+
IsReferenceConversionValid<T, U>,
116+
absl::negation<absl::disjunction<
117+
std::is_same<absl::StatusOr<T>, absl::remove_cvref_t<U>>,
118+
std::is_same<absl::Status, absl::remove_cvref_t<U>>,
119+
std::is_same<absl::in_place_t, absl::remove_cvref_t<U>>,
120+
IsDirectInitializationAmbiguous<T, U>>>>>;
104121

105122
// This trait detects whether `StatusOr<T>::operator=(U&&)` is ambiguous, which
106123
// is equivalent to whether all the following conditions are met:
@@ -140,7 +157,9 @@ using Equality = std::conditional_t<Value, T, absl::negation<T>>;
140157
template <bool Explicit, typename T, typename U, bool Lifetimebound>
141158
using IsConstructionValid = absl::conjunction<
142159
Equality<Lifetimebound,
143-
type_traits_internal::IsLifetimeBoundAssignment<T, U>>,
160+
absl::disjunction<
161+
std::is_reference<T>,
162+
type_traits_internal::IsLifetimeBoundAssignment<T, U>>>,
144163
IsDirectInitializationValid<T, U&&>, std::is_constructible<T, U&&>,
145164
Equality<!Explicit, std::is_convertible<U&&, T>>,
146165
absl::disjunction<
@@ -156,8 +175,13 @@ using IsConstructionValid = absl::conjunction<
156175
template <typename T, typename U, bool Lifetimebound>
157176
using IsAssignmentValid = absl::conjunction<
158177
Equality<Lifetimebound,
159-
type_traits_internal::IsLifetimeBoundAssignment<T, U>>,
160-
std::is_constructible<T, U&&>, std::is_assignable<T&, U&&>,
178+
absl::disjunction<
179+
std::is_reference<T>,
180+
type_traits_internal::IsLifetimeBoundAssignment<T, U>>>,
181+
std::conditional_t<std::is_reference_v<T>,
182+
IsReferenceConversionValid<T, U&&>,
183+
absl::conjunction<std::is_constructible<T, U&&>,
184+
std::is_assignable<T&, U&&>>>,
161185
absl::disjunction<
162186
std::is_same<T, absl::remove_cvref_t<U>>,
163187
absl::conjunction<
@@ -178,6 +202,9 @@ template <bool Explicit, typename T, typename U, bool Lifetimebound,
178202
typename UQ>
179203
using IsConstructionFromStatusOrValid = absl::conjunction<
180204
absl::negation<std::is_same<T, U>>,
205+
// If `T` is a reference, then U must be a compatible one.
206+
absl::disjunction<absl::negation<std::is_reference<T>>,
207+
IsReferenceConversionValid<T, U>>,
181208
Equality<Lifetimebound,
182209
type_traits_internal::IsLifetimeBoundAssignment<T, U>>,
183210
std::is_constructible<T, UQ>,
@@ -193,6 +220,16 @@ using IsStatusOrAssignmentValid = absl::conjunction<
193220
absl::negation<IsConstructibleOrConvertibleOrAssignableFromStatusOr<
194221
T, absl::remove_cvref_t<U>>>>;
195222

223+
template <typename T, typename U, bool Lifetimebound>
224+
using IsValueOrValid = absl::conjunction<
225+
// If `T` is a reference, then U must be a compatible one.
226+
absl::disjunction<absl::negation<std::is_reference<T>>,
227+
IsReferenceConversionValid<T, U>>,
228+
Equality<Lifetimebound,
229+
absl::disjunction<
230+
std::is_reference<T>,
231+
type_traits_internal::IsLifetimeBoundAssignment<T, U>>>>;
232+
196233
class Helper {
197234
public:
198235
// Move type-agnostic error handling to the .cc.
@@ -209,6 +246,26 @@ void PlacementNew(void* absl_nonnull p, Args&&... args) {
209246
new (p) T(std::forward<Args>(args)...);
210247
}
211248

249+
template <typename T>
250+
class Reference {
251+
public:
252+
constexpr explicit Reference(T ref ABSL_ATTRIBUTE_LIFETIME_BOUND)
253+
: payload_(std::addressof(ref)) {}
254+
255+
Reference(const Reference&) = default;
256+
Reference& operator=(const Reference&) = default;
257+
Reference& operator=(T value) {
258+
payload_ = std::addressof(value);
259+
return *this;
260+
}
261+
262+
operator T() const { return static_cast<T>(*payload_); } // NOLINT
263+
T get() const { return *this; }
264+
265+
private:
266+
std::remove_reference_t<T>* absl_nonnull payload_;
267+
};
268+
212269
// Helper base class to hold the data and all operations.
213270
// We move all this to a base class to allow mixing with the appropriate
214271
// TraitsBase specialization.
@@ -217,6 +274,14 @@ class StatusOrData {
217274
template <typename U>
218275
friend class StatusOrData;
219276

277+
decltype(auto) MaybeMoveData() {
278+
if constexpr (std::is_reference_v<T>) {
279+
return data_.get();
280+
} else {
281+
return std::move(data_);
282+
}
283+
}
284+
220285
public:
221286
StatusOrData() = delete;
222287

@@ -231,7 +296,7 @@ class StatusOrData {
231296

232297
StatusOrData(StatusOrData&& other) noexcept {
233298
if (other.ok()) {
234-
MakeValue(std::move(other.data_));
299+
MakeValue(other.MaybeMoveData());
235300
MakeStatus();
236301
} else {
237302
MakeStatus(std::move(other.status_));
@@ -251,7 +316,7 @@ class StatusOrData {
251316
template <typename U>
252317
explicit StatusOrData(StatusOrData<U>&& other) {
253318
if (other.ok()) {
254-
MakeValue(std::move(other.data_));
319+
MakeValue(other.MaybeMoveData());
255320
MakeStatus();
256321
} else {
257322
MakeStatus(std::move(other.status_));
@@ -264,13 +329,6 @@ class StatusOrData {
264329
MakeStatus();
265330
}
266331

267-
explicit StatusOrData(const T& value) : data_(value) {
268-
MakeStatus();
269-
}
270-
explicit StatusOrData(T&& value) : data_(std::move(value)) {
271-
MakeStatus();
272-
}
273-
274332
template <typename U,
275333
absl::enable_if_t<std::is_constructible<absl::Status, U&&>::value,
276334
int> = 0>
@@ -290,7 +348,7 @@ class StatusOrData {
290348
StatusOrData& operator=(StatusOrData&& other) {
291349
if (this == &other) return *this;
292350
if (other.ok())
293-
Assign(std::move(other.data_));
351+
Assign(other.MaybeMoveData());
294352
else
295353
AssignStatus(std::move(other.status_));
296354
return *this;
@@ -299,7 +357,9 @@ class StatusOrData {
299357
~StatusOrData() {
300358
if (ok()) {
301359
status_.~Status();
302-
data_.~T();
360+
if constexpr (!std::is_trivially_destructible_v<T>) {
361+
data_.~T();
362+
}
303363
} else {
304364
status_.~Status();
305365
}
@@ -340,11 +400,13 @@ class StatusOrData {
340400
// When T is const, we need some non-const object we can cast to void* for
341401
// the placement new. dummy_ is that object.
342402
Dummy dummy_;
343-
T data_;
403+
std::conditional_t<std::is_reference_v<T>, Reference<T>, T> data_;
344404
};
345405

346406
void Clear() {
347-
if (ok()) data_.~T();
407+
if constexpr (!std::is_trivially_destructible_v<T>) {
408+
if (ok()) data_.~T();
409+
}
348410
}
349411

350412
void EnsureOk() const {
@@ -359,7 +421,8 @@ class StatusOrData {
359421
// argument.
360422
template <typename... Arg>
361423
void MakeValue(Arg&&... arg) {
362-
internal_statusor::PlacementNew<T>(&dummy_, std::forward<Arg>(arg)...);
424+
internal_statusor::PlacementNew<decltype(data_)>(&dummy_,
425+
std::forward<Arg>(arg)...);
363426
}
364427

365428
// Construct the status (ie. status_) through placement new with the passed
@@ -369,6 +432,22 @@ class StatusOrData {
369432
internal_statusor::PlacementNew<Status>(&status_,
370433
std::forward<Args>(args)...);
371434
}
435+
436+
template <typename U>
437+
T ValueOrImpl(U&& default_value) const& {
438+
if (ok()) {
439+
return data_;
440+
}
441+
return std::forward<U>(default_value);
442+
}
443+
444+
template <typename U>
445+
T ValueOrImpl(U&& default_value) && {
446+
if (ok()) {
447+
return std::move(data_);
448+
}
449+
return std::forward<U>(default_value);
450+
}
372451
};
373452

374453
// Helper base classes to allow implicitly deleted constructors and assignment
@@ -411,8 +490,9 @@ struct MoveCtorBase<T, false> {
411490
MoveCtorBase& operator=(MoveCtorBase&&) = default;
412491
};
413492

414-
template <typename T, bool = std::is_copy_constructible<T>::value&&
415-
std::is_copy_assignable<T>::value>
493+
template <typename T, bool = (std::is_copy_constructible<T>::value &&
494+
std::is_copy_assignable<T>::value) ||
495+
std::is_reference_v<T>>
416496
struct CopyAssignBase {
417497
CopyAssignBase() = default;
418498
CopyAssignBase(const CopyAssignBase&) = default;
@@ -430,8 +510,9 @@ struct CopyAssignBase<T, false> {
430510
CopyAssignBase& operator=(CopyAssignBase&&) = default;
431511
};
432512

433-
template <typename T, bool = std::is_move_constructible<T>::value&&
434-
std::is_move_assignable<T>::value>
513+
template <typename T, bool = (std::is_move_constructible<T>::value &&
514+
std::is_move_assignable<T>::value) ||
515+
std::is_reference_v<T>>
435516
struct MoveAssignBase {
436517
MoveAssignBase() = default;
437518
MoveAssignBase(const MoveAssignBase&) = default;

absl/status/status_matchers_test.cc

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
#include "absl/status/status_matchers.h"
1919

2020
#include <string>
21+
#include <vector>
2122

2223
#include "gmock/gmock.h"
2324
#include "gtest/gtest-spi.h"
@@ -31,9 +32,12 @@ namespace {
3132
using ::absl_testing::IsOk;
3233
using ::absl_testing::IsOkAndHolds;
3334
using ::absl_testing::StatusIs;
35+
using ::testing::ElementsAre;
3436
using ::testing::Eq;
3537
using ::testing::Gt;
3638
using ::testing::MatchesRegex;
39+
using ::testing::Not;
40+
using ::testing::Ref;
3741

3842
TEST(StatusMatcherTest, StatusIsOk) { EXPECT_THAT(absl::OkStatus(), IsOk()); }
3943

@@ -158,4 +162,23 @@ TEST(StatusMatcherTest, StatusIsFailure) {
158162
"ungueltig");
159163
}
160164

165+
TEST(StatusMatcherTest, ReferencesWork) {
166+
int i = 17;
167+
int j = 19;
168+
EXPECT_THAT(absl::StatusOr<int&>(i), IsOkAndHolds(17));
169+
EXPECT_THAT(absl::StatusOr<int&>(i), Not(IsOkAndHolds(19)));
170+
EXPECT_THAT(absl::StatusOr<const int&>(i), IsOkAndHolds(17));
171+
172+
// Reference testing works as expected.
173+
EXPECT_THAT(absl::StatusOr<int&>(i), IsOkAndHolds(Ref(i)));
174+
EXPECT_THAT(absl::StatusOr<int&>(i), Not(IsOkAndHolds(Ref(j))));
175+
176+
// Try a more complex one.
177+
std::vector<std::string> vec = {"A", "B", "C"};
178+
EXPECT_THAT(absl::StatusOr<std::vector<std::string>&>(vec),
179+
IsOkAndHolds(ElementsAre("A", "B", "C")));
180+
EXPECT_THAT(absl::StatusOr<std::vector<std::string>&>(vec),
181+
Not(IsOkAndHolds(ElementsAre("A", "X", "C"))));
182+
}
183+
161184
} // namespace

0 commit comments

Comments
 (0)