blob: 342012612129cf07bed36272084f67b8121815ab [file] [log] [blame]
// Copyright 2019 Google LLC
//
// 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
//
// https://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.
#ifndef THIRD_PARTY_MLIR_EDGE_IREE_BASE_INTERNAL_STATUSOR_H_
#define THIRD_PARTY_MLIR_EDGE_IREE_BASE_INTERNAL_STATUSOR_H_
#include "third_party/absl/base/attributes.h"
#include "third_party/mlir_edge/iree/base/internal/status.h"
#include "third_party/mlir_edge/iree/base/internal/status_builder.h"
namespace iree {
template <typename T>
class ABSL_MUST_USE_RESULT StatusOr;
namespace internal_statusor {
template <typename T, typename U>
using IsStatusOrConversionAmbiguous =
absl::disjunction<std::is_constructible<T, StatusOr<U>&>,
std::is_constructible<T, const StatusOr<U>&>,
std::is_constructible<T, StatusOr<U>&&>,
std::is_constructible<T, const StatusOr<U>&&>,
std::is_convertible<StatusOr<U>&, T>,
std::is_convertible<const StatusOr<U>&, T>,
std::is_convertible<StatusOr<U>&&, T>,
std::is_convertible<const StatusOr<U>&&, T>>;
template <typename T, typename U>
using IsStatusOrConversionAssigmentAmbiguous =
absl::disjunction<IsStatusOrConversionAmbiguous<T, U>,
std::is_assignable<T&, StatusOr<U>&>,
std::is_assignable<T&, const StatusOr<U>&>,
std::is_assignable<T&, StatusOr<U>&&>,
std::is_assignable<T&, const StatusOr<U>&&>>;
template <typename T, typename U>
struct IsAmbiguousStatusOrForInitialization
: // Strip const-value refs from type and check again, else false_type.
public absl::conditional_t<
std::is_same<absl::remove_cv_t<absl::remove_reference_t<U>>,
U>::value,
std::false_type,
IsAmbiguousStatusOrForInitialization<
T, absl::remove_cv_t<absl::remove_reference_t<U>>>> {};
template <typename T, typename U>
struct IsAmbiguousStatusOrForInitialization<T, StatusOr<U>>
: public IsStatusOrConversionAmbiguous<T, U> {};
template <typename T, typename U>
using IsStatusOrDirectInitializationAmbiguous = absl::disjunction<
std::is_same<StatusOr<T>, absl::remove_cv_t<absl::remove_reference_t<U>>>,
std::is_same<Status, absl::remove_cv_t<absl::remove_reference_t<U>>>,
std::is_same<StatusBuilder, absl::remove_cv_t<absl::remove_reference_t<U>>>,
std::is_same<absl::in_place_t,
absl::remove_cv_t<absl::remove_reference_t<U>>>,
IsAmbiguousStatusOrForInitialization<T, U>>;
template <typename T, typename U>
using IsStatusOrDirectInitializationValid = absl::disjunction<
// The is_same allows nested status ors to ignore this check iff same type.
std::is_same<T, absl::remove_cv_t<absl::remove_reference_t<U>>>,
absl::negation<IsStatusOrDirectInitializationAmbiguous<T, U>>>;
class Helper {
public:
static void HandleInvalidStatusCtorArg(Status*);
ABSL_ATTRIBUTE_NORETURN static void Crash(const Status& status);
};
// Construct an instance of T in `p` through placement new, passing Args... to
// the constructor.
// This abstraction is here mostly for the gcc performance fix.
template <typename T, typename... Args>
void PlacementNew(void* p, Args&&... args) {
#if defined(__GNUC__) && !defined(__clang__)
// Teach gcc that 'p' cannot be null, fixing code size issues.
if (p == nullptr) __builtin_unreachable();
#endif
new (p) T(std::forward<Args>(args)...);
}
// 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.
template <typename T>
class StatusOrData {
template <typename U>
friend class StatusOrData;
public:
StatusOrData() = delete;
StatusOrData(const StatusOrData& other) {
if (other.ok()) {
MakeValue(other.data_);
MakeStatus();
} else {
MakeStatus(other.status_);
}
}
StatusOrData(StatusOrData&& other) noexcept {
if (other.ok()) {
MakeValue(std::move(other.data_));
MakeStatus();
} else {
MakeStatus(std::move(other.status_));
}
}
template <typename U>
explicit StatusOrData(const StatusOrData<U>& other) {
if (other.ok()) {
MakeValue(other.data_);
MakeStatus();
} else {
MakeStatus(other.status_);
}
}
template <typename U>
explicit StatusOrData(StatusOrData<U>&& other) {
if (other.ok()) {
MakeValue(std::move(other.data_));
MakeStatus();
} else {
MakeStatus(std::move(other.status_));
}
}
template <typename... Args>
explicit StatusOrData(absl::in_place_t, Args&&... args)
: data_(std::forward<Args>(args)...) {
MakeStatus();
}
explicit StatusOrData(const T& value) : data_(value) { MakeStatus(); }
explicit StatusOrData(T&& value) : data_(std::move(value)) { MakeStatus(); }
explicit StatusOrData(const Status& status) : status_(status) {
EnsureNotOk();
}
explicit StatusOrData(Status&& status) : status_(status) { EnsureNotOk(); }
explicit StatusOrData(const StatusBuilder& builder) : status_(builder) {
EnsureNotOk();
}
explicit StatusOrData(StatusBuilder&& builder) : status_(std::move(builder)) {
EnsureNotOk();
}
StatusOrData& operator=(const StatusOrData& other) {
if (this == &other) return *this;
if (other.ok())
Assign(other.data_);
else
Assign(other.status_);
return *this;
}
StatusOrData& operator=(StatusOrData&& other) {
if (this == &other) return *this;
if (other.ok())
Assign(std::move(other.data_));
else
Assign(std::move(other.status_));
return *this;
}
~StatusOrData() {
if (ok()) {
status_.~Status();
data_.~T();
} else {
status_.~Status();
}
}
void Assign(const T& value) {
if (ok()) {
data_.~T();
MakeValue(value);
} else {
MakeValue(value);
status_ = OkStatus();
}
}
void Assign(T&& value) {
if (ok()) {
data_.~T();
MakeValue(std::move(value));
} else {
MakeValue(std::move(value));
status_ = OkStatus();
}
}
void Assign(const Status& status) {
Clear();
status_ = status;
EnsureNotOk();
}
void Assign(Status&& status) {
Clear();
status_ = std::move(status);
EnsureNotOk();
}
bool ok() const { return status_.ok(); }
protected:
// status_ will always be active after the constructor.
// Union to be able to initialize exactly how we need without waste.
// Eg. in the copy constructor we use the default constructor of Status in
// the ok() path to avoid an extra Ref call.
union {
Status status_;
};
// data_ is active iff status_.ok()==true
struct Dummy {};
union {
// 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_;
};
void Clear() {
if (ok()) data_.~T();
}
void EnsureOk() const {
if (!ok()) Helper::Crash(status_);
}
void EnsureNotOk() {
if (ok()) Helper::HandleInvalidStatusCtorArg(&status_);
}
// Construct the value (data_) through placement new with the passed arg.
template <typename Arg>
void MakeValue(Arg&& arg) {
internal_statusor::PlacementNew<T>(&dummy_, std::forward<Arg>(arg));
}
// Construct the status (status_) through placement new with the passed arg.
template <typename... Args>
void MakeStatus(Args&&... args) {
internal_statusor::PlacementNew<Status>(&status_,
std::forward<Args>(args)...);
}
};
// Helper base class to allow implicitly deleted constructors and assignment
// operations in StatusOr.
// TraitsBase will explicitly delete what it can't support and StatusOr will
// inherit that behavior implicitly.
template <bool Copy, bool Move>
struct TraitsBase {
TraitsBase() = default;
TraitsBase(const TraitsBase&) = default;
TraitsBase(TraitsBase&&) = default;
TraitsBase& operator=(const TraitsBase&) = default;
TraitsBase& operator=(TraitsBase&&) = default;
};
template <>
struct TraitsBase<false, true> {
TraitsBase() = default;
TraitsBase(const TraitsBase&) = delete;
TraitsBase(TraitsBase&&) = default;
TraitsBase& operator=(const TraitsBase&) = delete;
TraitsBase& operator=(TraitsBase&&) = default;
};
template <>
struct TraitsBase<false, false> {
TraitsBase() = default;
TraitsBase(const TraitsBase&) = delete;
TraitsBase(TraitsBase&&) = delete;
TraitsBase& operator=(const TraitsBase&) = delete;
TraitsBase& operator=(TraitsBase&&) = delete;
};
} // namespace internal_statusor
// StatusOr<T> is the union of a Status object and a T object.
//
// A StatusOr object either holds a usable value, or an error Status explaining
// why such a value is not present.
template <typename T>
class StatusOr : private internal_statusor::StatusOrData<T>,
private internal_statusor::TraitsBase<
std::is_copy_constructible<T>::value,
std::is_move_constructible<T>::value> {
template <typename U>
friend class StatusOr;
typedef internal_statusor::StatusOrData<T> Base;
public:
typedef T element_type;
// Constructs a new StatusOr with StatusCode::kUnknown status.
explicit StatusOr();
// StatusOr<T> is copy constructible/assignable if T is copy constructible.
StatusOr(const StatusOr&) = default;
StatusOr& operator=(const StatusOr&) = default;
// StatusOr<T> is move constructible/assignable if T is move constructible.
StatusOr(StatusOr&&) = default;
StatusOr& operator=(StatusOr&&) = default;
// Converting constructors from StatusOr<U>, when T is constructible from U.
// To avoid ambiguity, they are disabled if T is also constructible from
// StatusOr<U>. Explicit iff the corresponding construction of T from U is
// explicit.
template <
typename U,
absl::enable_if_t<
absl::conjunction<
absl::negation<std::is_same<T, U>>,
std::is_constructible<T, const U&>,
std::is_convertible<const U&, T>,
absl::negation<internal_statusor::IsStatusOrConversionAmbiguous<
T, U>>>::value,
int> = 0>
StatusOr(const StatusOr<U>& other) // NOLINT
: Base(static_cast<const typename StatusOr<U>::Base&>(other)) {}
template <
typename U,
absl::enable_if_t<
absl::conjunction<
absl::negation<std::is_same<T, U>>,
std::is_constructible<T, const U&>,
absl::negation<std::is_convertible<const U&, T>>,
absl::negation<internal_statusor::IsStatusOrConversionAmbiguous<
T, U>>>::value,
int> = 0>
explicit StatusOr(const StatusOr<U>& other)
: Base(static_cast<const typename StatusOr<U>::Base&>(other)) {}
template <
typename U,
absl::enable_if_t<
absl::conjunction<
absl::negation<std::is_same<T, U>>, std::is_constructible<T, U&&>,
std::is_convertible<U&&, T>,
absl::negation<internal_statusor::IsStatusOrConversionAmbiguous<
T, U>>>::value,
int> = 0>
StatusOr(StatusOr<U>&& other) // NOLINT
: Base(static_cast<typename StatusOr<U>::Base&&>(other)) {}
template <
typename U,
absl::enable_if_t<
absl::conjunction<
absl::negation<std::is_same<T, U>>, std::is_constructible<T, U&&>,
absl::negation<std::is_convertible<U&&, T>>,
absl::negation<internal_statusor::IsStatusOrConversionAmbiguous<
T, U>>>::value,
int> = 0>
explicit StatusOr(StatusOr<U>&& other)
: Base(static_cast<typename StatusOr<U>::Base&&>(other)) {}
// Conversion copy/move assignment operator, T must be constructible and
// assignable from U. Only enable if T cannot be directly assigned from
// StatusOr<U>.
template <
typename U,
absl::enable_if_t<
absl::conjunction<
absl::negation<std::is_same<T, U>>,
std::is_constructible<T, const U&>,
std::is_assignable<T, const U&>,
absl::negation<
internal_statusor::IsStatusOrConversionAssigmentAmbiguous<
T, U>>>::value,
int> = 0>
StatusOr& operator=(const StatusOr<U>& other) {
this->Assign(other);
return *this;
}
template <
typename U,
absl::enable_if_t<
absl::conjunction<
absl::negation<std::is_same<T, U>>, std::is_constructible<T, U&&>,
std::is_assignable<T, U&&>,
absl::negation<
internal_statusor::IsStatusOrConversionAssigmentAmbiguous<
T, U>>>::value,
int> = 0>
StatusOr& operator=(StatusOr<U>&& other) {
this->Assign(std::move(other));
return *this;
}
// Constructs a new StatusOr with the given value. After calling this
// constructor, this->ok() will be true and the contained value may be
// retrieved with ValueOrDie(), operator*(), or operator->().
StatusOr(const T& value);
// Constructs a new StatusOr with the given non-ok status. After calling this
// constructor, this->ok() will be false and calls to ValueOrDie() will
// CHECK-fail.
StatusOr(const Status& status);
StatusOr& operator=(const Status& status);
StatusOr(const StatusBuilder& builder);
StatusOr& operator=(const StatusBuilder& builder);
// Similar to the `const T&` overload.
//
// REQUIRES: T is move constructible.
StatusOr(T&& value);
// RValue versions of the operations declared above.
StatusOr(Status&& status);
StatusOr& operator=(Status&& status);
StatusOr(StatusBuilder&& builder);
StatusOr& operator=(StatusBuilder&& builder);
// Constructs the inner value T in-place using the provided args, using the
// T(args...) constructor.
template <typename... Args>
explicit StatusOr(absl::in_place_t, Args&&... args);
template <typename U, typename... Args>
explicit StatusOr(absl::in_place_t, std::initializer_list<U> ilist,
Args&&... args);
// Constructs the inner value T in-place using the provided args, using the
// T(U) (direct-initialization) constructor. Only valid if T can be
// constructed from a U. Can accept move or copy constructors. Explicit it
// U is not convertible to T. To avoid ambiguity, this is disabled if U is
// a StatusOr<J>, where J is convertible to T.
template <
typename U = T,
absl::enable_if_t<
absl::conjunction<
internal_statusor::IsStatusOrDirectInitializationValid<T, U&&>,
std::is_constructible<T, U&&>,
std::is_convertible<U&&, T>>::value,
int> = 0>
StatusOr(U&& u) // NOLINT
: StatusOr(absl::in_place, std::forward<U>(u)) {}
template <
typename U = T,
absl::enable_if_t<
absl::conjunction<
internal_statusor::IsStatusOrDirectInitializationValid<T, U&&>,
std::is_constructible<T, U&&>,
absl::negation<std::is_convertible<U&&, T>>>::value,
int> = 0>
explicit StatusOr(U&& u) // NOLINT
: StatusOr(absl::in_place, std::forward<U>(u)) {}
// Returns this->ok()
explicit operator bool() const { return ok(); }
// Returns this->status().ok()
ABSL_MUST_USE_RESULT bool ok() const { return this->status_.ok(); }
// Returns a reference to our status. If this contains a T, then
// returns OkStatus().
const Status& status() const&;
Status status() &&;
// Returns a reference to our current value, or CHECK-fails if !this->ok(). If
// you have already checked the status using this->ok() or operator bool(),
// then you probably want to use operator*() or operator->() to access the
// current value instead of ValueOrDie().
const T& ValueOrDie() const&;
T& ValueOrDie() &;
const T&& ValueOrDie() const&&;
T&& ValueOrDie() &&;
// Returns a reference to the current value.
//
// REQUIRES: this->ok() == true, otherwise the behavior is undefined.
const T& operator*() const&;
T& operator*() &;
const T&& operator*() const&&;
T&& operator*() &&;
// Returns a pointer to the current value.
//
// REQUIRES: this->ok() == true, otherwise the behavior is undefined.
const T* operator->() const;
T* operator->();
// Returns a copy of the current value if this->ok() == true. Otherwise
// returns a default value.
template <typename U>
T value_or(U&& default_value) const&;
template <typename U>
T value_or(U&& default_value) &&;
// Ignores any errors. This method does nothing except potentially suppress
// complaints from any tools that are checking that errors are not dropped on
// the floor.
void IgnoreError() const;
private:
using internal_statusor::StatusOrData<T>::Assign;
template <typename U>
void Assign(const StatusOr<U>& other);
template <typename U>
void Assign(StatusOr<U>&& other);
};
////////////////////////////////////////////////////////////////////////////////
// Implementation details for StatusOr<T>
template <typename T>
StatusOr<T>::StatusOr() : Base(Status(StatusCode::kUnknown, "")) {}
template <typename T>
StatusOr<T>::StatusOr(const T& value) : Base(value) {}
template <typename T>
StatusOr<T>::StatusOr(const Status& status) : Base(status) {}
template <typename T>
StatusOr<T>::StatusOr(const StatusBuilder& builder) : Base(builder) {}
template <typename T>
StatusOr<T>& StatusOr<T>::operator=(const Status& status) {
this->Assign(status);
return *this;
}
template <typename T>
StatusOr<T>& StatusOr<T>::operator=(const StatusBuilder& builder) {
return *this = static_cast<Status>(builder);
}
template <typename T>
StatusOr<T>::StatusOr(T&& value) : Base(std::move(value)) {}
template <typename T>
StatusOr<T>::StatusOr(Status&& status) : Base(std::move(status)) {}
template <typename T>
StatusOr<T>::StatusOr(StatusBuilder&& builder) : Base(std::move(builder)) {}
template <typename T>
StatusOr<T>& StatusOr<T>::operator=(Status&& status) {
this->Assign(std::move(status));
return *this;
}
template <typename T>
StatusOr<T>& StatusOr<T>::operator=(StatusBuilder&& builder) {
return *this = static_cast<Status>(std::move(builder));
}
template <typename T>
template <typename U>
inline void StatusOr<T>::Assign(const StatusOr<U>& other) {
if (other.ok()) {
this->Assign(other.ValueOrDie());
} else {
this->Assign(other.status());
}
}
template <typename T>
template <typename U>
inline void StatusOr<T>::Assign(StatusOr<U>&& other) {
if (other.ok()) {
this->Assign(std::move(other).ValueOrDie());
} else {
this->Assign(std::move(other).status());
}
}
template <typename T>
template <typename... Args>
StatusOr<T>::StatusOr(absl::in_place_t, Args&&... args)
: Base(absl::in_place, std::forward<Args>(args)...) {}
template <typename T>
template <typename U, typename... Args>
StatusOr<T>::StatusOr(absl::in_place_t, std::initializer_list<U> ilist,
Args&&... args)
: Base(absl::in_place, ilist, std::forward<Args>(args)...) {}
template <typename T>
const Status& StatusOr<T>::status() const& {
return this->status_;
}
template <typename T>
Status StatusOr<T>::status() && {
return ok() ? OkStatus() : std::move(this->status_);
}
template <typename T>
const T& StatusOr<T>::ValueOrDie() const& {
this->EnsureOk();
return this->data_;
}
template <typename T>
T& StatusOr<T>::ValueOrDie() & {
this->EnsureOk();
return this->data_;
}
template <typename T>
const T&& StatusOr<T>::ValueOrDie() const&& {
this->EnsureOk();
return std::move(this->data_);
}
template <typename T>
T&& StatusOr<T>::ValueOrDie() && {
this->EnsureOk();
return std::move(this->data_);
}
template <typename T>
const T& StatusOr<T>::operator*() const& {
this->EnsureOk();
return this->data_;
}
template <typename T>
T& StatusOr<T>::operator*() & {
this->EnsureOk();
return this->data_;
}
template <typename T>
const T&& StatusOr<T>::operator*() const&& {
this->EnsureOk();
return std::move(this->data_);
}
template <typename T>
T&& StatusOr<T>::operator*() && {
this->EnsureOk();
return std::move(this->data_);
}
template <typename T>
const T* StatusOr<T>::operator->() const {
this->EnsureOk();
return &this->data_;
}
template <typename T>
T* 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);
}
template <typename T>
void StatusOr<T>::IgnoreError() const {
// no-op
}
} // namespace iree
#endif // THIRD_PARTY_MLIR_EDGE_IREE_BASE_INTERNAL_STATUSOR_H_