Files
meta.hpp/headers/meta.hpp/meta_uvalue/uvalue.hpp
2023-02-09 01:37:40 +07:00

623 lines
22 KiB
C++

/*******************************************************************************
* This file is part of the "https://github.com/blackmatov/meta.hpp"
* For conditions of distribution and use, see copyright notice in LICENSE.md
* Copyright (C) 2021-2023, by Matvey Cherevko (blackmatov@gmail.com)
******************************************************************************/
#pragma once
#include "../meta_base.hpp"
#include "../meta_registry.hpp"
#include "../meta_uvalue.hpp"
#include "../meta_detail/value_traits/deref_traits.hpp"
#include "../meta_detail/value_traits/index_traits.hpp"
#include "../meta_detail/value_traits/unmap_traits.hpp"
#include "../meta_detail/value_utilities/utraits.hpp"
namespace meta_hpp
{
struct uvalue::vtable_t final {
const any_type type;
void (*const move)(uvalue&& self, uvalue& to) noexcept;
void (*const copy)(const uvalue& self, uvalue& to);
void (*const reset)(uvalue& self) noexcept;
uvalue (*const deref)(const storage_u& self);
uvalue (*const index)(const storage_u& self, std::size_t i);
uvalue (*const unmap)(const storage_u& self);
template < typename T >
inline static constexpr bool in_internal_v = //
(sizeof(T) <= sizeof(internal_storage_t)) && (alignof(internal_storage_t) % alignof(T) == 0)
&& std::is_nothrow_destructible_v<T> && std::is_nothrow_move_constructible_v<T>;
template < typename T >
inline static constexpr bool in_trivial_internal_v = //
in_internal_v<T> && std::is_trivially_copyable_v<T>;
static std::pair<storage_e, const vtable_t*> unpack_vtag(const uvalue& self) noexcept {
constexpr std::uintptr_t tag_mask{0b11};
const std::uintptr_t vtag{self.storage_.vtag};
return std::make_pair(
static_cast<storage_e>(vtag & tag_mask),
// NOLINTNEXTLINE(*-no-int-to-ptr, *-reinterpret-cast)
reinterpret_cast<const vtable_t*>(vtag & ~tag_mask)
);
}
template < typename T >
static T* storage_cast(storage_u& storage) noexcept {
if constexpr ( in_internal_v<T> ) {
// NOLINTNEXTLINE(*-union-access, *-reinterpret-cast)
return std::launder(reinterpret_cast<T*>(storage.internal.data));
} else {
// NOLINTNEXTLINE(*-union-access)
return static_cast<T*>(storage.external.ptr);
}
}
template < typename T >
static const T* storage_cast(const storage_u& storage) noexcept {
if constexpr ( in_internal_v<T> ) {
// NOLINTNEXTLINE(*-union-access, *-reinterpret-cast)
return std::launder(reinterpret_cast<const T*>(storage.internal.data));
} else {
// NOLINTNEXTLINE(*-union-access)
return static_cast<const T*>(storage.external.ptr);
}
}
template < typename T, typename... Args, typename Tp = std::decay_t<T> >
static Tp& do_ctor(uvalue& dst, Args&&... args) {
META_HPP_ASSERT(!dst);
if constexpr ( in_internal_v<Tp> ) {
std::construct_at(storage_cast<Tp>(dst.storage_), std::forward<Args>(args)...);
dst.storage_.vtag = in_trivial_internal_v<Tp> ? detail::to_underlying(storage_e::trivial)
: detail::to_underlying(storage_e::internal);
} else {
// NOLINTNEXTLINE(*-union-access, *-owning-memory)
dst.storage_.external.ptr = new Tp(std::forward<Args>(args)...);
dst.storage_.vtag = detail::to_underlying(storage_e::external);
}
// NOLINTNEXTLINE(*-reinterpret-cast)
dst.storage_.vtag |= reinterpret_cast<std::uintptr_t>(vtable_t::get<Tp>());
return *storage_cast<Tp>(dst.storage_);
}
static void do_move(uvalue&& self, uvalue& to) noexcept {
META_HPP_ASSERT(!to);
auto&& [tag, vtable] = unpack_vtag(self);
switch ( tag ) {
case storage_e::nothing:
break;
case storage_e::trivial:
to.storage_ = self.storage_;
self.storage_.vtag = 0;
break;
case storage_e::internal:
case storage_e::external:
vtable->move(std::move(self), to);
break;
}
}
static void do_copy(const uvalue& self, uvalue& to) noexcept {
META_HPP_ASSERT(!to);
auto&& [tag, vtable] = unpack_vtag(self);
switch ( tag ) {
case storage_e::nothing:
break;
case storage_e::trivial:
to.storage_ = self.storage_;
break;
case storage_e::internal:
case storage_e::external:
vtable->copy(self, to);
break;
}
}
static void do_reset(uvalue& self) noexcept {
auto&& [tag, vtable] = unpack_vtag(self);
switch ( tag ) {
case storage_e::nothing:
break;
case storage_e::trivial:
self.storage_.vtag = 0;
break;
case storage_e::internal:
case storage_e::external:
vtable->reset(self);
break;
}
}
static void do_swap(uvalue& l, uvalue& r) noexcept {
if ( (&l == &r) || (!l && !r) ) {
return;
}
if ( l && r ) {
if ( unpack_vtag(l).first == storage_e::external ) {
r = std::exchange(l, std::move(r));
} else {
l = std::exchange(r, std::move(l));
}
} else {
if ( l ) {
r = std::move(l);
} else {
l = std::move(r);
}
}
}
template < typename Tp >
// NOLINTNEXTLINE(*-cognitive-complexity)
static vtable_t* get() {
static_assert(std::is_same_v<Tp, std::decay_t<Tp>>);
static vtable_t table{
.type = resolve_type<Tp>(),
.move{[](uvalue&& self, uvalue& to) noexcept {
META_HPP_ASSERT(!to);
META_HPP_ASSERT(self);
Tp* src = storage_cast<Tp>(self.storage_);
if constexpr ( in_internal_v<Tp> ) {
do_ctor<Tp>(to, std::move(*src));
do_reset(self);
} else {
// NOLINTNEXTLINE(*-union-access)
to.storage_.external.ptr = src;
std::swap(to.storage_.vtag, self.storage_.vtag);
}
}},
.copy{[](const uvalue& self, uvalue& to) {
META_HPP_ASSERT(!to);
META_HPP_ASSERT(self);
const Tp* src = storage_cast<Tp>(self.storage_);
if constexpr ( in_internal_v<Tp> ) {
do_ctor<Tp>(to, *src);
} else {
// NOLINTNEXTLINE(*-union-access, *-owning-memory)
to.storage_.external.ptr = new Tp(*src);
to.storage_.vtag = self.storage_.vtag;
}
}},
.reset{[](uvalue& self) noexcept {
META_HPP_ASSERT(self);
Tp* src = storage_cast<Tp>(self.storage_);
if constexpr ( in_internal_v<Tp> ) {
std::destroy_at(src);
} else {
// NOLINTNEXTLINE(*-owning-memory)
delete src;
}
self.storage_.vtag = 0;
}},
.deref{[]() {
if constexpr ( detail::has_deref_traits<Tp> ) {
return +[](const storage_u& self) -> uvalue {
return detail::deref_traits<Tp>{}(*storage_cast<Tp>(self));
};
} else {
return nullptr;
}
}()},
.index{[]() {
if constexpr ( detail::has_index_traits<Tp> ) {
return +[](const storage_u& self, std::size_t i) -> uvalue {
return detail::index_traits<Tp>{}(*storage_cast<Tp>(self), i);
};
} else {
return nullptr;
}
}()},
.unmap{[]() {
if constexpr ( detail::has_unmap_traits<Tp> ) {
return +[](const storage_u& self) -> uvalue {
return detail::unmap_traits<Tp>{}(*storage_cast<Tp>(self));
};
} else {
return nullptr;
}
}()},
};
return &table;
}
};
}
namespace meta_hpp
{
inline uvalue::~uvalue() {
reset();
}
inline uvalue::uvalue(uvalue&& other) noexcept {
vtable_t::do_move(std::move(other), *this);
}
inline uvalue::uvalue(const uvalue& other) {
vtable_t::do_copy(other, *this);
}
inline uvalue& uvalue::operator=(uvalue&& other) noexcept {
if ( this != &other ) {
vtable_t::do_reset(*this);
vtable_t::do_move(std::move(other), *this);
}
return *this;
}
inline uvalue& uvalue::operator=(const uvalue& other) {
if ( this != &other ) {
vtable_t::do_reset(*this);
vtable_t::do_copy(other, *this);
}
return *this;
}
template < typename T, typename Tp >
requires(!detail::any_uvalue_kind<Tp>) //
&& (!detail::is_in_place_type_v<Tp>) //
&& (std::is_copy_constructible_v<Tp>) //
// NOLINTNEXTLINE(*-forwarding-reference-overload)
uvalue::uvalue(T&& val) {
vtable_t::do_ctor<T>(*this, std::forward<T>(val));
}
template < typename T, typename Tp >
requires(!detail::any_uvalue_kind<Tp>) //
&& (!detail::is_in_place_type_v<Tp>) //
&& (std::is_copy_constructible_v<Tp>) //
uvalue& uvalue::operator=(T&& val) {
vtable_t::do_reset(*this);
vtable_t::do_ctor<T>(*this, std::forward<T>(val));
return *this;
}
template < typename T, typename... Args, typename Tp >
requires std::is_copy_constructible_v<Tp> //
&& std::is_constructible_v<Tp, Args...>
uvalue::uvalue(std::in_place_type_t<T>, Args&&... args) {
vtable_t::do_ctor<T>(*this, std::forward<Args>(args)...);
}
template < typename T, typename U, typename... Args, typename Tp >
requires std::is_copy_constructible_v<Tp> //
&& std::is_constructible_v<Tp, std::initializer_list<U>&, Args...>
uvalue::uvalue(std::in_place_type_t<T>, std::initializer_list<U> ilist, Args&&... args) {
vtable_t::do_ctor<T>(*this, ilist, std::forward<Args>(args)...);
}
template < typename T, typename... Args, typename Tp >
requires std::is_copy_constructible_v<Tp> //
&& std::is_constructible_v<Tp, Args...>
Tp& uvalue::emplace(Args&&... args) {
vtable_t::do_reset(*this);
return vtable_t::do_ctor<T>(*this, std::forward<Args>(args)...);
}
template < typename T, typename U, typename... Args, typename Tp >
requires std::is_copy_constructible_v<Tp> //
&& std::is_constructible_v<Tp, std::initializer_list<U>&, Args...>
Tp& uvalue::emplace(std::initializer_list<U> ilist, Args&&... args) {
vtable_t::do_reset(*this);
return vtable_t::do_ctor<T>(*this, ilist, std::forward<Args>(args)...);
}
inline bool uvalue::is_valid() const noexcept {
return storage_.vtag != 0;
}
inline uvalue::operator bool() const noexcept {
return is_valid();
}
inline void uvalue::reset() {
vtable_t::do_reset(*this);
}
inline void uvalue::swap(uvalue& other) noexcept {
vtable_t::do_swap(*this, other);
}
inline any_type uvalue::get_type() const noexcept {
auto&& [tag, vtable] = vtable_t::unpack_vtag(*this);
return tag == storage_e::nothing ? any_type{} : vtable->type;
}
inline void* uvalue::get_data() noexcept {
switch ( vtable_t::unpack_vtag(*this).first ) {
case storage_e::nothing:
return nullptr;
case storage_e::trivial:
case storage_e::internal:
// NOLINTNEXTLINE(*-union-access)
return static_cast<void*>(storage_.internal.data);
case storage_e::external:
// NOLINTNEXTLINE(*-union-access)
return storage_.external.ptr;
}
META_HPP_ASSERT(false);
return nullptr;
}
inline const void* uvalue::get_data() const noexcept {
switch ( vtable_t::unpack_vtag(*this).first ) {
case storage_e::nothing:
return nullptr;
case storage_e::trivial:
case storage_e::internal:
// NOLINTNEXTLINE(*-union-access)
return static_cast<const void*>(storage_.internal.data);
case storage_e::external:
// NOLINTNEXTLINE(*-union-access)
return storage_.external.ptr;
}
META_HPP_ASSERT(false);
return nullptr;
}
inline const void* uvalue::get_cdata() const noexcept {
switch ( vtable_t::unpack_vtag(*this).first ) {
case storage_e::nothing:
return nullptr;
case storage_e::trivial:
case storage_e::internal:
// NOLINTNEXTLINE(*-union-access)
return static_cast<const void*>(storage_.internal.data);
case storage_e::external:
// NOLINTNEXTLINE(*-union-access)
return storage_.external.ptr;
}
META_HPP_ASSERT(false);
return nullptr;
}
inline uvalue uvalue::operator*() const {
auto&& [tag, vtable] = vtable_t::unpack_vtag(*this);
return tag != storage_e::nothing && vtable->deref != nullptr //
? vtable->deref(storage_)
: uvalue{};
}
inline bool uvalue::has_deref_op() const noexcept {
auto&& [tag, vtable] = vtable_t::unpack_vtag(*this);
return tag != storage_e::nothing && vtable->deref != nullptr;
}
inline uvalue uvalue::operator[](std::size_t index) const {
auto&& [tag, vtable] = vtable_t::unpack_vtag(*this);
return tag != storage_e::nothing && vtable->index != nullptr //
? vtable->index(storage_, index)
: uvalue{};
}
inline bool uvalue::has_index_op() const noexcept {
auto&& [tag, vtable] = vtable_t::unpack_vtag(*this);
return tag != storage_e::nothing && vtable->index != nullptr;
}
inline uvalue uvalue::unmap() const {
auto&& [tag, vtable] = vtable_t::unpack_vtag(*this);
return tag != storage_e::nothing && vtable->unmap != nullptr //
? vtable->unmap(storage_)
: uvalue{};
}
inline bool uvalue::has_unmap_op() const noexcept {
auto&& [tag, vtable] = vtable_t::unpack_vtag(*this);
return tag != storage_e::nothing && vtable->unmap != nullptr;
}
template < typename T >
T uvalue::get_as() && {
static_assert(std::is_same_v<T, std::decay_t<T>>);
if constexpr ( detail::pointer_kind<T> ) {
if ( T ptr = try_get_as<T>(); ptr || get_type().is_nullptr() ) {
return ptr;
}
} else {
if ( T* ptr = try_get_as<T>() ) {
return std::move(*ptr);
}
}
META_HPP_THROW_AS(exception, "bad value cast");
}
template < typename T >
auto uvalue::get_as() & -> std::conditional_t<detail::pointer_kind<T>, T, T&> {
static_assert(std::is_same_v<T, std::decay_t<T>>);
if constexpr ( detail::pointer_kind<T> ) {
if ( T ptr = try_get_as<T>(); ptr || get_type().is_nullptr() ) {
return ptr;
}
} else {
if ( T* ptr = try_get_as<T>() ) {
return *ptr;
}
}
META_HPP_THROW_AS(exception, "bad value cast");
}
template < typename T >
auto uvalue::get_as() const& -> std::conditional_t<detail::pointer_kind<T>, T, const T&> {
static_assert(std::is_same_v<T, std::decay_t<T>>);
if constexpr ( detail::pointer_kind<T> ) {
if ( T ptr = try_get_as<T>(); ptr || get_type().is_nullptr() ) {
return ptr;
}
} else {
if ( const T* ptr = try_get_as<T>() ) {
return *ptr;
}
}
META_HPP_THROW_AS(exception, "bad value cast");
}
template < typename T >
// NOLINTNEXTLINE(*-cognitive-complexity)
auto uvalue::try_get_as() noexcept -> std::conditional_t<detail::pointer_kind<T>, T, T*> {
static_assert(std::is_same_v<T, std::decay_t<T>>);
const any_type& from_type = get_type();
const any_type& to_type = resolve_type<T>();
const auto is_a = [](const any_type& base, const any_type& derived) {
return (base == derived) //
|| (base.is_class() && derived.is_class() && base.as_class().is_base_of(derived.as_class()));
};
if constexpr ( detail::pointer_kind<T> ) {
if ( to_type.is_pointer() && from_type.is_nullptr() ) {
return static_cast<T>(nullptr);
}
if ( to_type.is_pointer() && from_type.is_pointer() ) {
const pointer_type& to_type_ptr = to_type.as_pointer();
const bool to_type_ptr_readonly = to_type_ptr.get_flags().has(pointer_flags::is_readonly);
const pointer_type& from_type_ptr = from_type.as_pointer();
const bool from_type_ptr_readonly = from_type_ptr.get_flags().has(pointer_flags::is_readonly);
const any_type& to_data_type = to_type_ptr.get_data_type();
const any_type& from_data_type = from_type_ptr.get_data_type();
if ( to_type_ptr_readonly >= from_type_ptr_readonly ) {
void** from_data_ptr = static_cast<void**>(get_data());
if ( to_data_type.is_void() || to_data_type == from_data_type ) {
void* to_ptr = *from_data_ptr;
return static_cast<T>(to_ptr);
}
if ( is_a(to_data_type, from_data_type) ) {
const class_type& to_data_class = to_data_type.as_class();
const class_type& from_data_class = from_data_type.as_class();
void* to_ptr = detail::pointer_upcast(*from_data_ptr, from_data_class, to_data_class);
return static_cast<T>(to_ptr);
}
}
}
}
if constexpr ( !detail::pointer_kind<T> ) {
if ( from_type == to_type ) {
T* to_ptr = static_cast<T*>(get_data());
return to_ptr;
}
if ( is_a(to_type, from_type) ) {
const class_type& to_class = to_type.as_class();
const class_type& from_class = from_type.as_class();
T* to_ptr = static_cast<T*>(detail::pointer_upcast(get_data(), from_class, to_class));
return to_ptr;
}
}
return nullptr;
}
template < typename T >
// NOLINTNEXTLINE(*-cognitive-complexity)
auto uvalue::try_get_as() const noexcept -> std::conditional_t<detail::pointer_kind<T>, T, const T*> {
static_assert(std::is_same_v<T, std::decay_t<T>>);
const any_type& from_type = get_type();
const any_type& to_type = resolve_type<T>();
const auto is_a = [](const any_type& base, const any_type& derived) {
return (base == derived) //
|| (base.is_class() && derived.is_class() && base.as_class().is_base_of(derived.as_class()));
};
if constexpr ( detail::pointer_kind<T> ) {
if ( to_type.is_pointer() && from_type.is_nullptr() ) {
return static_cast<T>(nullptr);
}
if ( to_type.is_pointer() && from_type.is_pointer() ) {
const pointer_type& to_type_ptr = to_type.as_pointer();
const bool to_type_ptr_readonly = to_type_ptr.get_flags().has(pointer_flags::is_readonly);
const pointer_type& from_type_ptr = from_type.as_pointer();
const bool from_type_ptr_readonly = from_type_ptr.get_flags().has(pointer_flags::is_readonly);
const any_type& to_data_type = to_type_ptr.get_data_type();
const any_type& from_data_type = from_type_ptr.get_data_type();
if ( to_type_ptr_readonly >= from_type_ptr_readonly ) {
void* const* from_data_ptr = static_cast<void* const*>(get_data());
if ( to_data_type.is_void() || to_data_type == from_data_type ) {
void* to_ptr = *from_data_ptr;
return static_cast<T>(to_ptr);
}
if ( is_a(to_data_type, from_data_type) ) {
const class_type& to_data_class = to_data_type.as_class();
const class_type& from_data_class = from_data_type.as_class();
void* to_ptr = detail::pointer_upcast(*from_data_ptr, from_data_class, to_data_class);
return static_cast<T>(to_ptr);
}
}
}
}
if constexpr ( !detail::pointer_kind<T> ) {
if ( from_type == to_type ) {
const T* to_ptr = static_cast<const T*>(get_data());
return to_ptr;
}
if ( is_a(to_type, from_type) ) {
const class_type& to_class = to_type.as_class();
const class_type& from_class = from_type.as_class();
const T* to_ptr = static_cast<const T*>(detail::pointer_upcast(get_data(), from_class, to_class));
return to_ptr;
}
}
return nullptr;
}
}