#pragma once #include #include #include #include namespace BAN { namespace detail { template constexpr size_t size_ref_as_ptr() { return is_lvalue_reference_v ? sizeof(remove_reference_t*) : sizeof(T); } template constexpr size_t align_ref_as_ptr() { return is_lvalue_reference_v ? alignof(remove_reference_t*) : alignof(T); } template constexpr size_t max_size_ref_as_ptr() { return size_ref_as_ptr(); } template constexpr size_t max_size_ref_as_ptr() { return size_ref_as_ptr() > size_ref_as_ptr() ? max_size_ref_as_ptr() : max_size_ref_as_ptr(); } template constexpr size_t max_align_ref_as_ptr() { return align_ref_as_ptr(); } template constexpr size_t max_align_ref_as_ptr() { return align_ref_as_ptr() > align_ref_as_ptr() ? max_align_ref_as_ptr() : max_align_ref_as_ptr(); } template constexpr size_t index() { if constexpr(is_same_v) return 0; else if constexpr(sizeof...(Ts) == 0) return 1; else return index() + 1; } template void destruct(size_t index, uint8_t* data) { if (index == 0) if constexpr(!is_lvalue_reference_v) reinterpret_cast(data)->~T(); else; else if constexpr(sizeof...(Ts) > 0) destruct(index - 1, data); else ASSERT_NOT_REACHED(); } template void move_construct(size_t index, uint8_t* source, uint8_t* target) { if (index == 0) if constexpr(!is_lvalue_reference_v) new (target) T(move(*reinterpret_cast(source))); else memcpy(target, source, sizeof(remove_reference_t*)); else if constexpr(sizeof...(Ts) > 0) move_construct(index - 1, source, target); else ASSERT_NOT_REACHED(); } template void copy_construct(size_t index, const uint8_t* source, uint8_t* target) { if (index == 0) if constexpr(!is_lvalue_reference_v) new (target) T(*reinterpret_cast(source)); else memcpy(target, source, sizeof(remove_reference_t*)); else if constexpr(sizeof...(Ts) > 0) copy_construct(index - 1, source, target); else ASSERT_NOT_REACHED(); } template void move_assign(size_t index, uint8_t* source, uint8_t* target) { if (index == 0) if constexpr(!is_lvalue_reference_v) *reinterpret_cast(target) = move(*reinterpret_cast(source)); else memcpy(target, source, sizeof(remove_reference_t*)); else if constexpr(sizeof...(Ts) > 0) move_assign(index - 1, source, target); else ASSERT_NOT_REACHED(); } template void copy_assign(size_t index, const uint8_t* source, uint8_t* target) { if (index == 0) if constexpr(!is_lvalue_reference_v) *reinterpret_cast(target) = *reinterpret_cast(source); else memcpy(target, source, sizeof(remove_reference_t*)); else if constexpr(sizeof...(Ts) > 0) copy_assign(index - 1, source, target); else ASSERT_NOT_REACHED(); } } template requires (!is_const_v && ...) class Variant { private: template static constexpr bool can_have() { return detail::index() != invalid_index(); } static constexpr size_t invalid_index() { return sizeof...(Ts); } public: Variant() = default; Variant(Variant&& other) : m_index(other.m_index) { detail::move_construct(other.m_index, other.m_storage, m_storage); other.clear(); } Variant(const Variant& other) : m_index(other.m_index) { detail::copy_construct(other.m_index, other.m_storage, m_storage); } template Variant(T&& value) requires (can_have() && !is_lvalue_reference_v) : m_index(detail::index()) { new (m_storage) T(move(value)); } template Variant(const T& value) requires (can_have() && !is_lvalue_reference_v) : m_index(detail::index()) { new (m_storage) T(value); } ~Variant() { clear(); } Variant& operator=(Variant&& other) { if (m_index == other.m_index) detail::move_assign(m_index, other.m_storage, m_storage); else { clear(); detail::move_construct(other.m_index, other.m_storage, m_storage); m_index = other.m_index; } other.clear(); return *this; } Variant& operator=(const Variant& other) { if (m_index == other.m_index) detail::copy_assign(m_index, other.m_storage, m_storage); else { clear(); detail::copy_construct(other.m_index, other.m_storage, m_storage); m_index = other.m_index; } return *this; } template Variant& operator=(T&& value) requires (can_have() && !is_lvalue_reference_v) { if (size_t index = detail::index(); index == m_index) get() = move(value); else { clear(); new (m_storage) T(move(value)); m_index = index; } return *this; } template Variant& operator=(const T& value) requires (can_have() && !is_lvalue_reference_v) { if (size_t index = detail::index(); index == m_index) get() = value; else { clear(); new (m_storage) T(value); m_index = index; } return *this; } template bool has() const requires (can_have()) { return m_index == detail::index(); } template void set(T&& value) requires (can_have() && !is_lvalue_reference_v) { if (has()) get() = move(value); else { clear(); m_index = detail::index(); new (m_storage) T(move(value)); } } template void set(const T& value) requires (can_have() && !is_lvalue_reference_v) { if (has()) get() = value; else { clear(); m_index = detail::index(); new (m_storage) T(value); } } template void set(T value) requires (can_have() && is_lvalue_reference_v) { clear(); m_index = detail::index(); *reinterpret_cast**>(m_storage) = &value; } template T& get() requires (can_have() && !is_lvalue_reference_v) { ASSERT(has()); return *reinterpret_cast(m_storage); } template const T& get() const requires (can_have() && !is_lvalue_reference_v) { ASSERT(has()); return *reinterpret_cast(m_storage); } template T get() requires (can_have() && is_lvalue_reference_v) { ASSERT(has()); return **reinterpret_cast**>(m_storage); } template const T get() const requires (can_have() && is_lvalue_reference_v) { ASSERT(has()); return **reinterpret_cast**>(m_storage); } void clear() { if (m_index != invalid_index()) { detail::destruct(m_index, m_storage); m_index = invalid_index(); } } private: alignas(detail::max_align_ref_as_ptr()) uint8_t m_storage[detail::max_size_ref_as_ptr()] {}; size_t m_index { invalid_index() }; }; }