mirror of
https://github.com/SerenityOS/serenity
synced 2024-10-06 16:09:30 +00:00
AK: Avoid the use of typeinfo in Variant
typeid() and RTTI was a nice clutch to implement this, but let's move away from the horrible slowness and implement variants using type indices for faster variants.
This commit is contained in:
parent
659c22e17e
commit
3038b6b7dc
239
AK/Variant.h
239
AK/Variant.h
|
@ -9,72 +9,102 @@
|
||||||
#include <AK/Array.h>
|
#include <AK/Array.h>
|
||||||
#include <AK/BitCast.h>
|
#include <AK/BitCast.h>
|
||||||
#include <AK/StdLibExtras.h>
|
#include <AK/StdLibExtras.h>
|
||||||
#include <typeinfo>
|
|
||||||
|
|
||||||
namespace AK::Detail {
|
namespace AK::Detail {
|
||||||
|
|
||||||
template<typename... Ts>
|
template<typename T, typename IndexType, IndexType InitialIndex, typename... InTypes>
|
||||||
struct Variant;
|
struct VariantIndexOf {
|
||||||
|
static_assert(DependentFalse<T, IndexType, InTypes...>, "Invalid VariantIndex instantiated");
|
||||||
|
};
|
||||||
|
|
||||||
template<typename F, typename... Ts>
|
template<typename T, typename IndexType, IndexType InitialIndex, typename InType, typename... RestOfInTypes>
|
||||||
struct Variant<F, Ts...> {
|
struct VariantIndexOf<T, IndexType, InitialIndex, InType, RestOfInTypes...> {
|
||||||
static void delete_(const std::type_info& id, void* data)
|
consteval IndexType operator()()
|
||||||
{
|
{
|
||||||
if (id == typeid(F))
|
if constexpr (IsSame<T, InType>)
|
||||||
bit_cast<F*>(data)->~F();
|
return InitialIndex;
|
||||||
else
|
else
|
||||||
Variant<Ts...>::delete_(id, data);
|
return VariantIndexOf<T, IndexType, InitialIndex + 1, RestOfInTypes...> {}();
|
||||||
}
|
|
||||||
|
|
||||||
static void move_(const std::type_info& old_id, void* old_data, void* new_data)
|
|
||||||
{
|
|
||||||
if (old_id == typeid(F))
|
|
||||||
new (new_data) F(move(*bit_cast<F*>(old_data)));
|
|
||||||
else
|
|
||||||
Variant<Ts...>::move_(old_id, old_data, new_data);
|
|
||||||
}
|
|
||||||
|
|
||||||
static void copy_(const std::type_info& old_id, const void* old_data, void* new_data)
|
|
||||||
{
|
|
||||||
if (old_id == typeid(F))
|
|
||||||
new (new_data) F(*bit_cast<F*>(old_data));
|
|
||||||
else
|
|
||||||
Variant<Ts...>::copy_(old_id, old_data, new_data);
|
|
||||||
}
|
|
||||||
|
|
||||||
template<typename Visitor>
|
|
||||||
static void visit_(const std::type_info& id, void* data, Visitor&& visitor)
|
|
||||||
{
|
|
||||||
if (id == typeid(F))
|
|
||||||
visitor(*bit_cast<F*>(data));
|
|
||||||
else
|
|
||||||
Variant<Ts...>::visit_(id, data, forward<Visitor>(visitor));
|
|
||||||
}
|
|
||||||
|
|
||||||
template<typename Visitor>
|
|
||||||
static void visit_(const std::type_info& id, const void* data, Visitor&& visitor)
|
|
||||||
{
|
|
||||||
if (id == typeid(F))
|
|
||||||
visitor(*bit_cast<const F*>(data));
|
|
||||||
else
|
|
||||||
Variant<Ts...>::visit_(id, data, forward<Visitor>(visitor));
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
template<>
|
template<typename T, typename IndexType, IndexType InitialIndex>
|
||||||
struct Variant<> {
|
struct VariantIndexOf<T, IndexType, InitialIndex> {
|
||||||
static void delete_(const std::type_info&, void*) { }
|
consteval IndexType operator()() { return InitialIndex; }
|
||||||
static void move_(const std::type_info&, void*, void*) { }
|
};
|
||||||
static void copy_(const std::type_info&, const void*, void*) { }
|
|
||||||
|
template<typename T, typename IndexType, typename... Ts>
|
||||||
|
consteval IndexType index_of()
|
||||||
|
{
|
||||||
|
return VariantIndexOf<T, IndexType, 0, Ts...> {}();
|
||||||
|
}
|
||||||
|
|
||||||
|
template<typename IndexType, IndexType InitialIndex, typename... Ts>
|
||||||
|
struct Variant;
|
||||||
|
|
||||||
|
template<typename IndexType, IndexType InitialIndex, typename F, typename... Ts>
|
||||||
|
struct Variant<IndexType, InitialIndex, F, Ts...> {
|
||||||
|
static constexpr auto current_index = VariantIndexOf<F, IndexType, InitialIndex, F, Ts...> {}();
|
||||||
|
static void delete_(IndexType id, void* data)
|
||||||
|
{
|
||||||
|
if (id == current_index)
|
||||||
|
bit_cast<F*>(data)->~F();
|
||||||
|
else
|
||||||
|
Variant<IndexType, InitialIndex + 1, Ts...>::delete_(id, data);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void move_(IndexType old_id, void* old_data, void* new_data)
|
||||||
|
{
|
||||||
|
if (old_id == current_index)
|
||||||
|
new (new_data) F(move(*bit_cast<F*>(old_data)));
|
||||||
|
else
|
||||||
|
Variant<IndexType, InitialIndex + 1, Ts...>::move_(old_id, old_data, new_data);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void copy_(IndexType old_id, const void* old_data, void* new_data)
|
||||||
|
{
|
||||||
|
if (old_id == current_index)
|
||||||
|
new (new_data) F(*bit_cast<F*>(old_data));
|
||||||
|
else
|
||||||
|
Variant<IndexType, InitialIndex + 1, Ts...>::copy_(old_id, old_data, new_data);
|
||||||
|
}
|
||||||
|
|
||||||
template<typename Visitor>
|
template<typename Visitor>
|
||||||
static void visit_(const std::type_info&, void*, Visitor&&) { }
|
static void visit_(IndexType id, void* data, Visitor&& visitor)
|
||||||
|
{
|
||||||
|
if (id == current_index)
|
||||||
|
visitor(*bit_cast<F*>(data));
|
||||||
|
else
|
||||||
|
Variant<IndexType, InitialIndex + 1, Ts...>::visit_(id, data, forward<Visitor>(visitor));
|
||||||
|
}
|
||||||
|
|
||||||
template<typename Visitor>
|
template<typename Visitor>
|
||||||
static void visit_(const std::type_info&, const void*, Visitor&&) { }
|
static void visit_(IndexType id, const void* data, Visitor&& visitor)
|
||||||
|
{
|
||||||
|
if (id == current_index)
|
||||||
|
visitor(*bit_cast<const F*>(data));
|
||||||
|
else
|
||||||
|
Variant<IndexType, InitialIndex + 1, Ts...>::visit_(id, data, forward<Visitor>(visitor));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
template<typename IndexType, IndexType InitialIndex>
|
||||||
|
struct Variant<IndexType, InitialIndex> {
|
||||||
|
static void delete_(IndexType, void*) { }
|
||||||
|
static void move_(IndexType, void*, void*) { }
|
||||||
|
static void copy_(IndexType, const void*, void*) { }
|
||||||
|
template<typename Visitor>
|
||||||
|
static void visit_(IndexType, void*, Visitor&&) { }
|
||||||
|
template<typename Visitor>
|
||||||
|
static void visit_(IndexType, const void*, Visitor&&) { }
|
||||||
};
|
};
|
||||||
|
|
||||||
struct VariantNoClearTag {
|
struct VariantNoClearTag {
|
||||||
explicit VariantNoClearTag() = default;
|
explicit VariantNoClearTag() = default;
|
||||||
};
|
};
|
||||||
|
struct VariantConstructTag {
|
||||||
|
explicit VariantConstructTag() = default;
|
||||||
|
};
|
||||||
|
|
||||||
template<typename T, typename Base>
|
template<typename T, typename Base>
|
||||||
struct VariantConstructors {
|
struct VariantConstructors {
|
||||||
|
@ -118,15 +148,22 @@ struct Empty {
|
||||||
template<typename... Ts>
|
template<typename... Ts>
|
||||||
struct Variant
|
struct Variant
|
||||||
: public Detail::VariantConstructors<Ts, Variant<Ts...>>... {
|
: public Detail::VariantConstructors<Ts, Variant<Ts...>>... {
|
||||||
|
private:
|
||||||
|
using IndexType = Conditional<sizeof...(Ts) < 255, u8, size_t>; // Note: size+1 reserved for internal value checks
|
||||||
|
static constexpr IndexType invalid_index = sizeof...(Ts);
|
||||||
|
|
||||||
|
template<typename T>
|
||||||
|
static constexpr IndexType index_of() { return Detail::index_of<T, IndexType, Ts...>(); }
|
||||||
|
|
||||||
|
public:
|
||||||
template<typename... NewTs>
|
template<typename... NewTs>
|
||||||
friend struct Variant;
|
friend struct Variant;
|
||||||
|
|
||||||
Variant(const Variant& old)
|
Variant(const Variant& old)
|
||||||
: Detail::VariantConstructors<Ts, Variant<Ts...>>()...
|
: Detail::VariantConstructors<Ts, Variant<Ts...>>()...
|
||||||
, m_type_info(old.m_type_info)
|
, m_index(old.m_index)
|
||||||
{
|
{
|
||||||
Helper::copy_(*old.m_type_info, old.m_data, m_data);
|
Helper::copy_(old.m_index, old.m_data, m_data);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Note: A moved-from variant emulates the state of the object it contains
|
// Note: A moved-from variant emulates the state of the object it contains
|
||||||
|
@ -135,129 +172,139 @@ struct Variant
|
||||||
// but it will still contain the "moved-from" state of the object it previously contained.
|
// but it will still contain the "moved-from" state of the object it previously contained.
|
||||||
Variant(Variant&& old)
|
Variant(Variant&& old)
|
||||||
: Detail::VariantConstructors<Ts, Variant<Ts...>>()...
|
: Detail::VariantConstructors<Ts, Variant<Ts...>>()...
|
||||||
, m_type_info(old.m_type_info)
|
, m_index(old.m_index)
|
||||||
{
|
{
|
||||||
Helper::move_(*old.m_type_info, old.m_data, m_data);
|
Helper::move_(old.m_index, old.m_data, m_data);
|
||||||
}
|
}
|
||||||
|
|
||||||
~Variant()
|
~Variant()
|
||||||
{
|
{
|
||||||
Helper::delete_(*m_type_info, m_data);
|
Helper::delete_(m_index, m_data);
|
||||||
}
|
}
|
||||||
|
|
||||||
Variant& operator=(const Variant& other)
|
Variant& operator=(const Variant& other)
|
||||||
{
|
{
|
||||||
m_type_info = other.m_type_info;
|
m_index = other.m_index;
|
||||||
Helper::copy_(*other.m_type_info, other.m_data, m_data);
|
Helper::copy_(other.m_index, other.m_data, m_data);
|
||||||
return *this;
|
return *this;
|
||||||
}
|
}
|
||||||
|
|
||||||
Variant& operator=(Variant&& other)
|
Variant& operator=(Variant&& other)
|
||||||
{
|
{
|
||||||
m_type_info = other.m_type_info;
|
m_index = other.m_index;
|
||||||
Helper::move_(*other.m_type_info, other.m_data, m_data);
|
Helper::move_(other.m_index, other.m_data, m_data);
|
||||||
return *this;
|
return *this;
|
||||||
}
|
}
|
||||||
|
|
||||||
using Detail::VariantConstructors<Ts, Variant<Ts...>>::VariantConstructors...;
|
using Detail::VariantConstructors<Ts, Variant<Ts...>>::VariantConstructors...;
|
||||||
|
|
||||||
template<typename T>
|
template<typename T>
|
||||||
void set(T&& t)
|
void set(T&& t) requires(index_of<T>() != invalid_index)
|
||||||
{
|
{
|
||||||
Helper::delete_(*m_type_info, m_data);
|
using StrippedT = RemoveCV<RemoveReference<T>>;
|
||||||
new (m_data) T(forward<T>(t));
|
constexpr auto new_index = index_of<StrippedT>();
|
||||||
m_type_info = &typeid(T);
|
Helper::delete_(m_index, m_data);
|
||||||
|
new (m_data) StrippedT(forward<T>(t));
|
||||||
|
m_index = new_index;
|
||||||
}
|
}
|
||||||
|
|
||||||
template<typename T>
|
template<typename T>
|
||||||
void set(T&& t, Detail::VariantNoClearTag)
|
void set(T&& t, Detail::VariantNoClearTag)
|
||||||
{
|
{
|
||||||
new (m_data) T(forward<T>(t));
|
using StrippedT = RemoveCV<RemoveReference<T>>;
|
||||||
m_type_info = &typeid(T);
|
constexpr auto new_index = index_of<StrippedT>();
|
||||||
|
new (m_data) StrippedT(forward<T>(t));
|
||||||
|
m_index = new_index;
|
||||||
}
|
}
|
||||||
|
|
||||||
template<typename T>
|
template<typename T>
|
||||||
T* get_pointer()
|
T* get_pointer()
|
||||||
{
|
{
|
||||||
if (typeid(T) == *m_type_info)
|
if (index_of<T>() == m_index)
|
||||||
return reinterpret_cast<T*>(m_data);
|
return bit_cast<T*>(&m_data);
|
||||||
return nullptr;
|
return nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
template<typename T>
|
template<typename T>
|
||||||
T& get()
|
[[gnu::noinline]] T& get()
|
||||||
{
|
{
|
||||||
VERIFY(typeid(T) == *m_type_info);
|
VERIFY(has<T>());
|
||||||
return *reinterpret_cast<T*>(m_data);
|
return *bit_cast<T*>(&m_data);
|
||||||
}
|
}
|
||||||
|
|
||||||
template<typename T>
|
template<typename T>
|
||||||
const T* get_pointer() const
|
[[gnu::noinline]] const T* get_pointer() const
|
||||||
{
|
{
|
||||||
if (typeid(T) == *m_type_info)
|
if (index_of<T>() == m_index)
|
||||||
return reinterpret_cast<const T*>(m_data);
|
return bit_cast<const T*>(&m_data);
|
||||||
return nullptr;
|
return nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
template<typename T>
|
template<typename T>
|
||||||
const T& get() const
|
[[gnu::noinline]] const T& get() const
|
||||||
{
|
{
|
||||||
VERIFY(typeid(T) == *m_type_info);
|
VERIFY(has<T>());
|
||||||
return *reinterpret_cast<const T*>(m_data);
|
return *bit_cast<const T*>(&m_data);
|
||||||
}
|
}
|
||||||
|
|
||||||
template<typename T>
|
template<typename T>
|
||||||
[[nodiscard]] bool has() const
|
[[nodiscard]] bool has() const
|
||||||
{
|
{
|
||||||
return typeid(T) == *m_type_info;
|
return index_of<T>() == m_index;
|
||||||
}
|
}
|
||||||
|
|
||||||
template<typename... Fs>
|
template<typename... Fs>
|
||||||
void visit(Fs&&... functions)
|
void visit(Fs&&... functions)
|
||||||
{
|
{
|
||||||
Visitor<Fs...> visitor { forward<Fs>(functions)... };
|
Visitor<Fs...> visitor { forward<Fs>(functions)... };
|
||||||
Helper::visit_(*m_type_info, m_data, visitor);
|
Helper::visit_(m_index, m_data, visitor);
|
||||||
}
|
}
|
||||||
|
|
||||||
template<typename... Fs>
|
template<typename... Fs>
|
||||||
void visit(Fs&&... functions) const
|
void visit(Fs&&... functions) const
|
||||||
{
|
{
|
||||||
Visitor<Fs...> visitor { forward<Fs>(functions)... };
|
Visitor<Fs...> visitor { forward<Fs>(functions)... };
|
||||||
Helper::visit_(*m_type_info, m_data, visitor);
|
Helper::visit_(m_index, m_data, visitor);
|
||||||
}
|
}
|
||||||
|
|
||||||
template<typename... NewTs>
|
template<typename... NewTs>
|
||||||
Variant<NewTs...> downcast() &&
|
Variant<NewTs...> downcast() &&
|
||||||
{
|
{
|
||||||
VERIFY(covers<NewTs...>());
|
Variant<NewTs...> instance { Variant<NewTs...>::invalid_index, Detail::VariantConstructTag {} };
|
||||||
Variant<NewTs...> instance { m_type_info };
|
visit([&](auto& value) {
|
||||||
Helper::move_(*m_type_info, m_data, instance.m_data);
|
if constexpr (Variant<NewTs...>::template can_contain<RemoveCV<RemoveReference<decltype(value)>>>())
|
||||||
|
instance.set(move(value), Detail::VariantNoClearTag {});
|
||||||
|
});
|
||||||
|
VERIFY(instance.m_index != instance.invalid_index);
|
||||||
return instance;
|
return instance;
|
||||||
}
|
}
|
||||||
|
|
||||||
template<typename... NewTs>
|
template<typename... NewTs>
|
||||||
Variant<NewTs...> downcast() &
|
Variant<NewTs...> downcast() &
|
||||||
{
|
{
|
||||||
VERIFY(covers<NewTs...>());
|
Variant<NewTs...> instance { Variant<NewTs...>::invalid_index, Detail::VariantConstructTag {} };
|
||||||
Variant<NewTs...> instance { m_type_info };
|
visit([&](const auto& value) {
|
||||||
Helper::copy_(*m_type_info, m_data, instance.m_data);
|
if constexpr (Variant<NewTs...>::template can_contain<RemoveCV<RemoveReference<decltype(value)>>>())
|
||||||
|
instance.set(value, Detail::VariantNoClearTag {});
|
||||||
|
});
|
||||||
|
VERIFY(instance.m_index != instance.invalid_index);
|
||||||
return instance;
|
return instance;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
template<typename T>
|
||||||
|
static constexpr bool can_contain()
|
||||||
|
{
|
||||||
|
return index_of<T>() != invalid_index;
|
||||||
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
static constexpr auto data_size = integer_sequence_generate_array<size_t>(0, IntegerSequence<size_t, sizeof(Ts)...>()).max();
|
static constexpr auto data_size = integer_sequence_generate_array<size_t>(0, IntegerSequence<size_t, sizeof(Ts)...>()).max();
|
||||||
static constexpr auto data_alignment = integer_sequence_generate_array<size_t>(0, IntegerSequence<size_t, alignof(Ts)...>()).max();
|
static constexpr auto data_alignment = integer_sequence_generate_array<size_t>(0, IntegerSequence<size_t, alignof(Ts)...>()).max();
|
||||||
using Helper = Detail::Variant<Ts...>;
|
using Helper = Detail::Variant<IndexType, 0, Ts...>;
|
||||||
|
|
||||||
template<typename... NewTs>
|
explicit Variant(IndexType index, Detail::VariantConstructTag)
|
||||||
bool covers() const
|
: Detail::MergeAndDeduplicatePacks<Detail::VariantConstructors<Ts, Variant<Ts...>>...>()
|
||||||
{
|
, m_index(index)
|
||||||
return ((typeid(NewTs) == *m_type_info) || ...);
|
|
||||||
}
|
|
||||||
|
|
||||||
explicit Variant(const std::type_info* type_info)
|
|
||||||
: Detail::VariantConstructors<Ts, Variant<Ts...>>()...
|
|
||||||
, m_type_info(type_info)
|
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -275,7 +322,7 @@ private:
|
||||||
// Note: Make sure not to default-initialize!
|
// Note: Make sure not to default-initialize!
|
||||||
// VariantConstructors::VariantConstructors(T) will set this to the correct value
|
// VariantConstructors::VariantConstructors(T) will set this to the correct value
|
||||||
// So default-constructing to anything will leave the first initialization with that value instead of the correct one.
|
// So default-constructing to anything will leave the first initialization with that value instead of the correct one.
|
||||||
const std::type_info* m_type_info;
|
IndexType m_index;
|
||||||
};
|
};
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue