// So here's the thing, std::optional and friends weren't added until C++17. // Some consumers of this codebase aren't on C++17 yet, so this won't work. // Plus, as a professional Rust evangelist, it's my duty to place as many Rust // idioms into other languages as possible to give everyone a taste of greatness. // Required error handling is correct error handling. And they called me a mad man. // Heavily influced from https://github.com/oktal/result, thank you #pragma once #include #include #include #include namespace IdeviceFFI { struct none_t {}; constexpr none_t None{}; template class Option { bool has_; typename std::aligned_storage::type storage_; T* ptr() { return reinterpret_cast(&storage_); } const T* ptr() const { return reinterpret_cast(&storage_); } public: // None Option() noexcept : has_(false) {} Option(none_t) noexcept : has_(false) {} // Some Option(const T& v) : has_(true) { ::new (ptr()) T(v); } Option(T&& v) : has_(true) { ::new (ptr()) T(std::move(v)); } // Copy / move Option(const Option& o) : has_(o.has_) { if (has_) { ::new (ptr()) T(*o.ptr()); } } Option(Option&& o) noexcept(std::is_nothrow_move_constructible::value) : has_(o.has_) { if (has_) { ::new (ptr()) T(std::move(*o.ptr())); o.reset(); } } Option& operator=(Option o) noexcept(std::is_nothrow_move_constructible::value && std::is_nothrow_move_assignable::value) { swap(o); return *this; } ~Option() { reset(); } void reset() noexcept { if (has_) { ptr()->~T(); has_ = false; } } void swap(Option& other) noexcept(std::is_nothrow_move_constructible::value) { if (has_ && other.has_) { using std::swap; swap(*ptr(), *other.ptr()); } else if (has_ && !other.has_) { ::new (other.ptr()) T(std::move(*ptr())); other.has_ = true; reset(); } else if (!has_ && other.has_) { ::new (ptr()) T(std::move(*other.ptr())); has_ = true; other.reset(); } } // State bool is_some() const noexcept { return has_; } bool is_none() const noexcept { return !has_; } // Unwraps (ref-qualified) T& unwrap() & { if (!has_) { throw std::runtime_error("unwrap on None"); } return *ptr(); } const T& unwrap() const& { if (!has_) { throw std::runtime_error("unwrap on None"); } return *ptr(); } T unwrap() && { if (!has_) { throw std::runtime_error("unwrap on None"); } T tmp = std::move(*ptr()); reset(); return tmp; } // unwrap_or / unwrap_or_else T unwrap_or(T default_value) const& { return has_ ? *ptr() : std::move(default_value); } T unwrap_or(T default_value) && { return has_ ? std::move(*ptr()) : std::move(default_value); } template T unwrap_or_else(F&& f) const& { return has_ ? *ptr() : static_cast(f()); } template T unwrap_or_else(F&& f) && { return has_ ? std::move(*ptr()) : static_cast(f()); } // map template auto map(F&& f) const -> Option::type> { using U = typename std::decay::type; if (has_) { return Option(f(*ptr())); } return Option(None); } }; // Helpers template inline Option::type> Some(T&& v) { return Option::type>(std::forward(v)); } inline Option Some() = delete; // no Option // template inline Option None() { // return Option(none); // } // still needs T specified // Prefer this at call sites (lets return-type drive the type): // return none; #define match_option(opt, SOME, NONE) \ if ((opt).is_some()) { \ auto&& SOME = (opt).unwrap(); #define or_else \ } \ else { \ NONE; \ } // --- Option helpers: if_let_some / if_let_some_move / if_let_none --- #define _opt_concat(a, b) a##b #define _opt_unique(base) _opt_concat(base, __LINE__) /* Bind a reference to the contained value if Some(...) */ #define if_let_some(expr, name, block) \ do { \ auto _opt_unique(_opt_) = (expr); \ if (_opt_unique(_opt_).is_some()) { \ auto&& name = _opt_unique(_opt_).unwrap(); \ block \ } \ } while (0) /* Move the contained value out (consumes the Option) if Some(...) */ #define if_let_some_move(expr, name, block) \ do { \ auto _opt_unique(_opt_) = (expr); \ if (_opt_unique(_opt_).is_some()) { \ auto name = std::move(_opt_unique(_opt_)).unwrap(); \ block \ } \ } while (0) /* Run a block if the option is None */ #define if_let_none(expr, block) \ do { \ auto _opt_unique(_opt_) = (expr); \ if (_opt_unique(_opt_).is_none()) { \ block \ } \ } while (0) } // namespace IdeviceFFI