Files
asl/asl/types/option.hpp

510 lines
13 KiB
C++

#pragma once
#include "asl/base/assert.hpp"
#include "asl/base/meta.hpp"
#include "asl/base/functional.hpp"
#include "asl/base/annotations.hpp"
#include "asl/types/maybe_uninit.hpp"
#include "asl/hashing/hash.hpp"
namespace asl
{
struct nullopt_t {};
static constexpr nullopt_t nullopt{};
// @Todo(option) Reference
// @Todo(option) Function
// @Todo(option) Arrays
template<is_object T> class option;
namespace option_internal
{
template<typename T, typename U>
concept not_constructible_from_option =
!constructible_from<T, option<U>&> &&
!constructible_from<T, const option<U>&> &&
!constructible_from<T, option<U>&&> &&
!constructible_from<T, const option<U>&&>;
template<typename T, typename U>
concept not_assignable_from_option =
!assignable_from<T&, option<U>&> &&
!assignable_from<T&, const option<U>&> &&
!assignable_from<T&, option<U>&&> &&
!assignable_from<T&, const option<U>&&>;
template<typename T, typename U>
concept not_constructible_assignable_from_option =
not_constructible_from_option<T, U> &&
not_assignable_from_option<T, U>;
} // namespace option_internal
template<typename T>
concept is_option = requires
{
typename T::type;
requires same_as<un_cvref_t<T>, option<typename T::type>>;
};
template<is_object T>
class option
{
static constexpr bool kHasNiche = has_niche<T>;
using HasValueMarker = select_t<kHasNiche, empty, bool>;
maybe_uninit<T> m_payload{};
ASL_NO_UNIQUE_ADDRESS HasValueMarker m_has_value{};
template<is_object U>
friend class option;
template<typename... Args>
constexpr void construct(Args&&... args)
{
ASL_ASSERT(!has_value());
if constexpr (!kHasNiche)
{
m_payload.construct_unsafe(ASL_FWD(args)...);
m_has_value = true;
}
else
{
if constexpr (move_assignable<T>)
{
m_payload.assign_unsafe(ASL_MOVE(T{ASL_FWD(args)...}));
}
else
{
m_payload.destroy_unsafe();
m_payload.construct_unsafe(ASL_FWD(args)...);
}
}
}
template<typename U>
constexpr void assign(U&& arg)
{
ASL_ASSERT(has_value());
m_payload.assign_unsafe(ASL_FWD(arg));
}
public:
using type = T;
constexpr option() : option{nullopt} {}
// NOLINTNEXTLINE(*-explicit-conversions)
constexpr option(nullopt_t) requires (!kHasNiche) {}
// NOLINTNEXTLINE(*-explicit-conversions)
constexpr option(nullopt_t) requires kHasNiche : m_payload{in_place, niche_t{}} {}
template<typename U = T>
constexpr explicit (!convertible_from<T, U&&>)
option(U&& value)
requires (
kHasNiche &&
constructible_from<T, U&&> &&
!same_as<un_cvref_t<U>, option>
)
: m_payload{in_place, ASL_FWD(value)}
{}
template<typename U = T>
constexpr explicit (!convertible_from<T, U&&>)
option(U&& value)
requires (
!kHasNiche &&
constructible_from<T, U&&> &&
!is_option<U>
)
: m_payload{in_place, ASL_FWD(value)}
, m_has_value{true}
{}
constexpr option(const option& other) requires trivially_copy_constructible<T> = default;
constexpr option(const option& other) requires (!copy_constructible<T>) = delete;
constexpr option(const option& other)
requires copy_constructible<T> && (!trivially_copy_constructible<T>)
: option{nullopt}
{
if (other.has_value())
{
construct(other.m_payload.as_init_unsafe());
}
}
constexpr option(option&& other) requires trivially_move_constructible<T> = default;
constexpr option(option&& other) requires (!move_constructible<T>) = delete;
constexpr option(option&& other)
requires move_constructible<T> && (!trivially_move_constructible<T>)
: option{nullopt}
{
if (other.has_value())
{
construct(ASL_MOVE(other.m_payload.as_init_unsafe()));
}
}
template<typename U>
constexpr explicit (!convertible_from<T, const U&>)
option(const option<U>& other)
requires (
constructible_from<T, const U&> &&
option_internal::not_constructible_from_option<T, U>
)
: option{nullopt}
{
if (other.has_value())
{
construct(other.m_payload.as_init_unsafe());
}
}
template<typename U>
constexpr explicit (!convertible_from<T, U&&>)
option(option<U>&& other)
requires (
constructible_from<T, U&&> &&
option_internal::not_constructible_from_option<T, U>
)
: option{nullopt}
{
if (other.has_value())
{
construct(ASL_MOVE(other.m_payload.as_init_unsafe()));
}
}
constexpr option& operator=(nullopt_t) &
{
reset();
return *this;
}
template<typename U = T>
constexpr option& operator=(U&& value) &
requires (
assignable_from<T&, U&&> &&
constructible_from<T, U&&> &&
!is_option<U>
)
{
if (has_value())
{
assign(ASL_FWD(value));
}
else
{
construct(ASL_FWD(value));
}
return *this;
}
constexpr option& operator=(const option& other) &
requires (!copy_assignable<T>) = delete;
constexpr option& operator=(const option& other) &
requires trivially_copy_assignable<T> = default;
constexpr option& operator=(const option& other) &
requires copy_assignable<T> && (!trivially_copy_constructible<T>)
{
if (&other == this) { return *this; }
if (other.has_value())
{
if (has_value())
{
assign(other.m_payload.as_init_unsafe());
}
else
{
construct(other.m_payload.as_init_unsafe());
}
}
else if (has_value())
{
reset();
}
return *this;
}
constexpr option& operator=(option&& other) &
requires (!move_assignable<T>) = delete;
constexpr option& operator=(option&& other) &
requires trivially_move_assignable<T> = default;
constexpr option& operator=(option&& other) &
requires move_assignable<T> && (!trivially_move_constructible<T>)
{
if (&other == this) { return *this; }
if (other.has_value())
{
if (has_value())
{
assign(ASL_MOVE(other.m_payload.as_init_unsafe()));
}
else
{
construct(ASL_MOVE(other.m_payload.as_init_unsafe()));
}
}
else if (has_value())
{
reset();
}
return *this;
}
template<typename U = T>
constexpr option& operator=(const option<U>& other) &
requires (
constructible_from<T, const U&> &&
assignable_from<T&, const U&> &&
option_internal::not_constructible_assignable_from_option<T, U>
)
{
if (other.has_value())
{
if (has_value())
{
assign(other.m_payload.as_init_unsafe());
}
else
{
construct(other.m_payload.as_init_unsafe());
}
}
else if (has_value())
{
reset();
}
return *this;
}
template<typename U = T>
constexpr option& operator=(option<U>&& other) &
requires (
constructible_from<T, U&&> &&
assignable_from<T&, U&&> &&
option_internal::not_constructible_assignable_from_option<T, U>
)
{
if (other.has_value())
{
if (has_value())
{
assign(ASL_MOVE(other.m_payload.as_init_unsafe()));
}
else
{
construct(ASL_MOVE(other.m_payload.as_init_unsafe()));
}
}
else if (has_value())
{
reset();
}
return *this;
}
constexpr ~option() requires trivially_destructible<T> = default;
constexpr ~option() requires (!trivially_destructible<T>)
{
reset();
}
constexpr void reset()
{
if (!has_value()) { return; }
if constexpr (kHasNiche)
{
if constexpr (move_assignable<T>)
{
m_payload.assign_unsafe(ASL_MOVE(T{niche_t{}}));
}
else
{
m_payload.destroy_unsafe();
m_payload.construct_unsafe(niche_t{});
}
}
else
{
m_has_value = false;
m_payload.destroy_unsafe();
}
}
constexpr bool has_value() const
{
if constexpr (kHasNiche)
{
return m_payload.as_init_unsafe() != niche_t{};
}
else
{
return m_has_value;
}
}
// @Todo(C++23) Deducing this
constexpr T&& value() &&
{
ASL_ASSERT_RELEASE(has_value());
return ASL_MOVE(m_payload).as_init_unsafe();
}
constexpr T& value() &
{
ASL_ASSERT_RELEASE(has_value());
return m_payload.as_init_unsafe();
}
constexpr const T& value() const&
{
ASL_ASSERT_RELEASE(has_value());
return m_payload.as_init_unsafe();
}
template<typename U>
constexpr T value_or(U&& other_value) const&
requires copy_constructible<T> && convertible_from<T, U&&>
{
return has_value() ? value() : static_cast<T>(ASL_FWD(other_value));
}
template<typename U>
constexpr T value_or(U&& other_value) &&
requires move_constructible<T> && convertible_from<T, U&&>
{
return has_value() ? ASL_MOVE(value()) : static_cast<T>(ASL_FWD(other_value));
}
template<typename... Args>
constexpr T& emplace(Args&&... args) &
requires constructible_from<T, Args&&...>
{
if (has_value()) { reset(); }
construct(ASL_FWD(args)...);
return value();
}
template<typename F>
constexpr auto and_then(F&& f) &
requires is_option<result_of_t<F(T&)>>
{
if (has_value())
{
return invoke(ASL_FWD(f), value());
}
return un_cvref_t<result_of_t<F(T&)>>{};
}
template<typename F>
constexpr auto and_then(F&& f) const&
requires is_option<result_of_t<F(const T&)>>
{
if (has_value())
{
return invoke(ASL_FWD(f), value());
}
return un_cvref_t<result_of_t<F(const T&)>>{};
}
template<typename F>
constexpr auto and_then(F&& f) &&
requires is_option<result_of_t<F(T)>>
{
if (has_value())
{
return invoke(ASL_FWD(f), ASL_MOVE(value()));
}
return un_cvref_t<result_of_t<F(T)>>{};
}
template<typename F>
constexpr auto transform(F&& f) &
{
using U = un_cvref_t<result_of_t<F(T&)>>;
if (has_value())
{
return option<U>{ invoke(ASL_FWD(f), value()) };
}
return option<U>{};
}
template<typename F>
constexpr auto transform(F&& f) const&
{
using U = un_cvref_t<result_of_t<F(const T&)>>;
if (has_value())
{
return option<U>{ invoke(ASL_FWD(f), value()) };
}
return option<U>{};
}
template<typename F>
constexpr auto transform(F&& f) &&
{
using U = un_cvref_t<result_of_t<F(T)>>;
if (has_value())
{
return option<U>{ invoke(ASL_FWD(f), ASL_MOVE(value())) };
}
return option<U>{};
}
template<typename F>
constexpr option or_else(F&& f) const&
requires same_as<un_cvref_t<result_of_t<F()>>, option>
{
return has_value() ? *this : invoke(ASL_FWD(f));
}
template<typename F>
constexpr option or_else(F&& f) &&
requires same_as<un_cvref_t<result_of_t<F()>>, option>
{
return has_value() ? ASL_MOVE(*this) : invoke(ASL_FWD(f));
}
template<typename H>
requires (!uniquely_represented<option>) && hashable<T>
friend H AslHashValue(H h, const option& opt)
{
if (!opt.has_value())
{
return H::combine(ASL_MOVE(h), 0);
}
return H::combine(ASL_MOVE(h), 1, opt.value());
}
};
template<typename T>
requires has_niche<T> && uniquely_represented<T>
struct is_uniquely_represented<option<T>> : true_type {};
template<typename T>
option(T) -> option<T>;
} // namespace asl