#ifndef ENTT_CORE_ANY_HPP #define ENTT_CORE_ANY_HPP #include #include #include #include #include "../config/config.h" #include "../core/utility.hpp" #include "fwd.hpp" #include "type_info.hpp" #include "type_traits.hpp" namespace entt { /** * @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. * @tparam Align Optional alignment requirement. */ template class basic_any { enum class operation : std::uint8_t { copy, move, transfer, assign, destroy, compare, get }; enum class policy : std::uint8_t { owner, ref, cref }; using storage_type = std::aligned_storage_t; using vtable_type = const void *(const operation, const basic_any &, const void *); template static constexpr bool in_situ = Len && alignof(Type) <= alignof(storage_type) && sizeof(Type) <= sizeof(storage_type) && std::is_nothrow_move_constructible_v; template static const void *basic_vtable([[maybe_unused]] const operation op, [[maybe_unused]] const basic_any &value, [[maybe_unused]] const void *other) { static_assert(!std::is_same_v && std::is_same_v>, Type>, "Invalid type"); const Type *element = nullptr; if constexpr(in_situ) { element = value.owner() ? reinterpret_cast(&value.storage) : static_cast(value.instance); } else { element = static_cast(value.instance); } switch(op) { case operation::copy: if constexpr(std::is_copy_constructible_v) { static_cast(const_cast(other))->initialize(*element); } break; case operation::move: if constexpr(in_situ) { if(value.owner()) { return new(&static_cast(const_cast(other))->storage) Type{std::move(*const_cast(element))}; } } return (static_cast(const_cast(other))->instance = std::exchange(const_cast(value).instance, nullptr)); case operation::transfer: if constexpr(std::is_move_assignable_v) { *const_cast(element) = std::move(*static_cast(const_cast(other))); return other; } [[fallthrough]]; case operation::assign: if constexpr(std::is_copy_assignable_v) { *const_cast(element) = *static_cast(other); return other; } break; case operation::destroy: if constexpr(in_situ) { element->~Type(); } else if constexpr(std::is_array_v) { delete[] element; } else { delete element; } break; case operation::compare: if constexpr(!std::is_function_v && !std::is_array_v && is_equality_comparable_v) { return *static_cast(element) == *static_cast(other) ? other : nullptr; } else { return (element == other) ? other : nullptr; } case operation::get: return element; } return nullptr; } template void initialize([[maybe_unused]] Args &&...args) { if constexpr(!std::is_void_v) { info = &type_id>>(); vtable = basic_vtable>>; if constexpr(std::is_lvalue_reference_v) { static_assert(sizeof...(Args) == 1u && (std::is_lvalue_reference_v && ...), "Invalid arguments"); mode = std::is_const_v> ? policy::cref : policy::ref; instance = (std::addressof(args), ...); } else if constexpr(in_situ) { if constexpr(sizeof...(Args) != 0u && std::is_aggregate_v) { new(&storage) Type{std::forward(args)...}; } else { new(&storage) Type(std::forward(args)...); } } else { if constexpr(sizeof...(Args) != 0u && std::is_aggregate_v) { instance = new Type{std::forward(args)...}; } else { instance = new Type(std::forward(args)...); } } } } basic_any(const basic_any &other, const policy pol) ENTT_NOEXCEPT : instance{other.data()}, info{other.info}, vtable{other.vtable}, mode{pol} {} public: /*! @brief Size of the internal storage. */ static constexpr auto length = Len; /*! @brief Alignment requirement. */ static constexpr auto alignment = Align; /*! @brief Default constructor. */ constexpr basic_any() ENTT_NOEXCEPT : instance{}, info{&type_id()}, vtable{}, mode{policy::owner} {} /** * @brief Constructs a wrapper by directly initializing the new object. * @tparam Type Type of object to use to initialize the wrapper. * @tparam Args Types of arguments to use to construct the new instance. * @param args Parameters to use to construct the instance. */ template explicit basic_any(std::in_place_type_t, Args &&...args) : basic_any{} { initialize(std::forward(args)...); } /** * @brief Constructs a wrapper from a given value. * @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, basic_any>>> basic_any(Type &&value) : basic_any{} { initialize>(std::forward(value)); } /** * @brief Copy constructor. * @param other The instance to copy from. */ basic_any(const basic_any &other) : basic_any{} { if(other.vtable) { other.vtable(operation::copy, other, this); } } /** * @brief Move constructor. * @param other The instance to move from. */ basic_any(basic_any &&other) ENTT_NOEXCEPT : instance{}, info{other.info}, vtable{other.vtable}, mode{other.mode} { if(other.vtable) { other.vtable(operation::move, other, this); } } /*! @brief Frees the internal storage, whatever it means. */ ~basic_any() { if(vtable && owner()) { vtable(operation::destroy, *this, nullptr); } } /** * @brief Copy assignment operator. * @param other The instance to copy from. * @return This any object. */ basic_any &operator=(const basic_any &other) { reset(); if(other.vtable) { other.vtable(operation::copy, other, this); } return *this; } /** * @brief Move assignment operator. * @param other The instance to move from. * @return This any object. */ basic_any &operator=(basic_any &&other) ENTT_NOEXCEPT { reset(); if(other.vtable) { other.vtable(operation::move, other, this); info = other.info; vtable = other.vtable; mode = other.mode; } return *this; } /** * @brief Value assignment operator. * @tparam Type Type of object to use to initialize the wrapper. * @param value An instance of an object to use to initialize the wrapper. * @return This any object. */ template std::enable_if_t, basic_any>, basic_any &> operator=(Type &&value) { emplace>(std::forward(value)); return *this; } /** * @brief Returns the object type if any, `type_id()` otherwise. * @return The object type if any, `type_id()` otherwise. */ [[nodiscard]] const type_info &type() const ENTT_NOEXCEPT { return *info; } /** * @brief Returns an opaque pointer to the contained instance. * @return An opaque pointer the contained instance, if any. */ [[nodiscard]] const void *data() const ENTT_NOEXCEPT { return vtable ? vtable(operation::get, *this, nullptr) : nullptr; } /** * @brief Returns an opaque pointer to the contained instance. * @param req Expected type. * @return An opaque pointer the contained instance, if any. */ [[nodiscard]] const void *data(const type_info &req) const ENTT_NOEXCEPT { return *info == req ? data() : nullptr; } /** * @brief Returns an opaque pointer to the contained instance. * @return An opaque pointer the contained instance, if any. */ [[nodiscard]] void *data() ENTT_NOEXCEPT { return (!vtable || mode == policy::cref) ? nullptr : const_cast(vtable(operation::get, *this, nullptr)); } /** * @brief Returns an opaque pointer to the contained instance. * @param req Expected type. * @return An opaque pointer the contained instance, if any. */ [[nodiscard]] void *data(const type_info &req) ENTT_NOEXCEPT { return *info == req ? data() : nullptr; } /** * @brief Replaces the contained object by creating a new instance directly. * @tparam Type Type of object to use to initialize the wrapper. * @tparam Args Types of arguments to use to construct the new instance. * @param args Parameters to use to construct the instance. */ template void emplace(Args &&...args) { reset(); initialize(std::forward(args)...); } /** * @brief Assigns a value to the contained object without replacing it. * @param other The value to assign to the contained object. * @return True in case of success, false otherwise. */ bool assign(const any &other) { if(vtable && mode != policy::cref && *info == *other.info) { return (vtable(operation::assign, *this, other.data()) != nullptr); } return false; } /*! @copydoc assign */ bool assign(any &&other) { if(vtable && mode != policy::cref && *info == *other.info) { if(auto *val = other.data(); val) { return (vtable(operation::transfer, *this, val) != nullptr); } else { return (vtable(operation::assign, *this, std::as_const(other).data()) != nullptr); } } return false; } /*! @brief Destroys contained object */ void reset() { if(vtable && owner()) { vtable(operation::destroy, *this, nullptr); } info = &type_id(); vtable = nullptr; mode = policy::owner; } /** * @brief Returns false if a wrapper is empty, true otherwise. * @return False if the wrapper is empty, true otherwise. */ [[nodiscard]] explicit operator bool() const ENTT_NOEXCEPT { return vtable != nullptr; } /** * @brief Checks if two wrappers differ in their content. * @param other Wrapper with which to compare. * @return False if the two objects differ in their content, true otherwise. */ bool operator==(const basic_any &other) const ENTT_NOEXCEPT { if(vtable && *info == *other.info) { return (vtable(operation::compare, *this, other.data()) != nullptr); } return (!vtable && !other.vtable); } /** * @brief Aliasing constructor. * @return A wrapper that shares a reference to an unmanaged object. */ [[nodiscard]] basic_any as_ref() ENTT_NOEXCEPT { return basic_any{*this, (mode == policy::cref ? policy::cref : policy::ref)}; } /*! @copydoc as_ref */ [[nodiscard]] basic_any as_ref() const ENTT_NOEXCEPT { return basic_any{*this, policy::cref}; } /** * @brief Returns true if a wrapper owns its object, false otherwise. * @return True if the wrapper owns its object, false otherwise. */ [[nodiscard]] bool owner() const ENTT_NOEXCEPT { return (mode == policy::owner); } private: union { const void *instance; storage_type storage; }; const type_info *info; vtable_type *vtable; policy mode; }; /** * @brief Checks if two wrappers differ in their content. * @tparam Len Size of the storage reserved for the small buffer optimization. * @tparam Align Alignment requirement. * @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. */ 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 Type Type to which conversion is required. * @tparam Len Size of the storage reserved for the small buffer optimization. * @tparam Align Alignment requirement. * @param data Target any object. * @return The element converted to the requested type. */ template Type any_cast(const basic_any &data) ENTT_NOEXCEPT { const auto *const instance = any_cast>(&data); ENTT_ASSERT(instance, "Invalid instance"); return static_cast(*instance); } /*! @copydoc any_cast */ 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>(&data); ENTT_ASSERT(instance, "Invalid instance"); return static_cast(*instance); } /*! @copydoc any_cast */ template Type any_cast(basic_any &&data) ENTT_NOEXCEPT { if constexpr(std::is_copy_constructible_v>>) { if(auto *const instance = any_cast>(&data); instance) { return static_cast(std::move(*instance)); } else { return any_cast(data); } } else { auto *const instance = any_cast>(&data); ENTT_ASSERT(instance, "Invalid instance"); return static_cast(std::move(*instance)); } } /*! @copydoc any_cast */ template const Type *any_cast(const basic_any *data) ENTT_NOEXCEPT { const auto &info = type_id>>(); return static_cast(data->data(info)); } /*! @copydoc any_cast */ template Type *any_cast(basic_any *data) ENTT_NOEXCEPT { const auto &info = type_id>>(); // last attempt to make wrappers for const references return their values return static_cast(static_cast, Type> *>(data)->data(info)); } /** * @brief Constructs a wrapper from a given type, passing it all arguments. * @tparam Type Type of object to use to initialize the wrapper. * @tparam Len Size of the storage reserved for the small buffer optimization. * @tparam Align Optional alignment requirement. * @tparam Args Types of arguments to use to construct the new instance. * @param args Parameters to use to construct the instance. * @return A properly initialized wrapper for an object of the given type. */ template::length, std::size_t Align = basic_any::alignment, typename... Args> basic_any make_any(Args &&...args) { return basic_any{std::in_place_type, std::forward(args)...}; } /** * @brief Forwards its argument and avoids copies for lvalue references. * @tparam Len Size of the storage reserved for the small buffer optimization. * @tparam Align Optional alignment requirement. * @tparam Type Type of argument to use to construct the new instance. * @param value Parameter to use to construct the instance. * @return A properly initialized and not necessarily owning wrapper. */ template::length, std::size_t Align = basic_any::alignment, typename Type> basic_any forward_as_any(Type &&value) { return basic_any{std::in_place_type, std::decay_t, Type>>, std::forward(value)}; } } // namespace entt #endif