#ifndef ENTT_CORE_ANY_HPP #define ENTT_CORE_ANY_HPP #include "../config/config.h" #include "../core/concepts.hpp" #include "../stl/concepts.hpp" #include "../stl/cstddef.hpp" #include "../stl/cstdint.hpp" #include "../stl/memory.hpp" #include "../stl/type_traits.hpp" #include "../stl/utility.hpp" #include "fwd.hpp" #include "type_info.hpp" #include "type_traits.hpp" #include "utility.hpp" namespace entt { /*! @cond ENTT_INTERNAL */ namespace internal { enum class any_request : stl::uint8_t { info, transfer, assign, compare, copy, move }; template struct basic_any_storage { static constexpr bool has_buffer = true; union { const void *instance{}; // NOLINTNEXTLINE(cppcoreguidelines-avoid-c-arrays, modernize-avoid-c-arrays) alignas(Align) stl::byte buffer[Len]; }; }; template struct basic_any_storage<0u, Align> { static constexpr bool has_buffer = false; const void *instance{}; }; template // NOLINTNEXTLINE(bugprone-sizeof-expression) struct in_situ: stl::bool_constant<(Len != 0u) && alignof(Type) <= Align && sizeof(Type) <= Len && stl::is_nothrow_move_constructible_v> {}; template struct in_situ: stl::false_type {}; } // namespace internal /*! @endcond */ /** * @brief A SBO friendly, type-safe container for single values of any type. * @tparam Len Size of the buffer reserved for the small buffer optimization. * @tparam Align Optional alignment requirement. */ template class basic_any: private internal::basic_any_storage { using request = internal::any_request; using base_type = internal::basic_any_storage; using vtable_type = const void *(const request, const basic_any &, const void *); using deleter_type = void(const basic_any &); template static constexpr bool in_situ_v = internal::in_situ::value; template static const void *basic_vtable(const request req, const basic_any &value, const void *other) { switch(const auto *elem = static_cast(value.data()); req) { using enum internal::any_request; case info: return &type_id(); case transfer: if constexpr(stl::is_move_assignable_v) { // NOLINTNEXTLINE(bugprone-casting-through-void) *const_cast(elem) = stl::move(*static_cast(const_cast(other))); return other; } [[fallthrough]]; case assign: if constexpr(stl::is_copy_assignable_v) { *const_cast(elem) = *static_cast(other); return other; } break; case compare: if constexpr(!stl::is_function_v && !stl::is_array_v && is_equality_comparable_v) { return (*elem == *static_cast(other)) ? other : nullptr; } else { return (elem == other) ? other : nullptr; } case copy: if constexpr(stl::is_copy_constructible_v) { // NOLINTNEXTLINE(bugprone-casting-through-void) static_cast(const_cast(other))->initialize(*elem); } break; case move: ENTT_ASSERT(value.mode == any_policy::embedded, "Unexpected policy"); if constexpr(in_situ_v) { // NOLINTNEXTLINE(bugprone-casting-through-void, bugprone-multi-level-implicit-pointer-conversion) return ::new(&static_cast(const_cast(other))->buffer) Type{stl::move(*const_cast(elem))}; } } return nullptr; } template static void basic_deleter(const basic_any &value) { ENTT_ASSERT((value.mode == any_policy::dynamic) || ((value.mode == any_policy::embedded) && !stl::is_trivially_destructible_v), "Unexpected policy"); const auto *elem = static_cast(value.data()); if constexpr(in_situ_v) { (value.mode == any_policy::embedded) ? elem->~Type() : (delete elem); } else if constexpr(stl::is_array_v) { delete[] elem; } else { delete elem; } } template void initialize([[maybe_unused]] Args &&...args) { using plain_type = stl::remove_cvref_t; vtable = basic_vtable; underlying_type = type_hash::value(); if constexpr(stl::is_void_v) { deleter = nullptr; mode = any_policy::empty; this->instance = nullptr; } else if constexpr(stl::is_lvalue_reference_v) { deleter = nullptr; mode = stl::is_const_v> ? any_policy::cref : any_policy::ref; static_assert((stl::is_lvalue_reference_v && ...) && (sizeof...(Args) == 1u), "Invalid arguments"); // NOLINTNEXTLINE(bugprone-multi-level-implicit-pointer-conversion) this->instance = (stl::addressof(args), ...); } else if constexpr(in_situ_v) { if constexpr(stl::is_trivially_destructible_v) { deleter = nullptr; } else { deleter = &basic_deleter; } mode = any_policy::embedded; if constexpr(stl::is_aggregate_v && (sizeof...(Args) != 0u || !stl::is_default_constructible_v)) { ::new(&this->buffer) plain_type{stl::forward(args)...}; } else { // NOLINTNEXTLINE(cppcoreguidelines-pro-bounds-array-to-pointer-decay) ::new(&this->buffer) plain_type(stl::forward(args)...); } } else { deleter = &basic_deleter; mode = any_policy::dynamic; if constexpr(stl::is_aggregate_v && (sizeof...(Args) != 0u || !stl::is_default_constructible_v)) { this->instance = new plain_type{stl::forward(args)...}; } else if constexpr(stl::is_array_v) { static_assert(sizeof...(Args) == 0u, "Invalid arguments"); this->instance = new plain_type[stl::extent_v](); } else { this->instance = new plain_type(stl::forward(args)...); } } } void invoke_deleter_if_exists() { if(deleter != nullptr) { deleter(*this); } } public: /*! @brief Size of the internal buffer. */ static constexpr auto length = Len; /*! @brief Alignment requirement. */ static constexpr auto alignment = Align; /*! @brief Default constructor. */ constexpr basic_any() noexcept : basic_any{stl::in_place_type} {} /** * @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(stl::in_place_type_t, Args &&...args) : base_type{} { initialize(stl::forward(args)...); } /** * @brief Constructs a wrapper taking ownership of the passed object. * @tparam Type Type of object to use to initialize the wrapper. * @param value A pointer to an object to take ownership of. */ template requires (!stl::is_const_v && !stl::is_void_v) explicit basic_any(stl::in_place_t, Type *value) : base_type{} { if(value == nullptr) { initialize(); } else { initialize(*value); deleter = &basic_deleter; mode = any_policy::dynamic; } } /** * @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 requires (!stl::same_as, basic_any>) basic_any(Type &&value) : basic_any{stl::in_place_type>, stl::forward(value)} {} /** * @brief Copy constructor. * @param other The instance to copy from. */ basic_any(const basic_any &other) : basic_any{} { other.vtable(request::copy, other, this); } /** * @brief Move constructor. * @param other The instance to move from. */ basic_any(basic_any &&other) noexcept : base_type{}, vtable{other.vtable}, deleter{other.deleter}, underlying_type{other.underlying_type}, mode{other.mode} { if(other.mode == any_policy::embedded) { other.vtable(request::move, other, this); } else if(other.mode != any_policy::empty) { this->instance = stl::exchange(other.instance, nullptr); } } /*! @brief Frees the internal buffer, whatever it means. */ ~basic_any() { invoke_deleter_if_exists(); } /** * @brief Copy assignment operator. * @param other The instance to copy from. * @return This any object. */ basic_any &operator=(const basic_any &other) { if(this != &other) { invoke_deleter_if_exists(); if(other) { other.vtable(request::copy, other, this); } else { initialize(); } } return *this; } /** * @brief Move assignment operator. * @param other The instance to move from. * @return This any object. */ basic_any &operator=(basic_any &&other) noexcept { if(this != &other) { invoke_deleter_if_exists(); if(other.mode == any_policy::embedded) { other.vtable(request::move, other, this); } else if(other.mode != any_policy::empty) { this->instance = stl::exchange(other.instance, nullptr); } vtable = other.vtable; deleter = other.deleter; underlying_type = other.underlying_type; 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 requires (!stl::same_as, basic_any>) basic_any &operator=(Type &&value) { emplace>(stl::forward(value)); return *this; } /** * @brief Returns false if a wrapper is empty, true otherwise. * @return False if the wrapper is empty, true otherwise. */ [[nodiscard]] bool has_value() const noexcept { return (mode != any_policy::empty); } /** * @brief Returns false if the wrapper does not contain the expected type, * true otherwise. * @param req Expected type. * @return False if the wrapper does not contain the expected type, true * otherwise. */ [[nodiscard]] bool has_value(const type_info &req) const noexcept { return (underlying_type == req.hash()); } /** * @brief Returns false if the wrapper does not contain the expected type, * true otherwise. * @tparam Type Expected type. * @return False if the wrapper does not contain the expected type, true * otherwise. */ template [[nodiscard]] bool has_value() const noexcept { return (underlying_type == type_hash::value()); } /** * @brief Returns the object type info if any, `type_id()` otherwise. * @return The object type info if any, `type_id()` otherwise. */ [[nodiscard]] const type_info &info() const noexcept { return *static_cast(vtable(request::info, *this, nullptr)); } /** * @brief Returns an opaque pointer to the contained instance. * @return An opaque pointer the contained instance, if any. */ [[nodiscard]] const void *data() const noexcept { if constexpr(base_type::has_buffer) { return (mode == any_policy::embedded) ? &this->buffer : this->instance; } else { return this->instance; } } /** * @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 noexcept { return has_value(req) ? data() : nullptr; } /** * @brief Returns an opaque pointer to the contained instance. * @tparam Type Expected type. * @return An opaque pointer the contained instance, if any. */ template [[nodiscard]] const Type *data() const noexcept { return has_value>() ? static_cast(data()) : nullptr; } /** * @brief Returns an opaque pointer to the contained instance. * @return An opaque pointer the contained instance, if any. */ [[nodiscard]] void *data() noexcept { return (mode == any_policy::cref) ? nullptr : const_cast(stl::as_const(*this).data()); } /** * @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) noexcept { return (mode == any_policy::cref) ? nullptr : const_cast(stl::as_const(*this).data(req)); } /** * @brief Returns an opaque pointer to the contained instance. * @tparam Type Expected type. * @return An opaque pointer the contained instance, if any. */ template [[nodiscard]] Type *data() noexcept { if constexpr(stl::is_const_v) { return stl::as_const(*this).template data>(); } else { return (mode == any_policy::cref) ? nullptr : const_cast(stl::as_const(*this).template data>()); } } /** * @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) { invoke_deleter_if_exists(); initialize(stl::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 basic_any &other) { if(other && (mode != any_policy::cref) && (underlying_type == other.underlying_type)) { return (vtable(request::assign, *this, other.data()) != nullptr); } return false; } /*! @copydoc assign */ // NOLINTNEXTLINE(cppcoreguidelines-rvalue-reference-param-not-moved) bool assign(basic_any &&other) { if(other && (mode != any_policy::cref) && (underlying_type == other.underlying_type)) { return (other.mode == any_policy::cref) ? (vtable(request::assign, *this, stl::as_const(other).data()) != nullptr) : (vtable(request::transfer, *this, other.data()) != nullptr); } return false; } /*! @brief Destroys contained object */ void reset() { invoke_deleter_if_exists(); initialize(); } /** * @brief Returns false if a wrapper is empty, true otherwise. * @return False if the wrapper is empty, true otherwise. */ [[nodiscard]] explicit operator bool() const noexcept { return has_value(); } /** * @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. */ [[nodiscard]] bool operator==(const basic_any &other) const noexcept { if(other && (underlying_type == other.underlying_type)) { return (vtable(request::compare, *this, other.data()) != nullptr); } return (!*this && !other); } /** * @brief Aliasing constructor. * @return A wrapper that shares a reference to an unmanaged object. */ [[nodiscard]] basic_any as_ref() noexcept { basic_any other = stl::as_const(*this).as_ref(); switch(mode) { using enum any_policy; case cref: case empty: other.mode = mode; break; default: other.mode = any_policy::ref; break; } return other; } /*! @copydoc as_ref */ [[nodiscard]] basic_any as_ref() const noexcept { basic_any other{}; other.instance = data(); other.vtable = vtable; other.underlying_type = underlying_type; other.mode = any_policy::cref; return other; } /** * @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 noexcept { return (mode == any_policy::dynamic || mode == any_policy::embedded); } /** * @brief Returns the current mode of an any object. * @return The current mode of the any object. */ [[nodiscard]] any_policy policy() const noexcept { return mode; } private: vtable_type *vtable{}; deleter_type *deleter{}; id_type underlying_type{}; any_policy mode{}; }; /** * @brief Performs type-safe access to the contained object. * @tparam Type Type to which conversion is required. * @tparam Len Size of the buffer reserved for the small buffer optimization. * @tparam Align Alignment requirement. * @param data Target any object. * @return The element converted to the requested type. */ template [[nodiscard]] stl::remove_const_t any_cast(const basic_any &data) noexcept { const auto *const instance = any_cast>(&data); ENTT_ASSERT(instance, "Invalid instance"); return static_cast(*instance); } /*! @copydoc any_cast */ template [[nodiscard]] stl::remove_const_t any_cast(basic_any &data) 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 // NOLINTNEXTLINE(cppcoreguidelines-rvalue-reference-param-not-moved) [[nodiscard]] stl::remove_const_t any_cast(basic_any &&data) noexcept { if constexpr(stl::is_copy_constructible_v>) { if(auto *const instance = any_cast>(&data); instance) { return static_cast(stl::move(*instance)); } return any_cast(data); } else { auto *const instance = any_cast>(&data); ENTT_ASSERT(instance, "Invalid instance"); return static_cast(stl::move(*instance)); } } /*! @copydoc any_cast */ template [[nodiscard]] const Type *any_cast(const basic_any *data) noexcept { return data->template data>(); } /*! @copydoc any_cast */ template [[nodiscard]] Type *any_cast(basic_any *data) noexcept { if constexpr(stl::is_const_v) { // last attempt to make wrappers for const references return their values return any_cast(&stl::as_const(*data)); } else { return data->template data(); } } /** * @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 buffer 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, stl::size_t Align = basic_any::alignment, typename... Args> [[nodiscard]] basic_any make_any(Args &&...args) { return basic_any{stl::in_place_type, stl::forward(args)...}; } /** * @brief Forwards its argument and avoids copies for lvalue references. * @tparam Len Size of the buffer 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, stl::size_t Align = basic_any::alignment, typename Type> [[nodiscard]] basic_any forward_as_any(Type &&value) { return basic_any{stl::in_place_type, stl::forward(value)}; } } // namespace entt #endif