Added `optional` (port from fuxx) for Option<T&>, etc.

Started `either`: `Cow<T>` Rust-like copy-on-write reference/owned-value wrapper.

Fortune for libexopt's current commit: Half curse − 半凶
boxed_is_boxed_value
Avril 1 year ago
parent 807008d965
commit 67c553903b
Signed by: flanchan
GPG Key ID: 284488987C31F630

@ -0,0 +1,144 @@
#pragma once
namespace exopt::types {
namespace either [[gnu::visibility("internal")]] {
//TODO: A version with std::shared_ptr<T> instead
template<typename T>//, typename P = T*>
class Cow final {
using var_t = std::variant<pointer_type // Borrowed
, value_type>; // Owned
public:
//TODO: Use pointer traits of P to deduce `pointer_type`, so we can have P be a shared_ptr too. Or perhaps, a specialisation for `Cow<std::shared_ptr<T>>` would be better
using value_type = std::remove_cvref_t<T>;
using reference_type = value_type &;
using const_reference_type = value_type const&;
using move_reference_type = value_type &&;
using pointer_type = value_type *;
using const_pointer_type = value_type const*;
constexpr Cow(std::add_rvalue_reference_t<T> value) noexcept(std::is_nothrow_constructible_v<value_type, decltype(value)>)
: m_value{std::move(value)} {}
constexpr explicit(std::is_convertible_v<std::add_rvalue_reference<T>, pointer_type>)
Cow(pointer_type p) noexcept
: m_value{p} {}
constexpr explicit Cow(std::nullptr_t) noexcept
: Cow(std::move(static_cast<pointer_type>(nullptr))) {}
constexpr Cow(Cow const& copy) noexcept
: m_value(copy.get()) {}
constexpr Cow(Cow&& move) noexcept(std::is_nothrow_move_constructible_v<value_type>)
: m_value(std::move(move.m_value)) { move.clear(); /* XXX: Is this needed? I think it is, to ensure that Cow<T> knows if it's been moved, even though std::variant already knows */ }
/// Return a newly copy-constructed owned object `T` from the referred (or held) value.
constexpr Cow clone() const noexcept(std::is_nothrow_copy_constructible_v<value_type>) requires(std::is_copy_constructible_v<T>)
{
if(const auto* pref = this->get())
return { *pref };
else return { nullptr };
}
//TODO: into_owned(){, const}, to_owned(){, const}{&, &&}, etc..
//TODO: Operator overloads for access to `value_type`: operator*, operator->, etc.
constexpr Cow(Cow&& move) noexcept(std::is_nothrow_move_constructible_v<value_type>) {
if(this != std::addressof(move)) {
if(__builtin_expect(move.is_empty(), false)) clear();
else {
m_value.emplace<value_type>(std::move(move).as_ref())
move.clear();
}
} return *this;
}
constexpr Cow(Cow const& copy) noexcept(std::is_nothrow_destructible_v<value_type>) {
if(this != std::addressof(copy)) {
m_value.emplace<pointer_type>(copy.get());
} return *this;
}
constexpr ~Cow() noexcept(std::is_nothrow_destructible_v<value_type>) = default;
constexpr move_reference_type as_ref() && {
std::visit([](auto&& arg) -> move_reference_type {
using U = std::remove_cvref_t<decltype(arg)>;
if constexpr(std::is_same_v<U, value_type)
return std::move(arg);
else return std::move(*arg);
}, m_value);
}
constexpr reference_type as_ref() & {
std::visit([](auto& arg) -> reference_type {
using U = std::remove_cvref_t<decltype(arg)>;
if constexpr(std::is_same_v<U, value_type)
return std::forward<decltype(arg)>(arg);
else return *arg;
}, m_value);
}
constexpr const_reference_type as_ref() const {
std::visit([](const auto& arg) -> const_reference_type {
using U = std::remove_cvref_t<decltype(arg)>;
if constexpr(std::is_same_v<U, value_type)
return std::forward<decltype(arg)>(arg);
else return *arg;
}, m_value);
}
//TODO: ^ is_empty() null-checks for `*arg`.
constexpr const_pointer_type get() const noexcept {
std::visit([](const auto& arg) -> const_pointer_type {
using U = std::remove_cvref_t<decltype(arg)>;
if constexpr(std::is_same_v<U, value_type)
return std::addressof(arg);
else return arg;
}, m_value);
}
constexpr pointer_type get() noexcept {
std::visit([](auto& arg) -> pointer_type {
using U = std::remove_cvref_t<decltype(arg)>;
if constexpr(std::is_same_v<U, value_type)
return std::addressof(arg);
else return arg;
}, m_value);
}
protected:
constexpr bool is_empty() const noexcept {
if(const const_pointer_type* p_ref = std::get_if<pointer_type>(&m_value))
if(!p_ref) return true;
return false;
}
constexpr void clear() noexcept(std::is_nothrow_destructible_v<value_type>) {
m_value.emplace<pointer_type>(nullptr);
}
private:
var_t m_value;
};
template<typename T>
class Cow<std::shared_ptr<T>> final {
using var_t = std::variant<
std::weak_ptr<value_type> // Borrowed
, std::shared_ptr<value_type>>; // Owned
public:
using value_type = std::remove_cvref_t<T>;
using reference_type = value_type &;
using const_reference_type = value_type const&;
using move_reference_type = value_type &&;
using pointer_type = value_type;
using const_pointer_type = value_type const*;
//TODO: AutoCow: See above comment about RC'd lifetimes with shallow coppies.
// How should we implement this... I think weak for borrowed and shared for owned is good, since we can still use shared_ptr's aliasing ctor to apply clone() (copy-construction) to the `value_type` directly while keeping the ref-count.
// XXX: Therfore, there should never be *any* strictly-aliasing variants both exposing the same `value_type`, despite *all* the variants managing the lifetime of the `value_type`. I think... Hnm... Can we get shared_ptr<T>'s aliasing constructor to *expose* a newly constructed `value_type` while managing the new one and the old one? XXX: Should we even manage the old one at all after a `clone()`? I actually don't think we should, since `clone()` should detach lifetime from the instance that it was called since it's a newly constructed (owned) object.
private:
var_t m_value;
};
}
/// Manually lifetime managed Cow<T>
using either::Cow;
/// RC-lifetime managed Cow<T>
template<typename T>
using AutoCow = Cow<std::shared_ptr<T>>;
}

@ -0,0 +1,31 @@
#pragma once
#include "exopt.h"
#include "optional.h"
#include "util.hh"
namespace exopt {
class Report {
virtual ~Report();
};
class Error {
constexpr Error() = default;
constexpr Error(Error const&) = default;
constexpr Error(Error &&) = default;
constexpr Error& operator=(Error &&) = default;
constexpr Error& operator=(Error const&) = default;
constexpr virtual std::string_view message() const noexcept =0;
constexpr virtual MaybeOwnedRef<Error> inner() noexcept { return {}; } //TODO: Maybe just use `std::optional<std::reference_wrapper<Error>>`
constexpr virtual MaybeOwned<Report> into_report() noexcept { /* TODO */ } // XXX: ^^
constexpr virtual MaybeOwned<Report> into_report() && noexcept { /* TODO: auto{*this}.into_report() */ }
constexpr virtual ~Error() = default;
protected:
private:
};
}

@ -1,6 +1,8 @@
#ifndef _EXOPT_H
#define _EXOPT_H
#define _EO__OPT_OLD_ASSUME //XXX: __attribute__((gnu::assume(X))) fails to build
#define _EXOPT_INTERNAL_PREFIX _exopt__
#ifdef __cplusplus
@ -27,7 +29,7 @@ extern "C" {
#if _EO(cpp)
# ifndef _EO_ASSUME
# define _EO_ASSUME(X) [[assume(X)]]
# define _EO_ASSUME(X) [[gnu::assume(X)]]
# endif
# define _exopt__restrict __restrict__
#else

@ -0,0 +1,548 @@
#pragma once
#include <utility>
#include <concepts>
#include "pointer.h"
/// A better std::optional
namespace exopt::types { namespace optional [[gnu::visibility("internal")]] {
template<typename T>
struct null_optimise;
template<typename T>
struct null_optimise<ptr::NonNull<T> > { constexpr static inline bool value = true;
using held_type = T*;
using type = ptr::NonNull<T>;
constexpr static decltype(auto) convert_to_held(std::add_rvalue_reference_t<type> t) noexcept { return t.get(); }
constexpr static decltype(auto) convert_from_held(std::add_rvalue_reference_t<held_type> t) noexcept { return ptr::NonNull<T>::new_unchecked(t); }
constexpr static inline auto sentinel = nullptr;
};
template<typename T>
struct null_optimise<T&> { constexpr static inline bool value = true;
using held_type = T*;
using type = T&;
constexpr static decltype(auto) convert_to_held(type t) noexcept { return std::addressof(t); }
constexpr static auto& convert_from_held(held_type h) noexcept { return static_cast<type>(*h); }
constexpr static inline auto sentinel = nullptr;
};
/* XXX: We can't optimise for this case with this model...
template<typename T> requires(sizeof(T&&) <= sizeof(T*))
struct null_optimise<T&&> { constexpr static inline bool value = true;
using held_type = T;
using type = T&&;
constexpr static decltype(auto) convert_to_held(type t) noexcept(std::is_nothrow_move_constructible_v<T>) { return std::move(t); }
constexpr static auto&& convert_from_held(held_type h) noexcept { return std::forward<type>(h); }
constexpr static inline auto sentinel = nullptr;
};*/
template<typename T>
struct null_optimise<T&&> { constexpr static inline bool value = true;//sizeof(T&&) == sizeof(T*);
using held_type = T*;
using type = T&&;
constexpr static decltype(auto) convert_to_held(type t) noexcept { return std::addressof(t); }
constexpr static auto&& convert_from_held(held_type h) noexcept { return std::forward<type>(*h); }
constexpr static inline auto sentinel = nullptr;
};
template<>
struct null_optimise<void> { constexpr static inline bool value = true;
using held_type = void;
using type = void;
};
template<typename T>
concept has_null_opt = std::is_void_v<T>
|| requires {
typename null_optimise<T>::type;
typename null_optimise<T>::held_type;
requires(null_optimise<T>::value);
/*requires(std::is_void_v<null_optimise<T>::held_type> || requires(std::add_rvalue_reference_t< null_optimise<T>::held_type> held) {
{ null_optimise<T>::sentinel == std::as_const(held) } -> std::convertible_to<bool>;
});*/
/*requires(null_optimise<T>::type unheld
, std::add_rvalue_reference_t<null_optimise<T>::held_type> held) {
//, const std::remove_reference_t<null_optimise<T>::held_type>& held_const) {
{ null_optimise<T>::sentinel } -> std::convertible_to<null_optmimise<T>::held_type>;
{ null_optimise<T>::convert_to_held(unheld) } -> std::convertible_to<null_optimise<T>::held_type>;
{ null_optimise<T>::convert_from_held(held) } -> std::convertible_to<null_optimise<T>::type>;
{ null_optimise<T>::sentinel == std::as_const(held) } -> std::convertible_to<bool>;
//{ null_optimise<T>::convert_from_held(held_const) } -> std::convertible_to<null_optimise<T>::type>;
};
requires(std::is_same_v<T, null_optimise<T>::type>);*/
};
namespace _null_opt_impl [[gnu::visibility("hidden")]] {
template<typename, bool> struct types_for;
template<typename T>
struct types_for<T, true> {
static_assert(has_null_opt<T>, "Type does not have nullptr optimisation enabled");
using held = null_optimise<T>::held_type;
using type = null_optimise<T>::type;
};
template<typename T>
struct types_for<T, false> {
using held=T;
using type=T;
};
template<typename T>
using held_type = typename types_for<T, has_null_opt<T>>::held;
template<typename T>
using used_type = typename types_for<T, has_null_opt<T>>::type;
}
namespace _details [[gnu::visibility("hidden")]] {
template<typename T>
struct not_void { using type = T; };
template<>
struct not_void<void> { struct type; };
template<typename T>
using not_void_t = typename not_void<T>::type;
template<typename T>
struct rv_ref_or_void { using type = std::add_rvalue_reference_t<T>; };
template<>
struct rv_ref_or_void<void> { struct type; };
template<typename T>
using rv_ref_t = typename rv_ref_or_void<T>::type;
template<typename T>
struct cv_ref_or_void { using type = const std::remove_reference_t<T>&; };
template<>
struct cv_ref_or_void<void> { struct _type; using type = const _type&; };
template<typename T>
using cv_ref_t = typename cv_ref_or_void<T>::type;
template<typename T>
struct const_ref_or_void { using type = const std::remove_const_t<T>&; };
template<>
struct const_ref_or_void<void> { struct type; };
template<typename T>
using const_ref_t = typename const_ref_or_void<T>::type;
template<typename T, typename U, bool>
struct if_then_else_type;
template<typename If, typename U>
struct if_then_else_type<If, U, true> { using type = If; using other = U; constexpr static inline bool result = true; };
template<typename T, typename Else>
struct if_then_else_type<T, Else, false> { using type = Else; using other = T; constexpr static inline bool result = false; };
template<bool Predicate, typename Then, typename Else>
using if_then_else_t = typename if_then_else_type<Then, Else, Predicate>::type;
template<typename T>
class any_ref_or_void {
template<typename _U = T, bool P = std::is_rvalue_reference_v<T>>
struct is_rv_ref_type;
template<typename U>
struct is_rv_ref_type<U, true> { using type = U; static_assert(std::is_rvalue_reference_v<type>, "Invalid reference"); };
template<typename U>
struct is_rv_ref_type<U, false> { using type = U&; static_assert(!std::is_rvalue_reference_v<U>, "Invalid reference"); };
public:
using type = is_rv_ref_type<T, std::is_rvalue_reference_v<T>>::type;
};
/* XXX: Having to account for all qualifiers is fucking dreck... we'd also need all variations of this with `__restrict__` too..
template<typename T>
struct any_ref_or_void<T&&> { using type = T&&; };
template<typename T>
struct any_ref_or_void<const T&&> { using type = const T&&; };
template<typename T>
struct any_ref_or_void<volatile T&&> { using type = volatile T&&; };
template<typename T>
struct any_ref_or_void<const volatile T&&> { using type = const volatile T&&; };
*/
template<>
struct any_ref_or_void<void> { struct type; };
template<typename T>
using any_ref_t = typename any_ref_or_void<T>::type;
template<typename T>
struct mut_ref_or_void { using type = any_ref_or_void<std::remove_const_t<T>>::type; };
template<>
struct mut_ref_or_void<void> { struct type; };
template<typename T>
using mut_ref_t = typename mut_ref_or_void<T>::type;
template<typename T> requires(requires(T v) { { v == nullptr } -> std::convertible_to<bool>; })
[[gnu::always_inline]]
constexpr decltype(auto) map_null(T&& value, auto&& with) noexcept(std::is_nothrow_invocable_v<decltype(with), decltype(std::forward<T>(value))>) { return (!bool(value == nullptr)) ? with(std::forward<T>(value)) : nullptr; }
template<typename T, typename U> requires(requires(T v) { { v == nullptr } -> std::convertible_to<bool>; })
[[gnu::always_inline]]
constexpr decltype(auto) map_null(T&& value, auto&& with, std::add_rvalue_reference_t<U> otherwise) noexcept(std::is_nothrow_invocable_v<decltype(with), decltype(std::forward<T>(value))> && (!std::is_invocable_v<U> || std::is_nothrow_invocable_v<decltype(otherwise)>)) {
if (!bool(value == nullptr)) return with(std::forward<T>(value));
else if constexpr(std::is_invocable_v<U>) return otherwise();
else return std::forward<decltype(otherwise)>(otherwise);
}
template<typename T, typename... Types>
constexpr inline bool is_same_any_v = (std::is_same_v<T, Types> || ...);
constexpr inline auto identity = [] <typename T> (T v) noexcept { return std::forward<T>(v); };
}
struct none_t {
constexpr none_t() noexcept = default;
constexpr ~none_t() noexcept = default;
constexpr none_t(std::nullptr_t) noexcept : none_t() {}
constexpr operator std::nullptr_t() const noexcept { return nullptr; }
};
constexpr inline none_t None;
template<typename T>
concept AsOption = (std::is_void_v<T>
|| requires{ requires(sizeof(T) == sizeof(T)); }
)
&& !_details::is_same_any_v<T, none_t, std::nullptr_t>;
struct OptionUnwrapError /*TODO : public virtual error::Error, virtual std::exception*/
{
constexpr static inline const auto MESSAGE = "Unwrap operation on a None value";
OptionUnwrapError() noexcept = default;
inline virtual ~OptionUnwrapError() noexcept {}
inline std::string_view message() const noexcept /*override*/ { return std::string_view{MESSAGE}; }
};
template<AsOption T>
struct Option final {
struct UnwrapError : public OptionUnwrapError
{
UnwrapError() noexcept =default;
inline UnwrapError(const UnwrapError&) noexcept : UnwrapError() {}
inline UnwrapError(UnwrapError&&) noexcept : UnwrapError() {}
inline virtual ~UnwrapError() noexcept {}
using OptionUnwrapError::message;
};
constexpr static inline bool is_null_optimised = has_null_opt<T>;
constexpr explicit Option(std::nullptr_t) noexcept : inner_(nullptr) {}
constexpr Option(none_t) noexcept : Option(nullptr) {}
constexpr ~Option() noexcept(std::is_nothrow_destructible_v<T>) {}
constexpr Option(const Option& copy) noexcept(std::is_nothrow_copy_constructible_v<decltype(copy.inner_)>)
: inner_(copy.inner_)
{}
constexpr Option(Option&& move) noexcept(std::is_nothrow_move_constructible_v<decltype(move.inner_)>)
: inner_(std::move(move.inner_))
{}
constexpr explicit(std::convertible_to<std::add_rvalue_reference_t<T>, Option<T>>) Option(_details::rv_ref_t<T> value) noexcept(std::is_nothrow_invocable_v<convert_to_held_rv, std::add_rvalue_reference_t<T>>) requires(!std::is_void_v<T>)
: inner_(convert_to_held_rv(std::forward<decltype(value)>(value)))
{}
//TODO: copy/move assign where appropriate
constexpr Option& operator=(const Option& copy) noexcept(std::is_void_v<T> || std::is_nothrow_copy_constructible_v<decltype(copy.inner_)::held_type>)
{
if constexpr(std::is_void_v<T>) {
inner_.has = copy.inner_.has;
} else {
if(this != std::addressof(copy)) {
using inner_t = std::remove_reference_t<decltype(inner_)>;
if(const auto* ptr = copy.try_get_value()) {
using held_t = inner_t::held_type;
inner_ = inner_t{held_t{*ptr}};
} else inner_ = inner_t{None};
}
} return *this;
}
constexpr Option& operator=(Option&& move) noexcept(std::is_void_v<T> || std::is_nothrow_move_constructible_v<decltype(move.inner_)::held_type>)
{
if constexpr(std::is_void_v<T>) std::exchange(inner_.has, move.inner_.has);
else {
if(this != std::addressof(move)) {
using inner_t = std::remove_reference_t<decltype(inner_)>;
if(auto* ptr = move.try_get_value()) {
inner_ = inner_t{ std::move(*ptr) };
} else inner_ = inner_t{None};
}
} return *this;
}
constexpr Option& operator=(none_t) noexcept(std::is_nothrow_destructible_v<decltype(std::declval<Option<T>>().inner_)::held_type>)
{
using inner_t = std::remove_reference_t<decltype(inner_)>;
inner_ = inner_t{ None };
return *this;
}
//TODO: XXX: For this to work, we need *another* partial-spec "trait" that is (T == void ? void || alias std::add_rvalue_reference_t<T>)
constexpr explicit(std::convertible_to<std::add_rvalue_reference_t<T>, Option<T>>) Option& operator=(_details::rv_ref_t<T> value) noexcept(std::is_void_v<T> || std::is_nothrow_constructible_v<decltype(std::declval<Option<T>>().inner_), std::add_rvalue_reference_t<std::invoke_result_t<decltype(convert_to_held_rv), std::add_rvalue_reference_t<T>>>>) requires(!std::is_void_v<T>)
{
using inner_t = std::remove_reference_t<decltype(inner_)>;
if constexpr (!std::is_void_v<T>)
inner_ = inner_t { convert_to_held_rv(std::forward<decltype(value)>(value)) };
else inner_ = inner_t { true };
return *this;
}
//accessors
constexpr bool has_value() const noexcept { return inner_.has_value(); }
//XXX: We're returning held_type* here, which we don't want to expose...
constexpr const auto* try_get_value() const noexcept { return _details::map_null(inner_.try_get_value(), [](const auto* ptr) { return std::addressof(convert_from_held_cv(*ptr)); }); }
constexpr auto* try_get_value() noexcept { return _details::map_null(inner_.try_get_value(), [](auto* ptr) { return std::addressof(convert_from_held_ref(*ptr)); }); }
//TODO: XXX: We need to `convert_from_held` here!
constexpr const auto& value() const& { if (const auto* p = try_get_value()) return convert_from_held_cv(*p); throw _unwrap_error(); }
constexpr auto& value() & { if(auto* p = try_get_value()) return convert_from_held_ref(*p); throw _unwrap_error(); }
constexpr decltype(auto) value() && { if(auto* p = try_get_value()) return convert_from_held_rv(std::move(*p)); throw _unwrap_error(); }
constexpr auto value() const&& { if(const auto* p = try_get_value()) return auto(convert_from_held_cv(*p)); throw _unwrap_error(); }
constexpr operator bool() const noexcept { return has_value(); }
constexpr const auto* operator->() const { return _details::map_null(try_get_value(), _details::identity, [] [[noreturn]] () -> std::nullptr_t { throw _unwrap_error(); }); }
constexpr auto* operator->() { return _details::map_null(try_get_value(), _details::identity, [] [[noreturn]] () -> std::nullptr_t { throw _unwrap_error(); }); }
constexpr const auto& operator*() const& { return value(); }
constexpr auto& operator*() & { return value(); }
constexpr decltype(auto) operator*() && { return value(); }
constexpr auto operator*() const&& { return value(); }
private:
constexpr static decltype(auto) _unwrap_error() {
if consteval {
return UnwrapError{};
} else {
return std::runtime_error(UnwrapError{}.message());
}
}
struct _impl_base {
typedef _null_opt_impl::held_type<T> held_type;
typedef _null_opt_impl::used_type<T> type;
//struct none_type {};
typedef none_t none_type;
constexpr _impl_base() noexcept = default;
constexpr ~_impl_base() noexcept = default;
};
template<typename _U = T, bool O = is_null_optimised> struct _impl;
template<typename _U, bool O> struct _impl_type { using type = _impl<_U, O>; };
template<bool O> struct _impl_type<void, O> {
static_assert(O, "void should be `null_opt`");
/// std::is_void_v<T>
struct alignas(bool) type final : public _impl_base {
using held_type = _impl_base::held_type;
using none_type = _impl_base::none_type;
constexpr type(std::nullptr_t) noexcept : _impl_base(), has(false) {}
constexpr explicit type(bool&& value) noexcept : _impl_base(), has(value) {}
constexpr ~type() noexcept {}
constexpr bool has_value() const noexcept { return has; }
constexpr const bool* try_get_value() const noexcept { return &has; }
constexpr bool* try_get_value() noexcept { return &has; }
constexpr bool get_value() const noexcept { return has; }
constexpr bool& get_value() noexcept { return has; }
bool has;
};
};
/// null_opt<T> == true
template<typename _U> struct alignas(_U) _impl<_U, true> final : public _impl_base {
using type_info = null_optimise<T>;
using held_type = _impl_base::held_type;
using none_type = _impl_base::none_type;
static_assert(has_null_opt<T>, "Invalid use of Option::_impl<true> where has_null_opt<T> == false");
constexpr _impl(std::nullptr_t) noexcept : _impl_base(), value(type_info::sentinel) {}
constexpr _impl(std::add_rvalue_reference_t<held_type> value) noexcept(std::is_nothrow_move_constructible_v<held_type>)
: _impl_base()
, value(std::move(value))
{}
/*//TODO: XXX: Make Option(T&&) ctor handle calling `convert_to_held` instead? Another template interface for this??
constexpr explicit _impl(std::add_rvalue_reference_t<type_info::type> value) noexcept(std::is_nothrow_constructible_v<held_type, std::invoke_result_t<type_info::convert_to_held, decltype(value)>> && std::is_nothrow_invocable_v<type_info::convert_to_held, decltype(value)) requires(!std::is_same_v<type_info::type, held_type>)
: _impl_base()
, value(type_info::convert_to_held(std::forward<decltype(value)>(value)))
{}*/
constexpr _impl& operator=(std::add_rvalue_reference_t<held_type> value) noexcept(std::is_nothrow_move_constructible_v<held_type>)
{
std::exchange(value, std::forward<decltype(value)>(value));
return *this;
}
constexpr _impl& operator=(none_t) noexcept(std::is_nothrow_destructible_v<held_type>)
{
if(has_value()) {
using sen_t = decltype(type_info::sentinel);
std::exchange(value, sen_t{type_info::sentinel});
}
return *this;
}
constexpr ~_impl() noexcept(std::is_nothrow_destructible_v<held_type>) {}
constexpr bool has_value() const noexcept { return !bool(type_info::sentinel == value); }
constexpr const held_type* try_get_value() const noexcept { return has_value() ? std::addressof(value) : nullptr; }
constexpr held_type* try_get_value() noexcept { return has_value() ? std::addressof(value) : nullptr; }
held_type value;
};
/// null_opt<T> == false
template<typename _U> struct alignas(_U) _impl<_U, false> final : public _impl_base {
using held_type = _impl_base::held_type;
using none_type = _impl_base::none_type;
constexpr _impl(std::nullptr_t) noexcept : _impl_base(), has(false), none(none_type{}) {}
constexpr _impl(std::add_rvalue_reference_t<held_type> value)
noexcept(std::is_nothrow_move_constructible_v<held_type>)
: _impl_base()
, has(true)
, some(std::move(value))
{}
constexpr _impl& operator=(std::add_rvalue_reference_t<held_type> value) noexcept(std::is_nothrow_move_constructible_v<held_type> && std::is_nothrow_destructible_v<held_type>)
{
if(has) std::exchange(some, std::forward<decltype(value)>(value));
else {
none.~none_type();
if constexpr(noexcept(std::is_nothrow_move_constructible_v<held_type>))
some = std::move(value);
else try {
some = std::move(value);
} catch(...) {
none = none_type{};
throw;
}
has = true;
}
return *this;
}
constexpr _impl& operator=(none_t) noexcept(std::is_nothrow_destructible_v<held_type>)
{
if(has) {
some.~held_type();
none = none_type{};
has = false;
}
return *this;
}
constexpr ~_impl() noexcept(std::is_nothrow_destructible_v<held_type>) {
if(has) some.~held_type();
else none.~none_type();
}
constexpr bool has_value() const noexcept { return has; }
constexpr const held_type* try_get_value() const noexcept {
return has ? std::addressof(some) : nullptr;
}
constexpr held_type* try_get_value() noexcept {
return has ? std::addressof(some) : nullptr;
}
bool has;
union {
held_type some;
[[no_unique_address]] _impl_base::none_type none;
};
};
_impl_type<T, is_null_optimised>::type inner_;
// --- `held_type` convertions
constexpr static decltype(auto) convert_to_held_rv(_details::rv_ref_t<T> from) noexcept(!is_null_optimised || std::is_nothrow_invocable_v<null_optimise<T>::convert_to_held, _details::rv_ref_t<T>>) { if constexpr(is_null_optimised) {
return null_optimise<T>::convert_to_held(std::forward<T>(from));
} else {
return std::forward<decltype(from)>(from);
} }
constexpr static decltype(auto) convert_to_held_cv(_details::cv_ref_t<T> from) noexcept(!is_null_optimised || std::is_nothrow_invocable_v<null_optimise<T>::convert_to_held, _details::cv_ref_t<T>>) { if constexpr(is_null_optimised) {
return null_optimise<T>::convert_to_held(from);
} else {
return std::forward<decltype(from)>(from);
} }
using this_held_type = typename std::remove_reference_t<
decltype(std::declval<Option<T>>()
.inner_)>
::held_type;
constexpr static decltype(auto) convert_from_held_cv(
_details::cv_ref_t<this_held_type> held) noexcept(!is_null_optimised || std::is_nothrow_invocable_v<null_optimise<T>::convert_from_held, _details::cv_ref_t<this_held_type>>) { if constexpr(is_null_optimised) {
return null_optimise<T>::convert_from_held(std::forward<decltype(held)>(held));
} else {
return std::forward<decltype(held)>(held);
} }
constexpr static decltype(auto) convert_from_held_ref(
_details::mut_ref_t<this_held_type> held) noexcept(!is_null_optimised || std::is_nothrow_invocable_v<null_optimise<T>::convert_from_held, _details::mut_ref_t<this_held_type>>) { if constexpr(is_null_optimised) {
return null_optimise<T>::convert_from_held(std::forward<decltype(held)>(held));
} else {
return std::forward<decltype(held)>(held);
} }
constexpr static decltype(auto) convert_from_held_rv(
_details::rv_ref_t<this_held_type> held) noexcept(!is_null_optimised || std::is_nothrow_invocable_v<null_optimise<T>::convert_from_held, _details::rv_ref_t<this_held_type>>) { if constexpr(is_null_optimised) {
return null_optimise<T>::convert_from_held(std::forward<decltype(held)>(held));
} else {
return std::forward<decltype(held)>(held);
} }
// ---
};
static_assert(sizeof(Option<void*>) > sizeof(void*), "Option<T*>: Bad null_opt size");
static_assert(alignof(Option<void*>) == alignof(void*), "Option<T*>: Bad null_opt align");
static_assert(sizeof(Option<ptr::NonNull<int>>) == sizeof(int*), "Option<NonNull<T>>: Bad null_opt size");
static_assert(alignof(Option<ptr::NonNull<int>>) == alignof(int*), "Option<NonNull<T>>: Bad null_opt align");
static_assert(sizeof(Option<double&>) == sizeof(double&), "Option<T&>: Bad null_opt size");
static_assert(alignof(Option<double&>) == alignof(double*), "Option<T&>: Bad null_opt align");
static_assert(sizeof(Option<long long int&&>) == sizeof(char*), "Option<T&&>: Bad null_opt size");
static_assert(alignof(Option<long long int&&>) == alignof(long*), "Option<T&&>: Bad null_opt align");
static_assert(alignof(Option<int>) == alignof(int), "Option<int>: Bad non null_opt align");
static_assert(sizeof(Option<void>) == sizeof(bool), "Option<void>: Bad size");
static_assert(alignof(Option<void>) == alignof(bool), "Option<void>: Bad align");
}
using optional::Option;
}

@ -0,0 +1,204 @@
#pragma once
#include <concepts>
#include <bit>
#include <stdexcept>
namespace exopt::ptr {
template<typename T>
using RawPtr = T*;
template<typename T>
using RawRef = T&;
template<typename T>
using UniquePtr = T* __restrict__;
template<typename T>
using UniqueRef = T& __restrict__;
template<typename T>
using AliasedPtr = T __attribute__((__may_alias__))*;
template<typename T>
using AliasedRef = T __attribute__((__may_alias__))&;
template<typename T, size_t Size>
concept IsSize = sizeof(T) == Size;
template<typename T>
concept PointerEqCmp = requires(T p) {
{ p == nullptr } -> std::convertible_to<bool>;
};
template<typename T>
concept PointerMemCmp = PointerEqCmp<T> && requires(T p, T p2) {
{ p == p2 } -> std::convertible_to<bool>;
{ static_cast<void*>(p) };
};
template<typename T>
concept PointerDeref = PointerEqCmp<T> && requires(T p) {
{ *p };
};
template<typename T, typename To>
concept PointerDerefTo = PointerDeref<T> && requires(T p) {
{ *p } -> std::convertible_to<To>;
};
template<PointerEqCmp T>
constexpr decltype(auto) map(T&& ptr, auto&& func) noexcept(std::is_nothrow_invocable_v<decltype(func), decltype(ptr)>) requires(std::is_invocable_v<decltype(func), decltype(ptr)>)
{
if (ptr == nullptr) return std::forward<T>(ptr);
else return func(std::forward<T>(ptr));
}
template<PointerEqCmp T>
constexpr decltype(auto) map(T&& ptr, auto&& func, auto&& other) noexcept(std::is_nothrow_invocable_v<decltype(func), decltype(ptr)> && (!std::is_invocable_v<decltype(other)> || std::is_nothrow_invocable_v<decltype(other)>)) requires(std::is_invocable_v<decltype(func), decltype(ptr)>)
{
if (ptr == nullptr) return std::forward<T>(ptr);
else if constexpr(std::is_invocable_v<decltype(other)>) return other();
else return std::forward<decltype(other)>(other);
}
template<PointerEqCmp T>
constexpr decltype(auto) not_null_or(T&& ptr, auto&& other) noexcept(!std::is_invocable_v<decltype(other)>) requires(std::is_convertible_v<decltype(other), T> || (std::is_invocable_v<decltype(other)> && std::is_convertible_v<std::invoke_result_t<decltype(other)>, decltype(ptr)>))
{
if(!(ptr == nullptr)) return std::forward<T>(ptr);
else if constexpr(std::is_invocable_v<decltype(other)>) return other();
else return std::forward<decltype(other)>(other);
}
template<typename T, typename U>
constexpr bool addr_eq(const T& o1, const U& o2) noexcept { return static_cast<void*>(std::addressof(o1)) == static_cast<void*>(std::addressof(o2)); }
template<typename U>
constexpr bool ptr_addr_eq(const PointerMemCmp auto& ptr, const U& obj) noexcept { return static_cast<void*>(ptr) == static_cast<void*>(std::addressof(obj)); }
template<PointerDeref U>
constexpr decltype(auto) deref_or(U&& ptr, auto&& other) noexcept(noexcept(*ptr) && (!std::is_invocable_v<decltype(other)> || std::is_nothrow_invocable_v<decltype(other)>)) {
if (!(ptr == nullptr)) return *ptr;
else if constexpr (std::is_invocable_v<decltype(other)>) return other();
else return std::forward<decltype(other)>(other);
}
template<typename E, typename... Args> requires(std::is_constructible_v<E, Args...>)
constexpr decltype(auto) throw_if_null(PointerEqCmp auto&& ptr, Args&&... error) {
auto _throw = [&] [[noreturn, gnu::noinline, gnu::cold]] () {
throw E{std::forward<Args>(error)...};
};
if(ptr == nullptr) _throw();
return std::forward<decltype(ptr)>(ptr);
}
template<typename E, typename... Args> requires(std::is_constructible_v<E, Args...>)
constexpr decltype(auto) deref_or_throw(PointerDeref auto&& ptr, Args&&... error)
{
return *throw_if_null<E, Args...>(std::forward<decltype(ptr)>(ptr), std::forward<Args>(error)...);
}
template<typename T>
constexpr auto read_once(const T& ref) noexcept(std::is_nothrow_copy_constructible_v<T>) requires(std::is_copy_constructible_v<T>)
{
AliasedPtr<const T> aref = std::addressof(ref);
return *aref;
}
template<typename T>
constexpr decltype(auto) write_once(T& ref, std::convertible_to<T> auto&& value) noexcept(std::is_nothrow_move_constructible_v<T>) requires(std::is_move_constructible_v<decltype(value)> && std::is_constructible_v<T, decltype(std::move(value))>)
{
AliasedPtr<T> aref = std::addressof(ref);
return *aref = std::move(value);
}
template<typename T>
constexpr T read_once_strict(const T& ref) noexcept(std::is_nothrow_copy_constructible_v<T>) requires(std::is_copy_constructible_v<T>)
{
return *static_cast<const volatile T*>(std::addressof(ref));
}
template<typename T>
constexpr void write_once_strict(T& ref, std::convertible_to<T> auto&& value) noexcept(std::is_nothrow_move_constructible_v<T>) requires(std::is_move_constructible_v<decltype(value)> && std::is_constructible_v<T, decltype(std::move(value))>)
{
*static_cast<volatile T*>(std::addressof(ref)) = T{std::move(value)};
}
template<typename T, typename... Args> requires(std::is_constructible_v<T, Args...>)
constexpr T& leak(Args&&... ctor) noexcept(std::is_nothrow_constructible_v<T, Args...>)
{
return *new T(std::forward<Args>(ctor)...);
}
struct NullException /*TODO : public error::Error*/ {
constexpr NullException() noexcept = default;
constexpr NullException(const NullException&) noexcept = default;
constexpr NullException& operator=(const NullException&) noexcept = default;
constexpr virtual ~NullException() noexcept {}
constexpr std::string_view message() const noexcept /*override*/ { return "pointer was null"; }
};
template<typename T> requires(!std::is_reference_v<T>)
struct NonNull final {
constexpr NonNull(T& ref) noexcept
: ptr_(std::addressof(ref)) {}
[[gnu::nonnull(1, 2)]]
constexpr explicit NonNull(T* ptr) noexcept
: ptr_(ptr) {}
constexpr NonNull(const NonNull&) noexcept = default;
constexpr NonNull(NonNull&&) noexcept = default;
constexpr NonNull& operator=(const NonNull&) noexcept = default;
constexpr NonNull& operator=(NonNull&&) noexcept = default;
constexpr ~NonNull() noexcept = default;
constexpr static NonNull<T> try_new(T* ptr) {
constexpr auto _throw = [] [[noreturn, gnu::noinline, gnu::cold]] () {
if consteval {
throw NullException{};
} else {
throw std::runtime_error(NullException{}.message());
}
//throw error::as_runtime_error(error::comptime_fail<NullException>());
};
if(!ptr) _throw();
return NonNull<T>{ptr};
}
#ifdef DEBUG
[[gnu::nonnull(1)]]
#endif
constexpr static NonNull<T> new_unchecked(T* ptr) noexcept {
#ifndef DEBUG
_EO_ASSUME(ptr!=nullptr);
#endif
return NonNull{ptr}; }
constexpr friend bool operator==(std::nullptr_t, const NonNull&) noexcept { return false; }
constexpr friend bool operator==(const NonNull&, std::nullptr_t) noexcept { return false; }
constexpr friend bool operator!=(std::nullptr_t, const NonNull&) noexcept { return true; }
constexpr friend bool operator!=(const NonNull&, std::nullptr_t) noexcept { return true; }
constexpr friend auto operator<=>(const NonNull& a, const NonNull& b) noexcept { return a.ptr_ <=> b.ptr_; }
constexpr friend auto operator<=>(const NonNull& a, T* b) noexcept { return (!b) ? (true<=>false) : a.ptr_ <=> b; }
constexpr friend auto operator<=>(T* a, const NonNull& b) noexcept { return (!a) ? (false<=>true) : (a <=> b.ptr_); }
[[gnu::returns_nonnull]]
constexpr T* get() const noexcept { return ptr_; }
/*
[[gnu::returns_nonnull]]
constexpr T* const&& get() const&& noexcept { return std::move(ptr_); }
*/
[[gnu::returns_nonnull]]
constexpr operator T*() const noexcept { return ptr_; }
constexpr T& operator*() & noexcept { return *ptr_; }
constexpr const T& operator*() const& noexcept { return *ptr_; }
constexpr T&& operator*() && noexcept { return std::move(*ptr_); }
constexpr decltype(auto) operator*() const&& noexcept { return *ptr_; }
[[gnu::returns_nonnull]]
constexpr T* operator->() noexcept { return ptr_; }
[[gnu::returns_nonnull]]
constexpr const T* operator->() const noexcept { return ptr_; }
private:
T* ptr_;
};
}

@ -1,11 +1,14 @@
#pragma once
#include <memory>
#include <string_view>
#include <utility>
#include <array>
#include "exopt.h"
#include "optional.h"
#define _EO_CONSTANT_VALUE(X) ([]() { \
struct { \
typedef decltype(X) comptime_constant_t; \
@ -20,6 +23,9 @@ namespace exopt { namespace util [[gnu::visibility("internal")]] {
typename T::comptime_constant_t;
};
template<typename T>
concept is_complete = requires{ { sizeof(T) == sizeof(T) }; };
constexpr auto comptime_value(auto value) noexcept {
using U = decltype(value);
struct inner {

Loading…
Cancel
Save