/* * Copyright © 2025 Google, Inc. * * This is part of HarfBuzz, a text shaping library. * * Permission is hereby granted, without written agreement and without * license or royalty fees, to use, copy, modify, and distribute this * software and its documentation for any purpose, provided that the * above copyright notice and the following two paragraphs appear in * all copies of this software. * * IN NO EVENT SHALL THE COPYRIGHT HOLDER BE LIABLE TO ANY PARTY FOR * DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES * ARISING OUT OF THE USE OF THIS SOFTWARE AND ITS DOCUMENTATION, EVEN * IF THE COPYRIGHT HOLDER HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH * DAMAGE. * * THE COPYRIGHT HOLDER SPECIFICALLY DISCLAIMS ANY WARRANTIES, INCLUDING, * BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND * FITNESS FOR A PARTICULAR PURPOSE. THE SOFTWARE PROVIDED HEREUNDER IS * ON AN "AS IS" BASIS, AND THE COPYRIGHT HOLDER HAS NO OBLIGATION TO * PROVIDE MAINTENANCE, SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS. * * Google Author(s): Garret Rieger */ #ifndef HB_RESULT_HH #define HB_RESULT_HH #include "hb.hh" // Helper structs for Ok(...) and Err(...), not used directly // by users of hb_result_t. template struct hb_result_ok_t { T value; }; template <> struct hb_result_ok_t {}; template struct hb_result_err_t { E error; template constexpr bool operator == (const hb_result_err_t& other) const { return error == other.error; } template constexpr bool operator != (const hb_result_err_t& other) const { return error != other.error; } }; template struct _hb_is_result_tag : hb_false_type {}; template struct _hb_is_result_tag> : hb_true_type {}; template struct _hb_is_result_tag> : hb_true_type {}; template static inline constexpr hb_result_ok_t Ok (T&& v) { return hb_result_ok_t{std::forward (v)}; } static inline constexpr hb_result_ok_t Ok () { return hb_result_ok_t{}; } template static inline constexpr hb_result_err_t Err (E&& e) { return hb_result_err_t{std::forward (e)}; } template struct HB_NODISCARD_STRUCT hb_result_t { private: union { T val_; E err_; }; bool is_ok_; void destroy () { if (is_ok_) val_.~T (); else err_.~E (); } void init(const hb_result_t& o) { is_ok_ = o.is_ok_; if (is_ok_) new (std::addressof (val_)) T (o.val_); else new (std::addressof (err_)) E (o.err_); } void init(hb_result_t&& o) { is_ok_ = o.is_ok_; if (is_ok_) new (std::addressof (val_)) T (std::move (o.val_)); else new (std::addressof (err_)) E (std::move (o.err_)); } public: ~hb_result_t () { destroy (); } hb_result_t () = delete; hb_result_t (const hb_result_t& o) : is_ok_ (false) { init (o); } hb_result_t (hb_result_t&& o) noexcept (std::is_nothrow_move_constructible::value && std::is_nothrow_move_constructible::value) : is_ok_ (false) { init (std::move (o)); } hb_result_t& operator = (const hb_result_t& o) { if (this == &o) return *this; if (is_ok_ && o.is_ok_) { val_ = o.val_; } else if (!is_ok_ && !o.is_ok_) { err_ = o.err_; } else { destroy (); init (o); } return *this; } hb_result_t& operator = (hb_result_t&& o) noexcept (std::is_nothrow_move_assignable::value && std::is_nothrow_move_assignable::value) { if (this == &o) return *this; if (is_ok_ && o.is_ok_) { val_ = std::move (o.val_); } else if (!is_ok_ && !o.is_ok_) { err_ = std::move (o.err_); } else { destroy (); init (std::move (o)); } return *this; } // Construct from Ok(...) tag template ::value))> hb_result_t (hb_result_ok_t&& o) : is_ok_ (true) { new (std::addressof (val_)) T (std::move (o.value)); } template ::value))> hb_result_t (const hb_result_ok_t& o) : is_ok_ (true) { new (std::addressof (val_)) T (o.value); } // Construct from Err(...) tag template ::value))> hb_result_t (hb_result_err_t&& e) : is_ok_ (false) { new (std::addressof (err_)) E (std::move (e.error)); } template ::value))> hb_result_t (const hb_result_err_t& e) : is_ok_ (false) { new (std::addressof (err_)) E (e.error); } // Construct directly from error E (when neither T or E are convertible into each other) template hb_result_t (F e) : is_ok_ (false) { new (std::addressof (err_)) E (std::move (e)); } // Construct directly from value T (when neither T or E are convertible into each other) template ::value && !hb_is_same (hb_decay, E) && !hb_is_same (hb_decay, hb_result_t) && !_hb_is_result_tag>::value && !(hb_is_convertible (T, E) || hb_is_convertible (E, T))))> hb_result_t (U&& v) : is_ok_ (true) { new (std::addressof (val_)) T (std::forward (v)); } // Move fallible into a result if it's in_error() method returns false. static hb_result_t from (T&& fallible, E error_value) { if (fallible.in_error()) { return Err(error_value); } return Ok(fallible); } bool is_ok () const { return is_ok_; } bool is_err () const { return !is_ok_; } explicit operator bool () const { return is_ok (); } T& value () & { assert (is_ok_); return val_; } const T& value () const & { assert (is_ok_); return val_; } T value () && { assert (is_ok_); return std::move (val_); } T& operator * () & { return value (); } const T& operator * () const & { return value (); } T operator * () && { return std::move (*this).value (); } T* operator -> () { return std::addressof (value ()); } const T* operator -> () const { return std::addressof (value ()); } template T value_or (U&& default_value) const & { if (is_ok_) return val_; return std::forward (default_value); } template T value_or (U&& default_value) && { if (is_ok_) return std::move (val_); return std::forward (default_value); } T value_or_default () const & { if (is_ok_) return val_; return T (); } T value_or_default () && { if (is_ok_) return std::move (val_); return T (); } const E& error () const & { assert (!is_ok_); return err_; } E& error () & { assert (!is_ok_); return err_; } E error () && { assert (!is_ok_); return std::move (err_); } bool operator == (const hb_result_t& o) const { if (is_ok_ != o.is_ok_) return false; if (is_ok_) return val_ == o.val_; return err_ == o.err_; } bool operator != (const hb_result_t& o) const { return !(*this == o); } template bool operator == (const hb_result_ok_t& o) const { return is_ok_ && val_ == o.value; } template bool operator != (const hb_result_ok_t& o) const { return !(*this == o); } template bool operator == (const hb_result_err_t& e) const { return !is_ok_ && err_ == e.error; } template bool operator != (const hb_result_err_t& e) const { return !(*this == e); } template bool operator == (const U&) const = delete; template bool operator != (const U&) const = delete; }; // Partial specialization for void template struct HB_NODISCARD_STRUCT hb_result_t { private: union { E err_; }; bool is_ok_; template friend struct hb_result_t; public: ~hb_result_t () { if (!is_ok_) err_.~E (); } hb_result_t () : is_ok_ (true) {} hb_result_t (hb_result_ok_t) : is_ok_ (true) {} hb_result_t (const hb_result_t& o) : is_ok_ (o.is_ok_) { if (!is_ok_) new (std::addressof (err_)) E (o.err_); } hb_result_t (hb_result_t&& o) noexcept (std::is_nothrow_move_constructible::value) : is_ok_ (o.is_ok_) { if (!is_ok_) new (std::addressof (err_)) E (std::move (o.err_)); } hb_result_t& operator = (const hb_result_t& o) { if (this == &o) return *this; if (!is_ok_ && !o.is_ok_) { err_ = o.err_; } else if (!is_ok_ && o.is_ok_) { err_.~E (); is_ok_ = true; } else if (is_ok_ && !o.is_ok_) { new (std::addressof (err_)) E (o.err_); is_ok_ = false; } return *this; } hb_result_t& operator = (hb_result_t&& o) noexcept (std::is_nothrow_move_assignable::value) { if (this == &o) return *this; if (!is_ok_ && !o.is_ok_) { err_ = std::move (o.err_); } else if (!is_ok_ && o.is_ok_) { err_.~E (); is_ok_ = true; } else if (is_ok_ && !o.is_ok_) { new (std::addressof (err_)) E (std::move (o.err_)); is_ok_ = false; } return *this; } template ::value))> hb_result_t (hb_result_err_t&& e) : is_ok_ (false) { new (std::addressof (err_)) E (std::move (e.error)); } template ::value))> hb_result_t (const hb_result_err_t& e) : is_ok_ (false) { new (std::addressof (err_)) E (e.error); } // Construct directly from error E hb_result_t (E e) : is_ok_ (false) { new (std::addressof (err_)) E (std::move(e)); } // V is any class which has an in_error() method. This checks the result // of in_error() and returns either Ok() or Err(error_value). template static hb_result_t from (const V& fallible, E error_value) { if (fallible.in_error()) { return Err(error_value); } return Ok(); } bool is_ok () const { return is_ok_; } bool is_err () const { return !is_ok_; } explicit operator bool () const { return is_ok (); } const E& error () const & { assert (!is_ok_); return err_; } E& error () & { assert (!is_ok_); return err_; } E error () && { assert (!is_ok_); return std::move (err_); } void operator * () & { return; } void operator * () const & { return; } void operator * () && { return; } bool operator == (const hb_result_t& o) const { return is_ok_ == o.is_ok_ && (is_ok_ || err_ == o.err_); } bool operator != (const hb_result_t& o) const { return !(*this == o); } bool operator == (hb_result_ok_t) const { return is_ok_; } bool operator != (hb_result_ok_t) const { return !is_ok_; } template bool operator == (const hb_result_err_t& e) const { return !is_ok_ && err_ == e.error; } template bool operator != (const hb_result_err_t& e) const { return !(*this == e); } template bool operator == (const U&) const = delete; template bool operator != (const U&) const = delete; }; template static inline bool operator == (const hb_result_ok_t& o, const hb_result_t& r) { return r == o; } template static inline bool operator != (const hb_result_ok_t& o, const hb_result_t& r) { return r != o; } template static inline bool operator == (const hb_result_err_t& e, const hb_result_t& r) { return r == e; } template static inline bool operator != (const hb_result_err_t& e, const hb_result_t& r) { return r != e; } #ifndef TRY #define TRY(expr) \ do { \ auto _res = (expr); \ if (unlikely (!_res.is_ok())) return Err(std::move(_res).error()); \ } while (0) #endif #ifndef TRY_ASSIGN #define TRY_ASSIGN(var, ...) \ auto HB_PASTE(_res_, __LINE__) = (__VA_ARGS__); \ if (unlikely (!HB_PASTE(_res_, __LINE__).is_ok())) return Err(std::move(HB_PASTE(_res_, __LINE__)).error()); \ var = *std::move(HB_PASTE(_res_, __LINE__)) #endif #endif /* HB_RESULT_HH */