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
parent
807008d965
commit
67c553903b
@ -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:
|
||||
};
|
||||
}
|
@ -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_;
|
||||
};
|
||||
}
|
Loading…
Reference in new issue