blob: f142c992c2179f6319f5ebce1b6543fa6babd64c [file]
/*
* 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 <typename T>
struct hb_result_ok_t
{
T value;
};
template <>
struct hb_result_ok_t<void> {};
template <typename E>
struct hb_result_err_t
{
E error;
template <typename OtherE>
constexpr bool operator == (const hb_result_err_t<OtherE>& other) const { return error == other.error; }
template <typename OtherE>
constexpr bool operator != (const hb_result_err_t<OtherE>& other) const { return error != other.error; }
};
template <typename X> struct _hb_is_result_tag : hb_false_type {};
template <typename X> struct _hb_is_result_tag<hb_result_ok_t<X>> : hb_true_type {};
template <typename X> struct _hb_is_result_tag<hb_result_err_t<X>> : hb_true_type {};
template <typename T>
static inline constexpr hb_result_ok_t<T&&> Ok (T&& v)
{
return hb_result_ok_t<T&&>{std::forward<T> (v)};
}
static inline constexpr hb_result_ok_t<void> Ok ()
{
return hb_result_ok_t<void>{};
}
template <typename E>
static inline constexpr hb_result_err_t<E&&> Err (E&& e)
{
return hb_result_err_t<E&&>{std::forward<E> (e)};
}
template <typename T, typename E>
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<T>::value &&
std::is_nothrow_move_constructible<E>::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<T>::value &&
std::is_nothrow_move_assignable<E>::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 <typename U,
hb_enable_if ((std::is_constructible<T, U>::value))>
hb_result_t (hb_result_ok_t<U>&& o) : is_ok_ (true)
{
new (std::addressof (val_)) T (std::move (o.value));
}
template <typename U,
hb_enable_if ((std::is_constructible<T, const U&>::value))>
hb_result_t (const hb_result_ok_t<U>& o) : is_ok_ (true)
{
new (std::addressof (val_)) T (o.value);
}
// Construct from Err(...) tag
template <typename F,
hb_enable_if ((std::is_constructible<E, F>::value))>
hb_result_t (hb_result_err_t<F>&& e) : is_ok_ (false)
{
new (std::addressof (err_)) E (std::move (e.error));
}
template <typename F,
hb_enable_if ((std::is_constructible<E, const F&>::value))>
hb_result_t (const hb_result_err_t<F>& 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 <typename F = E,
hb_enable_if ((hb_is_same (F, E) &&
!(hb_is_convertible (T, E) || hb_is_convertible (E, T))))>
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 <typename U = T,
hb_enable_if ((std::is_constructible<T, U>::value &&
!hb_is_same (hb_decay<U>, E) &&
!hb_is_same (hb_decay<U>, hb_result_t) &&
!_hb_is_result_tag<hb_decay<U>>::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<U> (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 <typename U>
T value_or (U&& default_value) const &
{
if (is_ok_) return val_;
return std::forward<U> (default_value);
}
template <typename U>
T value_or (U&& default_value) &&
{
if (is_ok_) return std::move (val_);
return std::forward<U> (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 <typename U>
bool operator == (const hb_result_ok_t<U>& o) const
{
return is_ok_ && val_ == o.value;
}
template <typename U>
bool operator != (const hb_result_ok_t<U>& o) const
{
return !(*this == o);
}
template <typename F>
bool operator == (const hb_result_err_t<F>& e) const
{
return !is_ok_ && err_ == e.error;
}
template <typename F>
bool operator != (const hb_result_err_t<F>& e) const
{
return !(*this == e);
}
template <typename U>
bool operator == (const U&) const = delete;
template <typename U>
bool operator != (const U&) const = delete;
};
// Partial specialization for void
template <typename E>
struct HB_NODISCARD_STRUCT hb_result_t<void, E>
{
private:
union
{
E err_;
};
bool is_ok_;
template <typename, typename> 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<void>) : 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<E>::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<E>::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 <typename F,
hb_enable_if ((std::is_constructible<E, F>::value))>
hb_result_t (hb_result_err_t<F>&& e) : is_ok_ (false)
{
new (std::addressof (err_)) E (std::move (e.error));
}
template <typename F,
hb_enable_if ((std::is_constructible<E, const F&>::value))>
hb_result_t (const hb_result_err_t<F>& 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<typename V>
static hb_result_t<void, E> 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<void, E>& o) const
{
return is_ok_ == o.is_ok_ && (is_ok_ || err_ == o.err_);
}
bool operator != (const hb_result_t<void, E>& o) const
{
return !(*this == o);
}
bool operator == (hb_result_ok_t<void>) const
{
return is_ok_;
}
bool operator != (hb_result_ok_t<void>) const
{
return !is_ok_;
}
template <typename F>
bool operator == (const hb_result_err_t<F>& e) const
{
return !is_ok_ && err_ == e.error;
}
template <typename F>
bool operator != (const hb_result_err_t<F>& e) const
{
return !(*this == e);
}
template <typename U>
bool operator == (const U&) const = delete;
template <typename U>
bool operator != (const U&) const = delete;
};
template <typename U, typename T, typename E>
static inline bool operator == (const hb_result_ok_t<U>& o, const hb_result_t<T, E>& r)
{
return r == o;
}
template <typename U, typename T, typename E>
static inline bool operator != (const hb_result_ok_t<U>& o, const hb_result_t<T, E>& r)
{
return r != o;
}
template <typename F, typename T, typename E>
static inline bool operator == (const hb_result_err_t<F>& e, const hb_result_t<T, E>& r)
{
return r == e;
}
template <typename F, typename T, typename E>
static inline bool operator != (const hb_result_err_t<F>& e, const hb_result_t<T, E>& 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 */