/* * Copyright (C) 2018 The Android Open Source Project * * 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. */ #ifndef LIBTEXTCLASSIFIER_UTILS_BASE_STATUSOR_H_ #define LIBTEXTCLASSIFIER_UTILS_BASE_STATUSOR_H_ #include #include #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 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 >, std::is_constructible, std::is_convertible>::value, int> = 0> inline StatusOr(const StatusOr& other); // NOLINT // Conversion move constructor, T must by move constructible from U. template < typename U, std::enable_if_t>, std::is_constructible, std::is_convertible>::value, int> = 0> inline StatusOr(StatusOr&& other); // NOLINT // Value conversion copy constructor, T must by copy constructible from U. template >, std::is_constructible, std::is_convertible>::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::is_constructible, std::is_convertible>::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 inline StatusOr& operator=(const StatusOr& other); template inline StatusOr& operator=(StatusOr&& 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 friend class StatusOr; private: void Clear() { if (ok()) { value_.~T(); } } // Construct the value through placement new with the passed argument. template void MakeValue(Arg&&... arg) { new (&value_) T(std::forward(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 void AssignValue(U&& value) { if (ok()) { value_ = std::forward(value); } else { MakeValue(std::forward(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 void AssignStatus(U&& v) { Clear(); status_ = static_cast(std::forward(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 inline StatusOr::StatusOr() : status_(StatusCode::UNKNOWN, "") {} template inline StatusOr::StatusOr(const Status& status) : status_(status) { if (status.ok()) { TC3_LOG(FATAL) << "OkStatus() is not a valid argument to StatusOr"; exit(1); } } template inline StatusOr::StatusOr(const T& value) : value_(value) {} template inline StatusOr::StatusOr(T&& value) : value_(std::move(value)) {} template inline StatusOr::StatusOr(const StatusOr& other) : status_(other.status_), value_(other.value_) {} template inline StatusOr::StatusOr(StatusOr&& other) : status_(other.status_), value_(std::move(other.value_)) {} template template < typename U, std::enable_if_t>, std::is_constructible, std::is_convertible>::value, int>> inline StatusOr::StatusOr(const StatusOr& other) : status_(other.status_), value_(other.value_) {} template template >, std::is_constructible, std::is_convertible>::value, int>> inline StatusOr::StatusOr(StatusOr&& other) : status_(other.status_), value_(std::move(other.value_)) {} template template < typename U, std::enable_if_t>, std::is_constructible, std::is_convertible>::value, int>> inline StatusOr::StatusOr(const U& value) : StatusOr(T(value)) {} template template >, std::is_constructible, std::is_convertible>::value, int>> inline StatusOr::StatusOr(U&& value) : StatusOr(T(std::forward(value))) {} template inline StatusOr& StatusOr::operator=(const StatusOr& other) { if (other.ok()) { AssignValue(other.value_); } else { AssignStatus(other.status_); } return *this; } template inline StatusOr& StatusOr::operator=(StatusOr&& other) { if (other.ok()) { AssignValue(std::move(other.value_)); } else { AssignStatus(std::move(other.status_)); } return *this; } template inline StatusOr::~StatusOr() { Clear(); } template template inline StatusOr& StatusOr::operator=(const StatusOr& other) { if (other.ok()) { AssignValue(other.value_); } else { AssignStatus(other.status_); } return *this; } template template inline StatusOr& StatusOr::operator=(StatusOr&& 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_