#ifndef ENTT_CORE_ANY_HPP #define ENTT_CORE_ANY_HPP #include #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, DTOR, COMP, ADDR, CADDR, TYPE }; 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 &, 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 [[nodiscard]] static constexpr policy type_to_policy() { if constexpr(std::is_lvalue_reference_v) { if constexpr(std::is_const_v>) { return policy::CREF; } else { return policy::REF; } } else { return policy::OWNER; } } template [[nodiscard]] static bool compare(const void *lhs, const void *rhs) { if constexpr(!std::is_function_v && is_equality_comparable_v) { return *static_cast(lhs) == *static_cast(rhs); } else { return lhs == rhs; } } template static const void * basic_vtable([[maybe_unused]] const operation op, [[maybe_unused]] const basic_any &from, [[maybe_unused]] void *to) { static_assert(std::is_same_v>, Type>, "Invalid type"); if constexpr(!std::is_void_v) { const Type *instance = (in_situ && from.mode == policy::OWNER) ? ENTT_LAUNDER(reinterpret_cast(&from.storage)) : static_cast(from.instance); switch(op) { case operation::COPY: if constexpr(std::is_copy_constructible_v) { static_cast(to)->emplace(*instance); } break; case operation::MOVE: if constexpr(in_situ) { if(from.mode == policy::OWNER) { return new (&static_cast(to)->storage) Type{std::move(*const_cast(instance))}; } } return (static_cast(to)->instance = std::exchange(const_cast(from).instance, nullptr)); case operation::DTOR: if(from.mode == policy::OWNER) { if constexpr(in_situ) { instance->~Type(); } else if constexpr(std::is_array_v) { delete[] instance; } else { delete instance; } } break; case operation::COMP: return compare(instance, (*static_cast(to))->data()) ? to : nullptr; case operation::ADDR: if(from.mode == policy::CREF) { return nullptr; } [[fallthrough]]; case operation::CADDR: return instance; case operation::TYPE: *static_cast(to) = type_id(); break; } } return nullptr; } template void initialize([[maybe_unused]] Args &&... args) { if constexpr(!std::is_void_v) { if constexpr(std::is_lvalue_reference_v) { static_assert(sizeof...(Args) == 1u && (std::is_lvalue_reference_v && ...), "Invalid arguments"); 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()}, 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. */ basic_any() ENTT_NOEXCEPT : instance{}, vtable{&basic_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) : instance{}, vtable{&basic_vtable>>}, mode{type_to_policy()} { initialize(std::forward(args)...); } /** * @brief Constructs a wrapper that holds an unmanaged object. * @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(std::reference_wrapper value) ENTT_NOEXCEPT : basic_any{} { // invokes deprecated assignment operator (and avoids issues with vs2017) *this = value; } /** * @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) : instance{}, vtable{&basic_vtable>}, mode{policy::OWNER} { initialize>(std::forward(value)); } /** * @brief Copy constructor. * @param other The instance to copy from. */ basic_any(const basic_any &other) : instance{}, vtable{&basic_vtable}, mode{policy::OWNER} { other.vtable(operation::COPY, other, this); } /** * @brief Move constructor. * @param other The instance to move from. */ basic_any(basic_any &&other) ENTT_NOEXCEPT : instance{}, vtable{other.vtable}, mode{other.mode} { vtable(operation::MOVE, other, this); } /*! @brief Frees the internal storage, whatever it means. */ ~basic_any() { vtable(operation::DTOR, *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(); 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 { std::exchange(vtable, other.vtable)(operation::DTOR, *this, nullptr); other.vtable(operation::MOVE, other, this); 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 [[deprecated("Use std::in_place_type, entt::make_any, emplace or forward_as_any instead")]] basic_any & operator=(std::reference_wrapper value) ENTT_NOEXCEPT { emplace(value.get()); 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 type of the contained object. * @return The type of the contained object, if any. */ [[nodiscard]] type_info type() const ENTT_NOEXCEPT { type_info info{}; vtable(operation::TYPE, *this, &info); 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(operation::CADDR, *this, nullptr); } /*! @copydoc data */ [[nodiscard]] void * data() ENTT_NOEXCEPT { return const_cast(vtable(operation::ADDR, *this, 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) { std::exchange(vtable, &basic_vtable>>)(operation::DTOR, *this, nullptr); mode = type_to_policy(); initialize(std::forward(args)...); } /*! @brief Destroys contained object */ void reset() { std::exchange(vtable, &basic_vtable)(operation::DTOR, *this, 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(operation::CADDR, *this, nullptr) == 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 { const basic_any *trampoline = &other; return type() == other.type() && (vtable(operation::COMP, *this, &trampoline) || !other.data()); } /** * @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; }; 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 { // 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(std::move(*instance)); } /*! @copydoc any_cast */ 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(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, Type> *>(data)->data()) : nullptr); } /** * @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)}; } } #endif