#ifndef ENTT_CORE_MEMORY_HPP #define ENTT_CORE_MEMORY_HPP #include #include #include #include #include #include #include "../config/config.h" namespace entt { /** * @brief Unwraps fancy pointers, does nothing otherwise (waiting for C++20). * @tparam Type Pointer type. * @param ptr Fancy or raw pointer. * @return A raw pointer that represents the address of the original pointer. */ template [[nodiscard]] constexpr auto to_address(Type &&ptr) ENTT_NOEXCEPT { if constexpr(std::is_pointer_v>>) { return ptr; } else { return to_address(std::forward(ptr).operator->()); } } /** * @brief Utility function to design allocation-aware containers. * @tparam Allocator Type of allocator. * @param lhs A valid allocator. * @param rhs Another valid allocator. */ template constexpr void propagate_on_container_copy_assignment([[maybe_unused]] Allocator &lhs, [[maybe_unused]] Allocator &rhs) ENTT_NOEXCEPT { if constexpr(std::allocator_traits::propagate_on_container_copy_assignment::value) { lhs = rhs; } } /** * @brief Utility function to design allocation-aware containers. * @tparam Allocator Type of allocator. * @param lhs A valid allocator. * @param rhs Another valid allocator. */ template constexpr void propagate_on_container_move_assignment([[maybe_unused]] Allocator &lhs, [[maybe_unused]] Allocator &rhs) ENTT_NOEXCEPT { if constexpr(std::allocator_traits::propagate_on_container_move_assignment::value) { lhs = std::move(rhs); } } /** * @brief Utility function to design allocation-aware containers. * @tparam Allocator Type of allocator. * @param lhs A valid allocator. * @param rhs Another valid allocator. */ template constexpr void propagate_on_container_swap([[maybe_unused]] Allocator &lhs, [[maybe_unused]] Allocator &rhs) ENTT_NOEXCEPT { ENTT_ASSERT(std::allocator_traits::propagate_on_container_swap::value || lhs == rhs, "Cannot swap the containers"); if constexpr(std::allocator_traits::propagate_on_container_swap::value) { using std::swap; swap(lhs, rhs); } } /** * @brief Checks whether a value is a power of two or not. * @param value A value that may or may not be a power of two. * @return True if the value is a power of two, false otherwise. */ [[nodiscard]] inline constexpr bool is_power_of_two(const std::size_t value) ENTT_NOEXCEPT { return value && ((value & (value - 1)) == 0); } /** * @brief Computes the smallest power of two greater than or equal to a value. * @param value The value to use. * @return The smallest power of two greater than or equal to the given value. */ [[nodiscard]] inline constexpr std::size_t next_power_of_two(const std::size_t value) ENTT_NOEXCEPT { ENTT_ASSERT(value < (std::size_t{1u} << (std::numeric_limits::digits - 1)), "Numeric limits exceeded"); std::size_t curr = value - (value != 0u); for(int next = 1; next < std::numeric_limits::digits; next = next * 2) { curr |= curr >> next; } return ++curr; } /** * @brief Fast module utility function (powers of two only). * @param value A value for which to calculate the modulus. * @param mod _Modulus_, it must be a power of two. * @return The common remainder. */ [[nodiscard]] inline constexpr std::size_t fast_mod(const std::size_t value, const std::size_t mod) ENTT_NOEXCEPT { ENTT_ASSERT(is_power_of_two(mod), "Value must be a power of two"); return value & (mod - 1u); } /** * @brief Deleter for allocator-aware unique pointers (waiting for C++20). * @tparam Args Types of arguments to use to construct the object. */ template struct allocation_deleter: private Allocator { /*! @brief Allocator type. */ using allocator_type = Allocator; /*! @brief Pointer type. */ using pointer = typename std::allocator_traits::pointer; /** * @brief Inherited constructors. * @param alloc The allocator to use. */ allocation_deleter(const allocator_type &alloc) : Allocator{alloc} {} /** * @brief Destroys the pointed object and deallocates its memory. * @param ptr A valid pointer to an object of the given type. */ void operator()(pointer ptr) { using alloc_traits = typename std::allocator_traits; alloc_traits::destroy(*this, to_address(ptr)); alloc_traits::deallocate(*this, ptr, 1u); } }; /** * @brief Allows `std::unique_ptr` to use allocators (waiting for C++20). * @tparam Type Type of object to allocate for and to construct. * @tparam Allocator Type of allocator used to manage memory and elements. * @tparam Args Types of arguments to use to construct the object. * @param allocator The allocator to use. * @param args Parameters to use to construct the object. * @return A properly initialized unique pointer with a custom deleter. */ template auto allocate_unique(Allocator &allocator, Args &&...args) { static_assert(!std::is_array_v, "Array types are not supported"); using alloc_traits = typename std::allocator_traits::template rebind_traits; using allocator_type = typename alloc_traits::allocator_type; allocator_type alloc{allocator}; auto ptr = alloc_traits::allocate(alloc, 1u); ENTT_TRY { alloc_traits::construct(alloc, to_address(ptr), std::forward(args)...); } ENTT_CATCH { alloc_traits::deallocate(alloc, ptr, 1u); ENTT_THROW; } return std::unique_ptr>{ptr, alloc}; } /** * @cond TURN_OFF_DOXYGEN * Internal details not to be documented. */ namespace internal { template struct uses_allocator_construction { template static constexpr auto args([[maybe_unused]] const Allocator &allocator, Params &&...params) ENTT_NOEXCEPT { if constexpr(!std::uses_allocator_v && std::is_constructible_v) { return std::forward_as_tuple(std::forward(params)...); } else { static_assert(std::uses_allocator_v, "Ill-formed request"); if constexpr(std::is_constructible_v) { return std::tuple(std::allocator_arg, allocator, std::forward(params)...); } else { static_assert(std::is_constructible_v, "Ill-formed request"); return std::forward_as_tuple(std::forward(params)..., allocator); } } } }; template struct uses_allocator_construction> { using type = std::pair; template static constexpr auto args(const Allocator &allocator, std::piecewise_construct_t, First &&first, Second &&second) ENTT_NOEXCEPT { return std::make_tuple( std::piecewise_construct, std::apply([&allocator](auto &&...curr) { return uses_allocator_construction::args(allocator, std::forward(curr)...); }, std::forward(first)), std::apply([&allocator](auto &&...curr) { return uses_allocator_construction::args(allocator, std::forward(curr)...); }, std::forward(second))); } template static constexpr auto args(const Allocator &allocator) ENTT_NOEXCEPT { return uses_allocator_construction::args(allocator, std::piecewise_construct, std::tuple<>{}, std::tuple<>{}); } template static constexpr auto args(const Allocator &allocator, First &&first, Second &&second) ENTT_NOEXCEPT { return uses_allocator_construction::args(allocator, std::piecewise_construct, std::forward_as_tuple(std::forward(first)), std::forward_as_tuple(std::forward(second))); } template static constexpr auto args(const Allocator &allocator, const std::pair &value) ENTT_NOEXCEPT { return uses_allocator_construction::args(allocator, std::piecewise_construct, std::forward_as_tuple(value.first), std::forward_as_tuple(value.second)); } template static constexpr auto args(const Allocator &allocator, std::pair &&value) ENTT_NOEXCEPT { return uses_allocator_construction::args(allocator, std::piecewise_construct, std::forward_as_tuple(std::move(value.first)), std::forward_as_tuple(std::move(value.second))); } }; } // namespace internal /** * Internal details not to be documented. * @endcond */ /** * @brief Uses-allocator construction utility (waiting for C++20). * * Primarily intended for internal use. Prepares the argument list needed to * create an object of a given type by means of uses-allocator construction. * * @tparam Type Type to return arguments for. * @tparam Allocator Type of allocator used to manage memory and elements. * @tparam Args Types of arguments to use to construct the object. * @param allocator The allocator to use. * @param args Parameters to use to construct the object. * @return The arguments needed to create an object of the given type. */ template constexpr auto uses_allocator_construction_args(const Allocator &allocator, Args &&...args) ENTT_NOEXCEPT { return internal::uses_allocator_construction::args(allocator, std::forward(args)...); } /** * @brief Uses-allocator construction utility (waiting for C++20). * * Primarily intended for internal use. Creates an object of a given type by * means of uses-allocator construction. * * @tparam Type Type of object to create. * @tparam Allocator Type of allocator used to manage memory and elements. * @tparam Args Types of arguments to use to construct the object. * @param allocator The allocator to use. * @param args Parameters to use to construct the object. * @return A newly created object of the given type. */ template constexpr Type make_obj_using_allocator(const Allocator &allocator, Args &&...args) { return std::make_from_tuple(internal::uses_allocator_construction::args(allocator, std::forward(args)...)); } /** * @brief Uses-allocator construction utility (waiting for C++20). * * Primarily intended for internal use. Creates an object of a given type by * means of uses-allocator construction at an uninitialized memory location. * * @tparam Type Type of object to create. * @tparam Allocator Type of allocator used to manage memory and elements. * @tparam Args Types of arguments to use to construct the object. * @param value Memory location in which to place the object. * @param allocator The allocator to use. * @param args Parameters to use to construct the object. * @return A pointer to the newly created object of the given type. */ template constexpr Type *uninitialized_construct_using_allocator(Type *value, const Allocator &allocator, Args &&...args) { return std::apply([&](auto &&...curr) { return new(value) Type(std::forward(curr)...); }, internal::uses_allocator_construction::args(allocator, std::forward(args)...)); } } // namespace entt #endif