banan-os/BAN/include/BAN/Variant.h

314 lines
7.8 KiB
C++

#pragma once
#include <BAN/Assert.h>
#include <BAN/Math.h>
#include <BAN/Move.h>
#include <BAN/PlacementNew.h>
#include <string.h>
namespace BAN
{
namespace detail
{
template<typename T>
constexpr size_t size_ref_as_ptr() { return is_lvalue_reference_v<T> ? sizeof(remove_reference_t<T>*) : sizeof(T); }
template<typename T>
constexpr size_t align_ref_as_ptr() { return is_lvalue_reference_v<T> ? alignof(remove_reference_t<T>*) : alignof(T); }
template<typename T>
constexpr size_t max_size_ref_as_ptr() { return size_ref_as_ptr<T>(); }
template<typename T0, typename T1, typename... Ts>
constexpr size_t max_size_ref_as_ptr() { return size_ref_as_ptr<T0>() > size_ref_as_ptr<T1>() ? max_size_ref_as_ptr<T0, Ts...>() : max_size_ref_as_ptr<T1, Ts...>(); }
template<typename T>
constexpr size_t max_align_ref_as_ptr() { return align_ref_as_ptr<T>(); }
template<typename T0, typename T1, typename... Ts>
constexpr size_t max_align_ref_as_ptr() { return align_ref_as_ptr<T0>() > align_ref_as_ptr<T1>() ? max_align_ref_as_ptr<T0, Ts...>() : max_align_ref_as_ptr<T1, Ts...>(); }
template<typename T, typename T0, typename... Ts>
constexpr size_t index()
{
if constexpr(is_same_v<T, T0>)
return 0;
else if constexpr(sizeof...(Ts) == 0)
return 1;
else
return index<T, Ts...>() + 1;
}
template<typename T, typename... Ts>
void destruct(size_t index, uint8_t* data)
{
if (index == 0)
{
if constexpr(!is_lvalue_reference_v<T>)
reinterpret_cast<T*>(data)->~T();
}
else if constexpr(sizeof...(Ts) > 0)
destruct<Ts...>(index - 1, data);
else
ASSERT_NOT_REACHED();
}
template<typename T, typename... Ts>
void move_construct(size_t index, uint8_t* source, uint8_t* target)
{
if (index == 0)
if constexpr(!is_lvalue_reference_v<T>)
new (target) T(move(*reinterpret_cast<T*>(source)));
else
memcpy(target, source, sizeof(remove_reference_t<T>*));
else if constexpr(sizeof...(Ts) > 0)
move_construct<Ts...>(index - 1, source, target);
else
ASSERT_NOT_REACHED();
}
template<typename T, typename... Ts>
void copy_construct(size_t index, const uint8_t* source, uint8_t* target)
{
if (index == 0)
if constexpr(!is_lvalue_reference_v<T>)
new (target) T(*reinterpret_cast<const T*>(source));
else
memcpy(target, source, sizeof(remove_reference_t<T>*));
else if constexpr(sizeof...(Ts) > 0)
copy_construct<Ts...>(index - 1, source, target);
else
ASSERT_NOT_REACHED();
}
template<typename T, typename... Ts>
void move_assign(size_t index, uint8_t* source, uint8_t* target)
{
if (index == 0)
if constexpr(!is_lvalue_reference_v<T>)
*reinterpret_cast<T*>(target) = move(*reinterpret_cast<T*>(source));
else
memcpy(target, source, sizeof(remove_reference_t<T>*));
else if constexpr(sizeof...(Ts) > 0)
move_assign<Ts...>(index - 1, source, target);
else
ASSERT_NOT_REACHED();
}
template<typename T, typename... Ts>
void copy_assign(size_t index, const uint8_t* source, uint8_t* target)
{
if (index == 0)
if constexpr(!is_lvalue_reference_v<T>)
*reinterpret_cast<T*>(target) = *reinterpret_cast<const T*>(source);
else
memcpy(target, source, sizeof(remove_reference_t<T>*));
else if constexpr(sizeof...(Ts) > 0)
copy_assign<Ts...>(index - 1, source, target);
else
ASSERT_NOT_REACHED();
}
}
template<typename... Ts>
requires (!is_const_v<Ts> && ...)
class Variant
{
private:
template<typename T>
static constexpr bool can_have() { return detail::index<T, Ts...>() != 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<Ts...>(other.m_index, other.m_storage, m_storage);
other.clear();
}
Variant(const Variant& other)
: m_index(other.m_index)
{
detail::copy_construct<Ts...>(other.m_index, other.m_storage, m_storage);
}
template<typename T>
Variant(T&& value) requires (can_have<T>() && !is_lvalue_reference_v<T>)
: m_index(detail::index<T, Ts...>())
{
new (m_storage) T(move(value));
}
template<typename T>
Variant(const T& value) requires (can_have<T>() && !is_lvalue_reference_v<T>)
: m_index(detail::index<T, Ts...>())
{
new (m_storage) T(value);
}
~Variant()
{
clear();
}
Variant& operator=(Variant&& other)
{
if (m_index == other.m_index)
detail::move_assign<Ts...>(m_index, other.m_storage, m_storage);
else
{
clear();
detail::move_construct<Ts...>(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<Ts...>(m_index, other.m_storage, m_storage);
else
{
clear();
detail::copy_construct<Ts...>(other.m_index, other.m_storage, m_storage);
m_index = other.m_index;
}
return *this;
}
template<typename T>
Variant& operator=(T&& value) requires (can_have<T>() && !is_lvalue_reference_v<T>)
{
if (size_t index = detail::index<T, Ts...>(); index == m_index)
get<T>() = move(value);
else
{
clear();
new (m_storage) T(move(value));
m_index = index;
}
return *this;
}
template<typename T>
Variant& operator=(const T& value) requires (can_have<T>() && !is_lvalue_reference_v<T>)
{
if (size_t index = detail::index<T, Ts...>(); index == m_index)
get<T>() = value;
else
{
clear();
new (m_storage) T(value);
m_index = index;
}
return *this;
}
template<typename T>
bool has() const requires (can_have<T>())
{
return m_index == detail::index<T, Ts...>();
}
template<typename T, typename... Args>
void emplace(Args&&... args) requires (can_have<T>() && is_constructible_v<T, Args...>)
{
clear();
m_index = detail::index<T, Ts...>();
new (m_storage) T(BAN::forward<Args>(args)...);
}
template<typename T>
void set(T&& value) requires (can_have<T>() && !is_lvalue_reference_v<T>)
{
if (has<T>())
get<T>() = move(value);
else
{
clear();
m_index = detail::index<T, Ts...>();
new (m_storage) T(move(value));
}
}
template<typename T>
void set(const T& value) requires (can_have<T>() && !is_lvalue_reference_v<T>)
{
if (has<T>())
get<T>() = value;
else
{
clear();
m_index = detail::index<T, Ts...>();
new (m_storage) T(value);
}
}
template<typename T>
void set(T value) requires (can_have<T>() && is_lvalue_reference_v<T>)
{
clear();
m_index = detail::index<T, Ts...>();
*reinterpret_cast<remove_reference_t<T>**>(m_storage) = &value;
}
template<typename T>
T& get() requires (can_have<T>() && !is_lvalue_reference_v<T>)
{
ASSERT(has<T>());
return *reinterpret_cast<T*>(m_storage);
}
template<typename T>
const T& get() const requires (can_have<T>() && !is_lvalue_reference_v<T>)
{
ASSERT(has<T>());
return *reinterpret_cast<const T*>(m_storage);
}
template<typename T>
T get() requires (can_have<T>() && is_lvalue_reference_v<T>)
{
ASSERT(has<T>());
return **reinterpret_cast<remove_reference_t<T>**>(m_storage);
}
template<typename T>
const T get() const requires (can_have<T>() && is_lvalue_reference_v<T>)
{
ASSERT(has<T>());
return **reinterpret_cast<const remove_reference_t<T>**>(m_storage);
}
bool has_value() const
{
return m_index != invalid_index();
}
explicit operator bool() const
{
return has_value();
}
void clear()
{
if (m_index != invalid_index())
{
detail::destruct<Ts...>(m_index, m_storage);
m_index = invalid_index();
}
}
private:
alignas(detail::max_align_ref_as_ptr<Ts...>()) uint8_t m_storage[detail::max_size_ref_as_ptr<Ts...>()] {};
size_t m_index { invalid_index() };
};
}