#pragma once
#include "memory.h"
#include <concepts>
#include <optional>
#include <type_traits>
template <typename E>
concept MarkableEnum = std::is_enum_v<E> && requires { E::_END; };
template <MarkableEnum E>
class Markable {
private:
constexpr static unsigned s_delta = requires { E::_BEGIN; } ? static_cast<unsigned>(E::_BEGIN) : 0;
constexpr inline static unsigned convert(E e) {
return static_cast<unsigned>(e) - s_delta;
}
constexpr inline static E convert(unsigned v) {
return static_cast<E>(v + s_delta);
}
constexpr static unsigned s_invalid = convert(E::_END) + 1;
public:
constexpr Markable()
: m_data(s_invalid)
{
}
constexpr Markable(std::nullopt_t)
: m_data(s_invalid)
{
}
constexpr Markable(E e)
: m_data(convert(e))
{
}
constexpr Markable(const std::optional<E>& other)
: m_data(other.has_value() ? convert(other.value()) : s_invalid)
{
}
constexpr Markable(std::optional<E>&& other)
: m_data(other.has_value() ? convert(other.value()) : s_invalid)
{
other.reset();
}
constexpr Markable(const Markable& other)
: m_data(other.m_data)
{
}
constexpr Markable(Markable&& other)
: m_data(other.m_data)
{
other.m_data = s_invalid;
}
constexpr Markable& operator=(std::nullopt_t) {
m_data = s_invalid;
return *this;
}
constexpr Markable& operator=(E e) {
m_data = convert(e);
return *this;
}
constexpr Markable& operator=(const std::optional<E>& other) {
m_data = other.has_value() ? convert(other.value()) : s_invalid;
return *this;
}
constexpr Markable& operator=(std::optional<E>&& other) {
m_data = other.has_value() ? convert(other.value()) : s_invalid;
other = std::nullopt;
return *this;
}
constexpr Markable& operator=(const Markable& other) {
m_data = other.m_data;
return *this;
}
constexpr Markable& operator=(Markable&& other) {
m_data = other.m_data;
other.m_data = s_invalid;
return *this;
}
constexpr operator bool() const {
return m_data != s_invalid;
}
constexpr bool has_value() const {
return m_data != s_invalid;
}
constexpr E operator*() const {
ASSERT(m_data != s_invalid);
return convert(m_data);
}
constexpr E value() const {
ASSERT(m_data != s_invalid);
return convert(m_data);
}
constexpr E value_or(E default_value) const {
return m_data != s_invalid ? convert(m_data) : default_value;
}
constexpr void swap(Markable& other) {
std::swap(m_data, other.m_data);
}
constexpr void swap(std::optional<E>& other) {
unsigned old = m_data;
m_data = other.has_value() ? convert(other.value()) : s_invalid;
if (old != s_invalid) {
other = convert(old);
} else {
other = std::nullopt;
}
}
constexpr void reset() {
m_data = s_invalid;
}
constexpr auto operator<=>(std::nullopt_t) const {
return (m_data != s_invalid) <=> false;
}
constexpr auto operator<=>(E other) const {
if (m_data != s_invalid) {
return convert(m_data) <=> other;
}
return std::strong_ordering::less;
}
constexpr auto operator<=>(const std::optional<E>& other) const {
bool thisHasValue = m_data != s_invalid;
bool otherHasValue = other.has_value();
if (thisHasValue && otherHasValue) {
return convert(m_data) <=> other.value();
}
return thisHasValue <=> otherHasValue;
}
constexpr auto operator<=>(const Markable& other) const {
bool thisHasValue = m_data != s_invalid;
bool otherHasValue = other.m_data != s_invalid;
if (thisHasValue && otherHasValue) {
return convert(m_data) <=> convert(other.m_data);
}
return thisHasValue <=> otherHasValue;
}
constexpr operator std::optional<E>() const {
if (m_data != s_invalid) {
return convert(m_data);
}
return std::nullopt;
}
constexpr static unsigned PACK_BITS = std::bit_width(s_invalid);
private:
unsigned m_data : PACK_MAX_VALUE(s_invalid);
};