//===--- status.h - Status and Expected classes -----------------*- C++ -*-===// // // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. // See https://llvm.org/LICENSE.txt for license information. // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception // //===----------------------------------------------------------------------===// #ifndef ACXXEL_STATUS_H #define ACXXEL_STATUS_H #include #include // The clang compiler supports annotating class declarations with the // warn_unused_result attribute, and this has the meaning that whenever that // type is returned from a function, the function is marked as // warn_unused_result. // // The gcc compiler does not support warn_unused_result for classes, only for // functions, so we only use this feature with clang. #ifdef __clang__ #define ACXXEL_WARN_UNUSED_RESULT_TYPE __attribute__((warn_unused_result)) #else #define ACXXEL_WARN_UNUSED_RESULT_TYPE #endif namespace acxxel { /// Status type. /// /// May represent failure with a string error message, or may indicate success. class ACXXEL_WARN_UNUSED_RESULT_TYPE Status { public: /// Creates a Status representing success. Status() : HasMessage(false) {} /// Creates a Status representing failure with the given error message. explicit Status(const std::string &Message) : HasMessage(true), Message(Message) {} Status(const Status &) = default; Status &operator=(const Status &) = default; Status(Status &&) noexcept = default; // Cannot use default because the move assignment operator for std::string is // not marked noexcept. Status &operator=(Status &&That) noexcept { HasMessage = That.HasMessage; Message = std::move(That.Message); return *this; } ~Status() = default; /// Returns true if this Status represents failure. Otherwise, returns false. bool isError() const { return HasMessage; } /// Returns true if this Status represents success. Otherwise, returns false. operator bool() const { return !HasMessage; } /// Gets a reference to the error message for this Status. /// /// Should only be called if isError() returns true. const std::string &getMessage() const { return Message; } private: bool HasMessage; std::string Message; }; class ExpectedBase { protected: enum class State { SUCCESS, FAILURE, MOVED, }; }; /// Either a value of type T or a Status representing failure. template class Expected : public ExpectedBase { public: /// Creates an Expected representing failure with the given Error status. // Intentionally implicit. Expected(Status AnError) : TheState(State::FAILURE), TheError(std::move(AnError)) { assert(AnError.isError() && "constructing an error Expected value from a " "success status is not allowed"); } /// Creates an Expected representing success with the given value. // Intentionally implicit. Expected(T Value) : TheState(State::SUCCESS), TheValue(std::move(Value)) {} Expected(const Expected &That) : TheState(That.TheState) { switch (TheState) { case State::SUCCESS: new (&TheValue) T(That.TheValue); break; case State::FAILURE: new (&TheError) Status(That.TheError); break; case State::MOVED: // Nothing to do in this case. break; } } Expected &operator=(Expected That) { TheState = That.TheState; switch (TheState) { case State::SUCCESS: new (&TheValue) T(std::move(That.TheValue)); break; case State::FAILURE: new (&TheError) Status(std::move(That.TheError)); break; case State::MOVED: // Nothing to do in this case. break; } return *this; } Expected(Expected &&That) noexcept : TheState(That.TheState) { switch (TheState) { case State::SUCCESS: new (&TheValue) T(std::move(That.TheValue)); break; case State::FAILURE: new (&TheError) Status(std::move(That.TheError)); break; case State::MOVED: // Nothing to do in this case. break; } That.TheState = State::MOVED; } template Expected(const Expected &That) : TheState(That.TheState) { switch (TheState) { case State::SUCCESS: new (&TheValue) T(That.TheValue); break; case State::FAILURE: new (&TheError) Status(That.TheError); break; case State::MOVED: // Nothing to do in this case. break; } } template Expected(Expected &&That) : TheState(That.TheState) { switch (TheState) { case State::SUCCESS: new (&TheValue) T(std::move(That.TheValue)); break; case State::FAILURE: new (&TheError) Status(std::move(That.TheError)); break; case State::MOVED: // Nothing to do in this case. break; } } ~Expected() { switch (TheState) { case State::SUCCESS: TheValue.~T(); break; case State::FAILURE: TheError.~Status(); break; case State::MOVED: // Nothing to do for this case. break; } } /// Returns true if this instance represents failure. bool isError() const { return TheState != State::SUCCESS; } /// Gets a reference to the Status object. /// /// Should only be called if isError() returns true. const Status &getError() const { assert(isError()); return TheError; } /// Gets a const reference to the value object. /// /// Should only be called if isError() returns false. const T &getValue() const { assert(!isError()); return TheValue; } /// Gets a reference to the value object. /// /// Should only be called if isError() returns false. T &getValue() { assert(!isError()); return TheValue; } /// Takes the value from this object by moving it to the return value. /// /// Should only be called if isError() returns false. T takeValue() { assert(!isError()); TheState = State::MOVED; return std::move(TheValue); } private: template friend class Expected; State TheState; union { T TheValue; Status TheError; }; }; } // namespace acxxel #endif // ACXXEL_STATUS_H