Просмотр исходного кода

any: support for sbo storage alignment (close #676)

Michele Caini 5 лет назад
Родитель
Сommit
52bfddd2e7
4 измененных файлов с 51 добавлено и 29 удалено
  1. 21 15
      src/entt/core/any.hpp
  2. 2 2
      src/entt/core/fwd.hpp
  3. 10 3
      test/CMakeLists.txt
  4. 18 9
      test/entt/core/any.cpp

+ 21 - 15
src/entt/core/any.hpp

@@ -19,12 +19,16 @@ 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<std::size_t Len>
+template<std::size_t Len, std::size_t... Align>
 class basic_any {
+    static_assert(sizeof...(Align) == 0u || Len);
+
     enum class operation { COPY, MOVE, DTOR, COMP, ADDR, CADDR, REF, CREF, TYPE };
 
-    using storage_type = std::aligned_storage_t<Len == 0u ? 1u : Len>;
+    // cannot use std::aligned_storage_t with parameter packs because of an issue of msvc
+    using storage_type = typename std::aligned_storage<Len + !Len, Align...>::type;
     using vtable_type = const void *(const operation, const basic_any &, const void *);
 
     template<typename Type>
@@ -337,12 +341,13 @@ private:
 /**
  * @brief Checks if two wrappers differ in their content.
  * @tparam Len Size of the storage reserved for the small buffer optimization.
+ * @tparam Align Optional 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<std::size_t Len>
-[[nodiscard]] inline bool operator!=(const basic_any<Len> &lhs, const basic_any<Len> &rhs) ENTT_NOEXCEPT {
+template<std::size_t Len, std::size_t... Align>
+[[nodiscard]] inline bool operator!=(const basic_any<Len, Align...> &lhs, const basic_any<Len, Align...> &rhs) ENTT_NOEXCEPT {
     return !(lhs == rhs);
 }
 
@@ -351,11 +356,12 @@ template<std::size_t Len>
  * @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 Optional alignment requirement.
  * @param data Target any object.
  * @return The element converted to the requested type.
  */
-template<typename Type, std::size_t Len>
-Type any_cast(const basic_any<Len> &data) ENTT_NOEXCEPT {
+template<typename Type, std::size_t Len, std::size_t... Align>
+Type any_cast(const basic_any<Len, Align...> &data) ENTT_NOEXCEPT {
     const auto * const instance = any_cast<std::remove_reference_t<Type>>(&data);
     ENTT_ASSERT(instance);
     return static_cast<Type>(*instance);
@@ -363,8 +369,8 @@ Type any_cast(const basic_any<Len> &data) ENTT_NOEXCEPT {
 
 
 /*! @copydoc any_cast */
-template<typename Type, std::size_t Len>
-Type any_cast(basic_any<Len> &data) ENTT_NOEXCEPT {
+template<typename Type, std::size_t Len, std::size_t... Align>
+Type any_cast(basic_any<Len, Align...> &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::conditional_t<std::is_reference_v<Type>, std::remove_reference_t<Type>, const Type>>(&data);
     ENTT_ASSERT(instance);
@@ -373,8 +379,8 @@ Type any_cast(basic_any<Len> &data) ENTT_NOEXCEPT {
 
 
 /*! @copydoc any_cast */
-template<typename Type, std::size_t Len>
-Type any_cast(basic_any<Len> &&data) ENTT_NOEXCEPT {
+template<typename Type, std::size_t Len, std::size_t... Align>
+Type any_cast(basic_any<Len, Align...> &&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::conditional_t<std::is_reference_v<Type>, std::remove_reference_t<Type>, const Type>>(&data);
     ENTT_ASSERT(instance);
@@ -383,17 +389,17 @@ Type any_cast(basic_any<Len> &&data) ENTT_NOEXCEPT {
 
 
 /*! @copydoc any_cast */
-template<typename Type, std::size_t Len>
-const Type * any_cast(const basic_any<Len> *data) ENTT_NOEXCEPT {
+template<typename Type, std::size_t Len, std::size_t... Align>
+const Type * any_cast(const basic_any<Len, Align...> *data) ENTT_NOEXCEPT {
     return (data->type() == type_id<Type>() ? static_cast<const Type *>(data->data()) : nullptr);
 }
 
 
 /*! @copydoc any_cast */
-template<typename Type, std::size_t Len>
-Type * any_cast(basic_any<Len> *data) ENTT_NOEXCEPT {
+template<typename Type, std::size_t Len, std::size_t... Align>
+Type * any_cast(basic_any<Len, Align...> *data) ENTT_NOEXCEPT {
     // last attempt to make wrappers for const references return their values
-    return (data->type() == type_id<Type>() ? static_cast<Type *>(static_cast<constness_as_t<basic_any<Len>, Type> *>(data)->data()) : nullptr);
+    return (data->type() == type_id<Type>() ? static_cast<Type *>(static_cast<constness_as_t<basic_any<Len, Align...>, Type> *>(data)->data()) : nullptr);
 }
 
 

+ 2 - 2
src/entt/core/fwd.hpp

@@ -8,7 +8,7 @@
 namespace entt {
 
 
-template<std::size_t = sizeof(double[2])>
+template<std::size_t, std::size_t...>
 class basic_any;
 
 
@@ -17,7 +17,7 @@ using id_type = ENTT_ID_TYPE;
 
 
 /*! @brief Alias declaration for the most common use case. */
-using any = basic_any<>;
+using any = basic_any<sizeof(double[2])>;
 
 
 }

+ 10 - 3
test/CMakeLists.txt

@@ -58,7 +58,14 @@ function(SETUP_TARGET TARGET_NAME)
         )
     endif()
 
-    target_compile_definitions(${TARGET_NAME} PRIVATE ENTT_STANDALONE ${ARGN})
+    target_compile_definitions(
+        ${TARGET_NAME}
+        PRIVATE
+            _ENABLE_EXTENDED_ALIGNED_STORAGE
+            ENTT_STANDALONE
+            NOMINMAX
+            ${ARGN}
+    )
 endfunction()
 
 add_library(odr OBJECT odr.cpp)
@@ -80,8 +87,8 @@ endfunction()
 
 function(SETUP_PLUGIN_TEST TEST_NAME)
     add_library(_${TEST_NAME} MODULE $<TARGET_OBJECTS:odr> lib/${TEST_NAME}/plugin.cpp)
-    SETUP_TARGET(_${TEST_NAME} NOMINMAX ${ARGVN})
-    SETUP_BASIC_TEST(lib_${TEST_NAME} lib/${TEST_NAME}/main.cpp NOMINMAX PLUGIN="$<TARGET_FILE:_${TEST_NAME}>" ${ARGVN})
+    SETUP_TARGET(_${TEST_NAME} ${ARGVN})
+    SETUP_BASIC_TEST(lib_${TEST_NAME} lib/${TEST_NAME}/main.cpp PLUGIN="$<TARGET_FILE:_${TEST_NAME}>" ${ARGVN})
     target_include_directories(_${TEST_NAME} PRIVATE ${cr_INCLUDE_DIR})
     target_include_directories(lib_${TEST_NAME} PRIVATE ${cr_INCLUDE_DIR})
     target_link_libraries(lib_${TEST_NAME} PRIVATE ${CMAKE_DL_LIBS})

+ 18 - 9
test/entt/core/any.cpp

@@ -887,17 +887,26 @@ TEST(Any, SBOVsZeroedSBOSize) {
     ASSERT_EQ(valid, same.data());
 }
 
-TEST(Any, NoSBOAlignment) {
+TEST(Any, Alignment) {
     static constexpr auto alignment = alignof(over_aligned);
-    entt::basic_any<alignment> target[2] = { over_aligned{}, over_aligned{} };
-    const auto *data = target[0].data();
 
-    ASSERT_TRUE((reinterpret_cast<std::uintptr_t>(entt::any_cast<over_aligned>(&target[0u])) % alignment) == 0u);
-    ASSERT_TRUE((reinterpret_cast<std::uintptr_t>(entt::any_cast<over_aligned>(&target[1u])) % alignment) == 0u);
+    auto test = [](auto *target, auto cb) {
+        const auto *data = target[0].data();
 
-    std::swap(target[0], target[1]);
+        ASSERT_TRUE((reinterpret_cast<std::uintptr_t>(entt::any_cast<over_aligned>(&target[0u])) % alignment) == 0u);
+        ASSERT_TRUE((reinterpret_cast<std::uintptr_t>(entt::any_cast<over_aligned>(&target[1u])) % alignment) == 0u);
 
-    ASSERT_TRUE((reinterpret_cast<std::uintptr_t>(entt::any_cast<over_aligned>(&target[0u])) % alignment) == 0u);
-    ASSERT_TRUE((reinterpret_cast<std::uintptr_t>(entt::any_cast<over_aligned>(&target[1u])) % alignment) == 0u);
-    ASSERT_EQ(data, target[1].data());
+        std::swap(target[0], target[1]);
+
+        ASSERT_TRUE((reinterpret_cast<std::uintptr_t>(entt::any_cast<over_aligned>(&target[0u])) % alignment) == 0u);
+        ASSERT_TRUE((reinterpret_cast<std::uintptr_t>(entt::any_cast<over_aligned>(&target[1u])) % alignment) == 0u);
+
+        cb(data, target[1].data());
+    };
+
+    entt::basic_any<alignment> nosbo[2] = { over_aligned{}, over_aligned{} };
+    test(nosbo, [](auto *pre, auto *post) { ASSERT_EQ(pre, post); });
+
+    entt::basic_any<alignment, alignment> sbo[2] = { over_aligned{}, over_aligned{} };
+    test(sbo, [](auto *pre, auto *post) { ASSERT_NE(pre, post); });
 }