status.h 6.07 KB
//===--- 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 <cassert>
#include <string>

// 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 <typename T> 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 <typename U>
  Expected(const Expected<U> &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 <typename U> Expected(Expected<U> &&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 <typename U> friend class Expected;

  State TheState;

  union {
    T TheValue;
    Status TheError;
  };
};

} // namespace acxxel

#endif // ACXXEL_STATUS_H