From 07565927f2e3bfc3b6f1b0ef5b6df1ccdb8d8cc8 Mon Sep 17 00:00:00 2001 From: Michele Caini Date: Fri, 19 Feb 2021 12:51:30 +0100 Subject: [PATCH] any: * added configurable sbo size (0 -> forced dyn) * reverted changes for forced dyn vtable --- TODO | 3 +- src/entt/core/any.hpp | 310 ++++++++++++++++++++--------------------- src/entt/core/fwd.hpp | 8 ++ test/entt/core/any.cpp | 2 +- 4 files changed, 164 insertions(+), 159 deletions(-) diff --git a/TODO b/TODO index e679b154b..6d36784ea 100644 --- a/TODO +++ b/TODO @@ -28,11 +28,12 @@ WIP: * HP: paginate pools * HP: headless (sparse set only) view * HP: write documentation for custom storages and views!! -* HP: any/poly: configurable sbo size, compile-time policies like sbo-required. +* HP: meta_any/poly: configurable sbo size. * HP: registry: use a poly object for pools, no more pool_data type. * HP: make runtime views use opaque storage and therefore return also elements. * HP: add exclude-only views to combine with packs * HP: entity-aware observer, add observer functions aside observer class +* HP: any and the like: remove constructor that accepts reference wrapper, allow only in-place T&? * deprecate non-owning groups in favor of owning views and view packs, introduce lazy owning views * pagination doesn't work nicely across boundaries probably, give it a look. RO operations are fine, adding components maybe not. * snapshot: support for range-based archives diff --git a/src/entt/core/any.hpp b/src/entt/core/any.hpp index 658b939fe..984adb441 100644 --- a/src/entt/core/any.hpp +++ b/src/entt/core/any.hpp @@ -2,11 +2,13 @@ #define ENTT_CORE_ANY_HPP +#include #include #include #include #include #include "../config/config.h" +#include "fwd.hpp" #include "type_info.hpp" #include "type_traits.hpp" @@ -14,15 +16,19 @@ namespace entt { -/*! @brief A SBO friendly, type-safe container for single values of any type. */ -class any { +/** + * @brief A SBO friendly, type-safe container for single values of any type. + * @tparam Len Size of the storage reserved for the small buffer optimization. + */ +template +class basic_any { enum class operation { COPY, MOVE, DTOR, COMP, ADDR, CADDR, REF, CREF, TYPE }; - using storage_type = std::aligned_storage_t; - using vtable_type = const void *(const operation, const any &, const void *); + using storage_type = std::aligned_storage_t; + using vtable_type = const void *(const operation, const basic_any &, const void *); template - static constexpr auto in_situ = sizeof(Type) <= sizeof(storage_type) && std::is_nothrow_move_constructible_v; + static constexpr auto in_situ = (Len != 0u) && (sizeof(Type) <= sizeof(storage_type)) && std::is_nothrow_move_constructible_v; template [[nodiscard]] static bool compare(const void *lhs, const void *rhs) { @@ -38,123 +44,113 @@ class any { return *const_cast(static_cast(to)); } - static const void * vtable_void(const operation, const any &, const void *) { - return nullptr; - } - template - static const void * vtable_ref(const operation op, const any &from, const void *to) { - using base_type = std::remove_const_t; + static const void * basic_vtable([[maybe_unused]] const operation op, [[maybe_unused]] const basic_any &from, [[maybe_unused]] const void *to) { + if constexpr(!std::is_void_v) { + if constexpr(std::is_lvalue_reference_v) { + using base_type = std::remove_const_t>; - switch(op) { - case operation::COPY: - if constexpr(std::is_copy_constructible_v) { - as(to).emplace(*static_cast(from.instance)); - } - break; - case operation::MOVE: - as(to).instance = from.instance; - case operation::DTOR: - break; - case operation::COMP: - return compare(from.instance, to) ? to : nullptr; - case operation::ADDR: - return std::is_const_v ? nullptr : from.instance; - case operation::CADDR: - return from.instance; - case operation::REF: - as(to).instance = from.instance; - as(to).vtable = vtable_ref; - break; - case operation::CREF: - as(to).instance = from.instance; - as(to).vtable = vtable_ref; - break; - case operation::TYPE: - as(to) = type_id(); - break; - } + switch(op) { + case operation::COPY: + if constexpr(std::is_copy_constructible_v) { + as(to).emplace(*static_cast(from.instance)); + } + break; + case operation::MOVE: + as(to).instance = from.instance; + case operation::DTOR: + break; + case operation::COMP: + return compare(from.instance, to) ? to : nullptr; + case operation::ADDR: + return std::is_const_v> ? nullptr : from.instance; + case operation::CADDR: + return from.instance; + case operation::REF: + as(to).instance = from.instance; + as(to).vtable = basic_vtable; + break; + case operation::CREF: + as(to).instance = from.instance; + as(to).vtable = basic_vtable; + break; + case operation::TYPE: + as(to) = type_id(); + break; + } + } else if constexpr(in_situ) { + #if defined(__cpp_lib_launder) && __cpp_lib_launder >= 201606L + auto *instance = const_cast(std::launder(reinterpret_cast(&from.storage))); + #else + auto *instance = const_cast(reinterpret_cast(&from.storage)); + #endif - return nullptr; - } - - template - static const void * vtable_in_situ(const operation op, const any &from, const void *to) { - #if defined(__cpp_lib_launder) && __cpp_lib_launder >= 201606L - auto *instance = const_cast(std::launder(reinterpret_cast(&from.storage))); - #else - auto *instance = const_cast(reinterpret_cast(&from.storage)); - #endif - - switch(op) { - case operation::COPY: - if constexpr(std::is_copy_constructible_v) { - new (&as(to).storage) Type{std::as_const(*instance)}; - as(to).vtable = from.vtable; - } - break; - case operation::MOVE: - new (&as(to).storage) Type{std::move(*instance)}; - [[fallthrough]]; - case operation::DTOR: - instance->~Type(); - break; - case operation::COMP: - return compare(instance, to) ? to : nullptr; - case operation::ADDR: - case operation::CADDR: - return instance; - case operation::REF: - as(to).instance = instance; - as(to).vtable = vtable_ref; - break; - case operation::CREF: - as(to).instance = instance; - as(to).vtable = vtable_ref; - break; - case operation::TYPE: - as(to) = type_id(); - break; - } - - return nullptr; - } - - template - static const void * vtable_default(const operation op, const any &from, const void *to) { - switch(op) { - case operation::COPY: - if constexpr(std::is_copy_constructible_v) { - as(to).instance = new Type{*static_cast(from.instance)}; - as(to).vtable = from.vtable; - } - break; - case operation::MOVE: - as(to).instance = from.instance; - break; - case operation::DTOR: - if constexpr(std::is_array_v) { - delete[] static_cast(from.instance); + switch(op) { + case operation::COPY: + if constexpr(std::is_copy_constructible_v) { + new (&as(to).storage) Type{std::as_const(*instance)}; + as(to).vtable = from.vtable; + } + break; + case operation::MOVE: + new (&as(to).storage) Type{std::move(*instance)}; + [[fallthrough]]; + case operation::DTOR: + instance->~Type(); + break; + case operation::COMP: + return compare(instance, to) ? to : nullptr; + case operation::ADDR: + case operation::CADDR: + return instance; + case operation::REF: + as(to).instance = instance; + as(to).vtable = basic_vtable; + break; + case operation::CREF: + as(to).instance = instance; + as(to).vtable = basic_vtable; + break; + case operation::TYPE: + as(to) = type_id(); + break; + } } else { - delete static_cast(from.instance); + switch(op) { + case operation::COPY: + if constexpr(std::is_copy_constructible_v) { + as(to).instance = new Type{*static_cast(from.instance)}; + as(to).vtable = from.vtable; + } + break; + case operation::MOVE: + as(to).instance = from.instance; + break; + case operation::DTOR: + if constexpr(std::is_array_v) { + delete[] static_cast(from.instance); + } else { + delete static_cast(from.instance); + } + break; + case operation::COMP: + return compare(from.instance, to) ? to : nullptr; + case operation::ADDR: + case operation::CADDR: + return from.instance; + case operation::REF: + as(to).instance = from.instance; + as(to).vtable = basic_vtable; + break; + case operation::CREF: + as(to).instance = from.instance; + as(to).vtable = basic_vtable; + break; + case operation::TYPE: + as(to) = type_id(); + break; + } } - break; - case operation::COMP: - return compare(from.instance, to) ? to : nullptr; - case operation::ADDR: - case operation::CADDR: - return from.instance; - case operation::REF: - as(to).instance = from.instance; - as(to).vtable = vtable_ref; - break; - case operation::CREF: - as(to).instance = from.instance; - as(to).vtable = vtable_ref; - break; - case operation::TYPE: - as(to) = type_id(); - break; } return nullptr; @@ -162,8 +158,8 @@ class any { public: /*! @brief Default constructor. */ - any() ENTT_NOEXCEPT - : any{std::in_place_type} + basic_any() ENTT_NOEXCEPT + : basic_any{std::in_place_type} {} /** @@ -173,21 +169,18 @@ public: * @param args Parameters to use to construct the instance. */ template - explicit any(std::in_place_type_t, [[maybe_unused]] Args &&... args) - : vtable{&vtable_void}, + explicit basic_any(std::in_place_type_t, [[maybe_unused]] Args &&... args) + : vtable{&basic_vtable}, instance{} { if constexpr(!std::is_void_v) { if constexpr(std::is_lvalue_reference_v) { static_assert(sizeof...(Args) == 1u && (std::is_lvalue_reference_v && ...)); instance = (&args, ...); - vtable = vtable_ref>; } else if constexpr(in_situ) { new (&storage) Type(std::forward(args)...); - vtable = vtable_in_situ; } else { instance = new Type(std::forward(args)...); - vtable = vtable_default; } } } @@ -198,8 +191,8 @@ public: * @param value An instance of an object to use to initialize the wrapper. */ template - any(std::reference_wrapper value) ENTT_NOEXCEPT - : any{std::in_place_type, value.get()} + basic_any(std::reference_wrapper value) ENTT_NOEXCEPT + : basic_any{std::in_place_type, value.get()} {} /** @@ -207,17 +200,17 @@ public: * @tparam Type Type of object to use to initialize the wrapper. * @param value An instance of an object to use to initialize the wrapper. */ - template, any>>> - any(Type &&value) - : any{std::in_place_type>, std::forward(value)} + template, basic_any>>> + basic_any(Type &&value) + : basic_any{std::in_place_type>, std::forward(value)} {} /** * @brief Copy constructor. * @param other The instance to copy from. */ - any(const any &other) - : any{} + basic_any(const basic_any &other) + : basic_any{} { other.vtable(operation::COPY, other, this); } @@ -226,15 +219,15 @@ public: * @brief Move constructor. * @param other The instance to move from. */ - any(any &&other) ENTT_NOEXCEPT - : any{} + basic_any(basic_any &&other) ENTT_NOEXCEPT + : basic_any{} { other.vtable(operation::MOVE, other, this); - vtable = std::exchange(other.vtable, &vtable_void); + vtable = std::exchange(other.vtable, &basic_vtable); } /*! @brief Frees the internal storage, whatever it means. */ - ~any() { + ~basic_any() { vtable(operation::DTOR, *this, nullptr); } @@ -243,7 +236,7 @@ public: * @param other The instance to assign from. * @return This any object. */ - any & operator=(any other) { + basic_any & operator=(basic_any other) { swap(*this, other); return *this; } @@ -279,12 +272,12 @@ public: */ template void emplace(Args &&... args) { - *this = any{std::in_place_type, std::forward(args)...}; + *this = basic_any{std::in_place_type, std::forward(args)...}; } /*! @brief Destroys contained object */ void reset() { - *this = any{}; + *this = basic_any{}; } /** @@ -300,7 +293,7 @@ public: * @param other Wrapper with which to compare. * @return False if the two objects differ in their content, true otherwise. */ - bool operator==(const any &other) const ENTT_NOEXCEPT { + bool operator==(const basic_any &other) const ENTT_NOEXCEPT { return type() == other.type() && (vtable(operation::COMP, *this, other.data()) == other.data()); } @@ -309,8 +302,8 @@ public: * @param lhs A valid any object. * @param rhs A valid any object. */ - friend void swap(any &lhs, any &rhs) { - any tmp{}; + friend void swap(basic_any &lhs, basic_any &rhs) { + basic_any tmp{}; lhs.vtable(operation::MOVE, lhs, &tmp); rhs.vtable(operation::MOVE, rhs, &lhs); lhs.vtable(operation::MOVE, tmp, &rhs); @@ -321,15 +314,15 @@ public: * @brief Aliasing constructor. * @return An any that shares a reference to an unmanaged object. */ - [[nodiscard]] any as_ref() ENTT_NOEXCEPT { - any ref{}; + [[nodiscard]] basic_any as_ref() ENTT_NOEXCEPT { + basic_any ref{}; vtable(operation::REF, *this, &ref); return ref; } /*! @copydoc as_ref */ - [[nodiscard]] any as_ref() const ENTT_NOEXCEPT { - any ref{}; + [[nodiscard]] basic_any as_ref() const ENTT_NOEXCEPT { + basic_any ref{}; vtable(operation::CREF, *this, &ref); return ref; } @@ -342,23 +335,26 @@ private: /** * @brief Checks if two wrappers differ in their content. + * @tparam Len Size of the storage reserved for the small buffer optimization. * @param lhs A wrapper, either empty or not. * @param rhs A wrapper, either empty or not. * @return True if the two wrappers differ in their content, false otherwise. */ -[[nodiscard]] inline bool operator!=(const any &lhs, const any &rhs) ENTT_NOEXCEPT { +template +[[nodiscard]] inline bool operator!=(const basic_any &lhs, const basic_any &rhs) ENTT_NOEXCEPT { return !(lhs == rhs); } /** * @brief Performs type-safe access to the contained object. + * @tparam Len Size of the storage reserved for the small buffer optimization. * @tparam Type Type to which conversion is required. * @param data Target any object. * @return The element converted to the requested type. */ -template -Type any_cast(const any &data) ENTT_NOEXCEPT { +template +Type any_cast(const basic_any &data) ENTT_NOEXCEPT { const auto * const instance = any_cast>(&data); ENTT_ASSERT(instance); return static_cast(*instance); @@ -366,8 +362,8 @@ Type any_cast(const any &data) ENTT_NOEXCEPT { /*! @copydoc any_cast */ -template -Type any_cast(any &data) ENTT_NOEXCEPT { +template +Type any_cast(basic_any &data) ENTT_NOEXCEPT { // forces const on non-reference types to make them work also with wrappers for const references auto * const instance = any_cast, std::remove_reference_t, const Type>>(&data); ENTT_ASSERT(instance); @@ -376,8 +372,8 @@ Type any_cast(any &data) ENTT_NOEXCEPT { /*! @copydoc any_cast */ -template -Type any_cast(any &&data) ENTT_NOEXCEPT { +template +Type any_cast(basic_any &&data) ENTT_NOEXCEPT { // forces const on non-reference types to make them work also with wrappers for const references auto * const instance = any_cast, std::remove_reference_t, const Type>>(&data); ENTT_ASSERT(instance); @@ -386,17 +382,17 @@ Type any_cast(any &&data) ENTT_NOEXCEPT { /*! @copydoc any_cast */ -template -const Type * any_cast(const any *data) ENTT_NOEXCEPT { +template +const Type * any_cast(const basic_any *data) ENTT_NOEXCEPT { return (data->type() == type_id() ? static_cast(data->data()) : nullptr); } /*! @copydoc any_cast */ -template -Type * any_cast(any *data) ENTT_NOEXCEPT { +template +Type * any_cast(basic_any *data) ENTT_NOEXCEPT { // last attempt to make wrappers for const references return their values - return (data->type() == type_id() ? static_cast(static_cast *>(data)->data()) : nullptr); + return (data->type() == type_id() ? static_cast(static_cast, Type> *>(data)->data()) : nullptr); } diff --git a/src/entt/core/fwd.hpp b/src/entt/core/fwd.hpp index c155979a4..d1498288b 100644 --- a/src/entt/core/fwd.hpp +++ b/src/entt/core/fwd.hpp @@ -8,10 +8,18 @@ namespace entt { +template +class basic_any; + + /*! @brief Alias declaration for type identifiers. */ using id_type = ENTT_ID_TYPE; +/*! @brief Alias declaration for the most common use case. */ +using any = basic_any<>; + + } diff --git a/test/entt/core/any.cpp b/test/entt/core/any.cpp index 46d6264df..99ef08b08 100644 --- a/test/entt/core/any.cpp +++ b/test/entt/core/any.cpp @@ -801,7 +801,7 @@ TEST(Any, AnyCast) { ASSERT_EQ(*entt::any_cast(&cany), 42); ASSERT_EQ(entt::any_cast(any), 42); ASSERT_EQ(entt::any_cast(cany), 42); - ASSERT_EQ(entt::any_cast(42), 42); + ASSERT_EQ(entt::any_cast(entt::any{42}), 42); } TEST(Any, NotCopyableType) {