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

This commit is contained in:
Michele Caini
2021-03-22 10:51:28 +01:00
parent f59adcdc37
commit 52bfddd2e7
4 changed files with 51 additions and 29 deletions

View File

@@ -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);
}

View File

@@ -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])>;
}

View File

@@ -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})

View File

@@ -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); });
}