Files
idevice/cpp/include/idevice++/option.hpp
2025-08-29 14:19:28 -06:00

185 lines
7.1 KiB
C++

// 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 <cstdio>
#include <stdexcept>
#include <type_traits>
#include <utility>
namespace IdeviceFFI {
struct none_t {};
constexpr none_t None{};
template <typename T> class Option {
bool has_;
typename std::aligned_storage<sizeof(T), alignof(T)>::type storage_;
T* ptr() { return reinterpret_cast<T*>(&storage_); }
const T* ptr() const { return reinterpret_cast<const T*>(&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<T>::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<T>::value
&& std::is_nothrow_move_assignable<T>::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<T>::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 <typename F> T unwrap_or_else(F&& f) const& {
return has_ ? *ptr() : static_cast<T>(f());
}
template <typename F> T unwrap_or_else(F&& f) && {
return has_ ? std::move(*ptr()) : static_cast<T>(f());
}
// map
template <typename F>
auto map(F&& f) const -> Option<typename std::decay<decltype(f(*ptr()))>::type> {
using U = typename std::decay<decltype(f(*ptr()))>::type;
if (has_) {
return Option<U>(f(*ptr()));
}
return Option<U>(None);
}
};
// Helpers
template <typename T> inline Option<typename std::decay<T>::type> Some(T&& v) {
return Option<typename std::decay<T>::type>(std::forward<T>(v));
}
inline Option<void> Some() = delete; // no Option<void>
// template <typename T> inline Option<T> None() {
// return Option<T>(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