blob: afc9389296ef042370cc6df1b381bff2662aa340 [file] [log] [blame]
// Copyright 2020 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 LIBTEXTCLASSIFIER_UTILS_BASE_STATUSOR_H_
#define LIBTEXTCLASSIFIER_UTILS_BASE_STATUSOR_H_
#include <type_traits>
#include <utility>
#include "utils/base/logging.h"
#include "utils/base/macros.h"
#include "utils/base/status.h"
namespace libtextclassifier3 {
// A StatusOr holds a Status (in the case of an error), or a value T.
template <typename T>
class StatusOr {
public:
// Has status UNKNOWN.
inline StatusOr();
// Builds from a non-OK status. Crashes if an OK status is specified.
inline StatusOr(const Status& status); // NOLINT
// Builds from the specified value.
inline StatusOr(const T& value); // NOLINT
inline StatusOr(T&& value); // NOLINT
// Copy constructor.
inline StatusOr(const StatusOr& other);
// Move constructor.
inline StatusOr(StatusOr&& other);
// Conversion copy constructor, T must be copy constructible from U.
template <typename U,
std::enable_if_t<
std::conjunction<std::negation<std::is_same<T, U>>,
std::is_constructible<T, const U&>,
std::is_convertible<const U&, T>>::value,
int> = 0>
inline StatusOr(const StatusOr<U>& other); // NOLINT
// Conversion move constructor, T must by move constructible from U.
template <
typename U,
std::enable_if_t<std::conjunction<std::negation<std::is_same<T, U>>,
std::is_constructible<T, U&&>,
std::is_convertible<U&&, T>>::value,
int> = 0>
inline StatusOr(StatusOr<U>&& other); // NOLINT
// Value conversion copy constructor, T must by copy constructible from U.
template <typename U,
std::enable_if_t<
std::conjunction<std::negation<std::is_same<T, U>>,
std::is_constructible<T, const U&>,
std::is_convertible<const U&, T>>::value,
int> = 0>
inline StatusOr(const U& value); // NOLINT
// Value conversion move constructor, T must by move constructible from U.
template <
typename U,
std::enable_if_t<std::conjunction<std::negation<std::is_same<T, U>>,
std::is_constructible<T, U&&>,
std::is_convertible<U&&, T>>::value,
int> = 0>
inline StatusOr(U&& value); // NOLINT
// Assignment operator.
inline StatusOr& operator=(const StatusOr& other);
inline StatusOr& operator=(StatusOr&& other);
// Conversion assignment operator, T must be assignable from U
template <typename U>
inline StatusOr& operator=(const StatusOr<U>& other);
template <typename U>
inline StatusOr& operator=(StatusOr<U>&& other);
inline ~StatusOr();
// Accessors.
inline const Status& status() const& { return status_; }
inline Status status() && { return std::move(status_); }
// Shorthand for status().ok().
inline bool ok() const { return status_.ok(); }
// Returns value or crashes if ok() is false.
inline const T& ValueOrDie() const& {
if (!ok()) {
TC3_LOG(FATAL) << "Attempting to fetch value of non-OK StatusOr: "
<< status();
exit(1);
}
return value_;
}
inline T& ValueOrDie() & {
if (!ok()) {
TC3_LOG(FATAL) << "Attempting to fetch value of non-OK StatusOr: "
<< status();
exit(1);
}
return value_;
}
inline const T&& ValueOrDie() const&& {
if (!ok()) {
TC3_LOG(FATAL) << "Attempting to fetch value of non-OK StatusOr: "
<< status();
exit(1);
}
return std::move(value_);
}
inline T&& ValueOrDie() && {
if (!ok()) {
TC3_LOG(FATAL) << "Attempting to fetch value of non-OK StatusOr: "
<< status();
exit(1);
}
return std::move(value_);
}
template <typename U>
friend class StatusOr;
private:
void Clear() {
if (ok()) {
value_.~T();
}
}
// Construct the value through placement new with the passed argument.
template <typename... Arg>
void MakeValue(Arg&&... arg) {
new (&value_) T(std::forward<Arg>(arg)...);
}
// Creates a valid instance of type T constructed with U and assigns it to
// value_. Handles how to properly assign to value_ if value_ was never
// actually initialized (if this is currently non-OK).
template <typename U>
void AssignValue(U&& value) {
if (ok()) {
value_ = std::forward<U>(value);
} else {
MakeValue(std::forward<U>(value));
status_ = Status::OK;
}
}
// Creates a status constructed with U and assigns it to status_. It also
// properly destroys value_ if this is OK and value_ represents a valid
// instance of T.
template <typename U>
void AssignStatus(U&& v) {
Clear();
status_ = static_cast<Status>(std::forward<U>(v));
}
Status status_;
// The members of unions do not require initialization and are not destructed
// unless specifically called. This allows us to construct instances of
// StatusOr with only error statuses where T is not default constructible.
union {
// value_ is active iff status_.ok()==true
// WARNING: The destructor of value_ is called ONLY if status_ is OK.
T value_;
};
};
// Implementation.
template <typename T>
inline StatusOr<T>::StatusOr() : status_(StatusCode::UNKNOWN, "") {}
template <typename T>
inline StatusOr<T>::StatusOr(const Status& status) : status_(status) {
if (status.ok()) {
TC3_LOG(FATAL) << "OkStatus() is not a valid argument to StatusOr";
exit(1);
}
}
template <typename T>
inline StatusOr<T>::StatusOr(const T& value) : value_(value) {}
template <typename T>
inline StatusOr<T>::StatusOr(T&& value) : value_(std::move(value)) {}
template <typename T>
inline StatusOr<T>::StatusOr(const StatusOr& other)
: status_(other.status_), value_(other.value_) {}
template <typename T>
inline StatusOr<T>::StatusOr(StatusOr&& other)
: status_(other.status_), value_(std::move(other.value_)) {}
template <typename T>
template <
typename U,
std::enable_if_t<std::conjunction<std::negation<std::is_same<T, U>>,
std::is_constructible<T, const U&>,
std::is_convertible<const U&, T>>::value,
int>>
inline StatusOr<T>::StatusOr(const StatusOr<U>& other)
: status_(other.status_), value_(other.value_) {}
template <typename T>
template <typename U,
std::enable_if_t<std::conjunction<std::negation<std::is_same<T, U>>,
std::is_constructible<T, U&&>,
std::is_convertible<U&&, T>>::value,
int>>
inline StatusOr<T>::StatusOr(StatusOr<U>&& other)
: status_(other.status_), value_(std::move(other.value_)) {}
template <typename T>
template <
typename U,
std::enable_if_t<std::conjunction<std::negation<std::is_same<T, U>>,
std::is_constructible<T, const U&>,
std::is_convertible<const U&, T>>::value,
int>>
inline StatusOr<T>::StatusOr(const U& value) : StatusOr(T(value)) {}
template <typename T>
template <typename U,
std::enable_if_t<std::conjunction<std::negation<std::is_same<T, U>>,
std::is_constructible<T, U&&>,
std::is_convertible<U&&, T>>::value,
int>>
inline StatusOr<T>::StatusOr(U&& value) : StatusOr(T(std::forward<U>(value))) {}
template <typename T>
inline StatusOr<T>& StatusOr<T>::operator=(const StatusOr& other) {
if (other.ok()) {
AssignValue(other.value_);
} else {
AssignStatus(other.status_);
}
return *this;
}
template <typename T>
inline StatusOr<T>& StatusOr<T>::operator=(StatusOr&& other) {
if (other.ok()) {
AssignValue(std::move(other.value_));
} else {
AssignStatus(std::move(other.status_));
}
return *this;
}
template <typename T>
inline StatusOr<T>::~StatusOr() {
Clear();
}
template <typename T>
template <typename U>
inline StatusOr<T>& StatusOr<T>::operator=(const StatusOr<U>& other) {
if (other.ok()) {
AssignValue(other.value_);
} else {
AssignStatus(other.status_);
}
return *this;
}
template <typename T>
template <typename U>
inline StatusOr<T>& StatusOr<T>::operator=(StatusOr<U>&& other) {
if (other.ok()) {
AssignValue(std::move(other.value_));
} else {
AssignStatus(std::move(other.status_));
}
return *this;
}
} // namespace libtextclassifier3
#define TC3_ASSIGN_OR_RETURN(...) \
TC_STATUS_MACROS_IMPL_GET_VARIADIC_( \
(__VA_ARGS__, TC_STATUS_MACROS_IMPL_ASSIGN_OR_RETURN_3_, \
TC_STATUS_MACROS_IMPL_ASSIGN_OR_RETURN_2_)) \
(__VA_ARGS__)
#define TC3_ASSIGN_OR_RETURN_NULL(lhs, rexpr) \
TC3_ASSIGN_OR_RETURN(lhs, rexpr, nullptr)
#define TC3_ASSIGN_OR_RETURN_FALSE(lhs, rexpr) \
TC3_ASSIGN_OR_RETURN(lhs, rexpr, false)
#define TC3_ASSIGN_OR_RETURN_0(...) \
TC_STATUS_MACROS_IMPL_GET_VARIADIC_( \
(__VA_ARGS__, TC_STATUS_MACROS_IMPL_ASSIGN_OR_RETURN_0_3_, \
TC_STATUS_MACROS_IMPL_ASSIGN_OR_RETURN_0_2_)) \
(__VA_ARGS__)
#define TC_STATUS_MACROS_IMPL_ASSIGN_OR_RETURN_0_2_(lhs, rexpr) \
TC3_ASSIGN_OR_RETURN(lhs, rexpr, 0)
#define TC_STATUS_MACROS_IMPL_ASSIGN_OR_RETURN_0_3_(lhs, rexpr, \
log_expression) \
TC3_ASSIGN_OR_RETURN(lhs, rexpr, (log_expression, 0))
// =================================================================
// == Implementation details, do not rely on anything below here. ==
// =================================================================
// Some builds do not support C++14 fully yet, using C++11 constexpr technique.
constexpr bool HasPossiblyConditionalOperator(const char* lhs, int index) {
return (index == -1 ? false
: (lhs[index] == '?'
? true
: HasPossiblyConditionalOperator(lhs, index - 1)));
}
// MSVC incorrectly expands variadic macros, splice together a macro call to
// work around the bug.
#define TC_STATUS_MACROS_IMPL_GET_VARIADIC_HELPER_(_1, _2, _3, NAME, ...) NAME
#define TC_STATUS_MACROS_IMPL_GET_VARIADIC_(args) \
TC_STATUS_MACROS_IMPL_GET_VARIADIC_HELPER_ args
#define TC_STATUS_MACROS_IMPL_ASSIGN_OR_RETURN_2_(lhs, rexpr) \
TC_STATUS_MACROS_IMPL_ASSIGN_OR_RETURN_3_(lhs, rexpr, _)
#define TC_STATUS_MACROS_IMPL_ASSIGN_OR_RETURN_3_(lhs, rexpr, \
error_expression) \
TC_STATUS_MACROS_IMPL_ASSIGN_OR_RETURN_( \
TC_STATUS_MACROS_IMPL_CONCAT_(_status_or_value, __COUNTER__), lhs, \
rexpr, error_expression)
#define TC_STATUS_MACROS_IMPL_ASSIGN_OR_RETURN_(statusor, lhs, rexpr, \
error_expression) \
auto statusor = (rexpr); \
if (!statusor.ok()) { \
::libtextclassifier3::Status _(std::move(statusor).status()); \
(void)_; /* error_expression is allowed to not use this variable */ \
return (error_expression); \
} \
{ \
static_assert(#lhs[0] != '(' || #lhs[sizeof(#lhs) - 2] != ')' || \
!HasPossiblyConditionalOperator(#lhs, sizeof(#lhs) - 2), \
"Identified potential conditional operator, consider not " \
"using ASSIGN_OR_RETURN"); \
} \
TC_STATUS_MACROS_IMPL_UNPARENTHESIZE_IF_PARENTHESIZED(lhs) = \
std::move(statusor).ValueOrDie()
// Internal helpers for macro expansion.
#define TC_STATUS_MACROS_IMPL_EAT(...)
#define TC_STATUS_MACROS_IMPL_REM(...) __VA_ARGS__
#define TC_STATUS_MACROS_IMPL_EMPTY()
// Internal helpers for emptyness arguments check.
#define TC_STATUS_MACROS_IMPL_IS_EMPTY_INNER(...) \
TC_STATUS_MACROS_IMPL_IS_EMPTY_INNER_I(__VA_ARGS__, 0, 1)
#define TC_STATUS_MACROS_IMPL_IS_EMPTY_INNER_I(e0, e1, is_empty, ...) is_empty
#define TC_STATUS_MACROS_IMPL_IS_EMPTY(...) \
TC_STATUS_MACROS_IMPL_IS_EMPTY_I(__VA_ARGS__)
#define TC_STATUS_MACROS_IMPL_IS_EMPTY_I(...) \
TC_STATUS_MACROS_IMPL_IS_EMPTY_INNER(_, ##__VA_ARGS__)
// Internal helpers for if statement.
#define TC_STATUS_MACROS_IMPL_IF_1(_Then, _Else) _Then
#define TC_STATUS_MACROS_IMPL_IF_0(_Then, _Else) _Else
#define TC_STATUS_MACROS_IMPL_IF(_Cond, _Then, _Else) \
TC_STATUS_MACROS_IMPL_CONCAT_(TC_STATUS_MACROS_IMPL_IF_, _Cond)(_Then, _Else)
// Expands to 1 if the input is parenthesized. Otherwise expands to 0.
#define TC_STATUS_MACROS_IMPL_IS_PARENTHESIZED(...) \
TC_STATUS_MACROS_IMPL_IS_EMPTY(TC_STATUS_MACROS_IMPL_EAT __VA_ARGS__)
// If the input is parenthesized, removes the parentheses. Otherwise expands to
// the input unchanged.
#define TC_STATUS_MACROS_IMPL_UNPARENTHESIZE_IF_PARENTHESIZED(...) \
TC_STATUS_MACROS_IMPL_IF( \
TC_STATUS_MACROS_IMPL_IS_PARENTHESIZED(__VA_ARGS__), \
TC_STATUS_MACROS_IMPL_REM, TC_STATUS_MACROS_IMPL_EMPTY()) \
__VA_ARGS__
// Internal helper for concatenating macro values.
#define TC_STATUS_MACROS_IMPL_CONCAT_INNER_(x, y) x##y
#define TC_STATUS_MACROS_IMPL_CONCAT_(x, y) \
TC_STATUS_MACROS_IMPL_CONCAT_INNER_(x, y)
#endif // LIBTEXTCLASSIFIER_UTILS_BASE_STATUSOR_H_