outcome.h 4.16 KB
Newer Older
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170
#ifndef AIRMAP_OUTCOME_H_
#define AIRMAP_OUTCOME_H_

#include <type_traits>

namespace airmap {

/// Outcome models a return value from a function XOR an error object
/// describing the error condition if no value can be returned.
template <typename Value, typename Error>
class Outcome {
 public:
  /// @cond
  static_assert(not std::is_same<Value, Error>::value, "Value and Error must not be the same type");
  static_assert(std::is_copy_constructible<Value>::value && std::is_move_constructible<Value>::value,
                "Value must be copy- and move-constructible");
  static_assert(std::is_copy_constructible<Error>::value && std::is_move_constructible<Error>::value,
                "Error must be copy- and move-constructible");
  /// @endcond

  /// Outcome initializes a new instance with value.
  explicit Outcome(const Value& value) : type{Type::value} {
    new (&data.value) Value{value};
  }

  /// Outcome initializes a new instance with error.
  explicit Outcome(const Error& error) : type{Type::error} {
    new (&data.error) Error{error};
  }

  /// Outcome initializes a new instance with the value or error of 'other'.
  Outcome(const Outcome& other) : type{other.type} {
    switch (type) {
      case Type::error:
        new (&data.error) Error{other.data.error};
        break;
      case Type::value:
        new (&data.value) Value{other.data.value};
        break;
    }
  }

  /// Outcome initializes a new instance with the value or error of 'other'.
  Outcome(Outcome&& other) : type{other.type} {
    switch (type) {
      case Type::error:
        new (&data.error) Error{other.data.error};
        break;
      case Type::value:
        new (&data.value) Value{other.data.value};
        break;
    }
  }

  /// Outcome assigns the value or error contained in 'other' to this instance.
  Outcome& operator=(const Outcome& other) {
    switch (type) {
      case Type::error: {
        (&data.error)->~Error();
        break;
      }
      case Type::value: {
        (&data.value)->~Value();
        break;
      }
    }

    type = other.type;

    switch (type) {
      case Type::error: {
        new (&data.error) Error{other.data.error};
        break;
      }
      case Type::value: {
        new (&data.value) Value{other.data.value};
        break;
      }
    }

    return *this;
  }

  /// Outcome assigns the value or error contained in 'other' to this instance.
  Outcome& operator=(Outcome&& other) {
    switch (type) {
      case Type::error: {
        (&data.error)->~Error();
        break;
      }
      case Type::value: {
        (&data.value)->~Value();
        break;
      }
    }

    type = other.type;

    switch (type) {
      case Type::error: {
        new (&data.error) Error{other.data.error};
        break;
      }
      case Type::value: {
        new (&data.value) Value{other.data.value};
        break;
      }
    }

    return *this;
  }

  /// ~Outcome frees up the contained error or value contained in this instance.
  ~Outcome() {
    switch (type) {
      case Type::error:
        data.error.~Error();
        break;
      case Type::value:
        data.value.~Value();
        break;
    }
  }

  /// operator bool returns true if a value is contained in this instance.
  explicit operator bool() const {
    return !has_error();
  }

  /// has_error returns true if this instance carries an error.
  inline bool has_error() const {
    return type == Type::error;
  }

  /// has_value returns true if this instance carries a value.
  inline bool has_value() const {
    return type == Type::value;
  }

  /// error returns an immutable reference to the Error contained in this instance.
  /// The result of this call is undefined if has_error() returns false.
  inline const Error& error() const {
    return data.error;
  }

  /// value returns an immutable reference to the Value contained in this instance.
  /// The result of this call is undefined if has_value() returns false.
  inline const Value& value() const {
    return data.value;
  }

 private:
  enum class Type { value, error };

  Type type;
  union Data {
    Data() : value{} {
    }

    ~Data() {
    }

    Value value;
    Error error;
  } data;
};

}  // namespace airmap

#endif  // AIRMAP_OUTCOME_H_