Quellcode durchsuchen

core:
* added trait is_ebco_eligible[_v]
* added class compressed_pair
* tests and doc for compressed_pair

Michele Caini vor 4 Jahren
Ursprung
Commit
8978a0d159

+ 23 - 0
docs/md/core.md

@@ -27,6 +27,7 @@
     * [Integral constant](#integral-constant)
     * [Tag](#tag)
     * [Type list and value list](#type-list-and-value-list)
+* [Compressed pair](#compressed-pair)
 * [Utilities](#utilities)
 <!--
 @endcond TURN_OFF_DOXYGEN
@@ -644,6 +645,28 @@ Many of these functionalities also exist in their version dedicated to value
 lists. We therefore have `value_list_element[_v]` as well as
 `value_list_cat[_t]`and so on.
 
+# Compressed pair
+
+Primarily designed for internal use and far from being feature complete, the
+`compressed_pair` class does exactly what it promises: it tries to reduce the
+size of a pair by exploiting _Empty Base Class Optimization_ (or _EBCO_).<br/>
+This class **is not** a drop-in replacement for `std::pair`. However, it offers
+enough functionalities to be a good alternative for when reducing memory usage
+is more important than having some cool and probably useless feature.
+
+Although the API is very close to that of `std::pair` (apart from the fact that
+the template parameters are inferred from the constructor and therefore there is
+no` entt::make_compressed_pair`), the major difference is that `first` and
+`second` are functions for implementation needs:
+
+```cpp
+entt::compressed_pair pair{0, 3.};
+pair.first() = 42;
+```
+
+There isn't much to describe then. It's recommended to rely on documentation and
+intuition. At the end of the day, it's just a pair and nothing more.
+
 # Utilities
 
 It's not possible to escape the temptation to add utilities of some kind to a

+ 240 - 0
src/entt/core/compressed_pair.hpp

@@ -0,0 +1,240 @@
+#ifndef ENTT_CORE_COMPRESSED_PAIR_HPP
+#define ENTT_CORE_COMPRESSED_PAIR_HPP
+
+
+#include <tuple>
+#include <type_traits>
+#include <utility>
+#include "../config/config.h"
+#include "type_traits.hpp"
+
+
+namespace entt {
+
+
+/**
+ * @cond TURN_OFF_DOXYGEN
+ * Internal details not to be documented.
+ */
+
+
+namespace internal {
+
+
+template<typename Type, std::size_t, typename = void>
+struct compressed_pair_element {
+    template<bool Dummy = true, typename = std::enable_if_t<Dummy && std::is_default_constructible_v<Type>>>
+    compressed_pair_element()
+        : value{}
+    {}
+
+    template<typename Args, typename = std::enable_if_t<!std::is_same_v<std::decay_t<Args>, compressed_pair_element>>>
+    compressed_pair_element(Args &&args)
+        : value{std::forward<Args>(args)}
+    {}
+
+    template<typename... Args, std::size_t... Index>
+    compressed_pair_element(std::tuple<Args...> args, std::index_sequence<Index...>)
+        : value{std::get<Index>(args)...}
+    {}
+
+    [[nodiscard]] Type & get() ENTT_NOEXCEPT {
+        return value;
+    }
+
+    [[nodiscard]] const Type & get() const ENTT_NOEXCEPT {
+        return value;
+    }
+
+private:
+    Type value;
+};
+
+
+template<typename Type, std::size_t Tag>
+struct compressed_pair_element<Type, Tag, std::enable_if_t<is_ebco_eligible_v<Type>>>
+    : Type
+{
+    template<bool Dummy = true, typename = std::enable_if_t<Dummy && std::is_default_constructible_v<Type>>>
+    compressed_pair_element()
+        : Type{}
+    {}
+
+    template<typename Args, typename = std::enable_if_t<!std::is_same_v<std::decay_t<Args>, compressed_pair_element>>>
+    compressed_pair_element(Args &&args)
+        : Type{std::forward<Args>(args)}
+    {}
+
+    template<typename... Args, std::size_t... Index>
+    compressed_pair_element(std::tuple<Args...> args, std::index_sequence<Index...>)
+        : Type{std::get<Index>(args)...}
+    {}
+
+    [[nodiscard]] Type & get() ENTT_NOEXCEPT {
+        return *this;
+    }
+
+    [[nodiscard]] const Type & get() const ENTT_NOEXCEPT {
+        return *this;
+    }
+};
+
+
+}
+
+
+/**
+ * Internal details not to be documented.
+ * @endcond
+ */
+
+
+/**
+ * @brief A compressed pair.
+ *
+ * A pair that exploits the _Empty Base Class Optimization_ (or _EBCO_) to
+ * reduce its final size to a minimum.
+ *
+ * @tparam First The type of the first element that the pair stores.
+ * @tparam Second The type of the second element that the pair stores.
+ */
+template<typename First, typename Second>
+class compressed_pair final:
+    internal::compressed_pair_element<First, 0u>,
+    internal::compressed_pair_element<Second, 1u>
+{
+    using first_base = internal::compressed_pair_element<First, 0u>;
+    using second_base = internal::compressed_pair_element<Second, 1u>;
+
+public:
+    /**
+     * @brief Default constructor, conditionally enabled.
+     *
+     * This constructor is only available when the types that the pair stores
+     * are both at least default constructible.
+     *
+     * @tparam Dummy Dummy template parameter used for internal purposes.
+     */
+    template<bool Dummy = true, typename = std::enable_if_t<Dummy && std::is_default_constructible_v<First> && std::is_default_constructible_v<Second>>>
+    constexpr compressed_pair()
+        : first_base{},
+          second_base{}
+    {}
+
+    /**
+     * @brief Copy constructor.
+     * @param other The instance to copy from.
+     */
+    constexpr compressed_pair(const compressed_pair &other) = default;
+
+    /**
+     * @brief Move constructor.
+     * @param other The instance to move from.
+     */
+    constexpr compressed_pair(compressed_pair &&other) ENTT_NOEXCEPT = default;
+
+    /**
+     * @brief Constructs a pair from its values.
+     * @tparam Arg Type of value to use to initialize the first element.
+     * @tparam Other Type of value to use to initialize the second element.
+     * @param arg Value to use to initialize the first element.
+     * @param other Value to use to initialize the second element.
+     */
+    template<typename Arg, typename Other>
+    constexpr compressed_pair(Arg &&arg, Other &&other)
+        : first_base{std::forward<Arg>(arg)},
+          second_base{std::forward<Other>(other)}
+    {}
+
+    /**
+     * @brief Constructs a pair by forwarding the arguments to its parts.
+     * @tparam Args Types of arguments to use to initialize the first element.
+     * @tparam Other Types of arguments to use to initialize the second element.
+     * @param args Arguments to use to initialize the first element.
+     * @param other Arguments to use to initialize the second element.
+     */
+    template<typename... Args, typename... Other>
+    constexpr compressed_pair(std::piecewise_construct_t, std::tuple<Args...> args, std::tuple<Other...> other)
+        : first_base{std::move(args), std::index_sequence_for<Args...>{}},
+          second_base{std::move(other), std::index_sequence_for<Other...>{}}
+    {}
+
+    /**
+     * @brief Copy assignment operator.
+     * @param other The instance to copy from.
+     * @return This compressed pair object.
+     */
+    constexpr compressed_pair & operator=(const compressed_pair &other) = default;
+
+    /**
+     * @brief Move assignment operator.
+     * @param other The instance to move from.
+     * @return This compressed pair object.
+     */
+    constexpr compressed_pair & operator=(compressed_pair &&other) ENTT_NOEXCEPT = default;
+
+    /**
+     * @brief Returns the first element that a pair stores.
+     * @return The first element that a pair stores.
+     */
+    [[nodiscard]] First & first() ENTT_NOEXCEPT {
+        return static_cast<first_base &>(*this).get();
+    }
+
+    /*! @copydoc first */
+    [[nodiscard]] const First & first() const ENTT_NOEXCEPT {
+        return static_cast<const first_base &>(*this).get();
+    }
+
+    /**
+     * @brief Returns the second element that a pair stores.
+     * @return The second element that a pair stores.
+     */
+    [[nodiscard]] Second & second() ENTT_NOEXCEPT {
+        return static_cast<second_base &>(*this).get();
+    }
+
+    /*! @copydoc second */
+    [[nodiscard]] const Second & second() const ENTT_NOEXCEPT {
+        return static_cast<const second_base &>(*this).get();
+    }
+
+    /**
+     * @brief Swaps two compressed pair objects.
+     * @param other The compressed pair to swap with.
+     */
+    void swap(compressed_pair &other) {
+        using std::swap;
+        swap(first(), other.first());
+        swap(second(), other.second());
+    }
+};
+
+
+/**
+ * @brief Deduction guide.
+ * @tparam Type Type of value to use to initialize the first element.
+ * @tparam Other Type of value to use to initialize the second element.
+ */
+template<typename Type, typename Other>
+compressed_pair(Type &&, Other &&)
+-> compressed_pair<std::decay_t<Type>, std::decay_t<Other>>;
+
+
+/**
+ * @brief Swaps two compressed pair objects.
+ * @tparam First The type of the first element that the pairs store.
+ * @tparam Second The type of the second element that the pairs store.
+ * @param lhs A valid compressed pair object.
+ * @param rhs A valid compressed pair object.
+ */
+template<typename First, typename Second>
+inline void swap(compressed_pair<First, Second> &lhs, compressed_pair<First, Second> &rhs) {
+    lhs.swap(rhs);
+}
+
+
+}
+
+
+#endif

+ 19 - 0
src/entt/core/type_traits.hpp

@@ -562,6 +562,25 @@ template<typename Type, typename It>
 inline constexpr bool is_iterator_type_v = is_iterator_type<Type, It>::value;
 
 
+/**
+ * @brief Provides the member constant `value` to true if a given type is both
+ * an empty and non-final class, false otherwise.
+ * @tparam Type The type to test
+ */
+template<typename Type>
+struct is_ebco_eligible
+    : std::conjunction<std::is_empty<Type>, std::negation<std::is_final<Type>>>
+{};
+
+
+/**
+ * @brief Helper variable template.
+ * @tparam Type The type to test.
+ */
+template<typename Type>
+inline constexpr bool is_ebco_eligible_v = is_ebco_eligible<Type>::value;
+
+
 /**
  * @cond TURN_OFF_DOXYGEN
  * Internal details not to be documented.

+ 1 - 0
src/entt/entt.hpp

@@ -2,6 +2,7 @@
 #include "core/algorithm.hpp"
 #include "core/any.hpp"
 #include "core/attribute.h"
+#include "core/compressed_pair.hpp"
 #include "core/family.hpp"
 #include "core/hashed_string.hpp"
 #include "core/ident.hpp"

+ 1 - 0
test/CMakeLists.txt

@@ -169,6 +169,7 @@ endif()
 
 SETUP_BASIC_TEST(algorithm entt/core/algorithm.cpp)
 SETUP_BASIC_TEST(any entt/core/any.cpp)
+SETUP_BASIC_TEST(compressed_pair entt/core/compressed_pair.cpp)
 SETUP_BASIC_TEST(family entt/core/family.cpp)
 SETUP_BASIC_TEST(hashed_string entt/core/hashed_string.cpp)
 SETUP_BASIC_TEST(ident entt/core/ident.cpp)

+ 132 - 0
test/entt/core/compressed_pair.cpp

@@ -0,0 +1,132 @@
+#include <tuple>
+#include <type_traits>
+#include <utility>
+#include <gtest/gtest.h>
+#include <entt/core/compressed_pair.hpp>
+
+struct empty_type {};
+
+struct move_only_type {
+    move_only_type(): value{new int{99}} {}
+    move_only_type(int v): value{new int{v}} {}
+
+    ~move_only_type() { delete value; }
+
+    move_only_type(const move_only_type &) = delete;
+    move_only_type & operator=(const move_only_type &) = delete;
+
+    move_only_type(move_only_type &&other) ENTT_NOEXCEPT
+        : value{std::exchange(other.value, nullptr)}
+    {}
+
+    move_only_type & operator=(move_only_type &&other) ENTT_NOEXCEPT {
+        delete value;
+        value = std::exchange(other.value, nullptr);
+        return *this;
+    }
+
+    int *value;
+};
+
+struct non_default_constructible {
+    non_default_constructible(int v): value{v} {}
+    int value;
+};
+
+TEST(CompressedPair, Size) {
+    struct local {
+        int value;
+        empty_type empty;
+    };
+
+    static_assert(sizeof(entt::compressed_pair<int, int>) == sizeof(int[2u]));
+    static_assert(sizeof(entt::compressed_pair<empty_type, int>) == sizeof(int));
+    static_assert(sizeof(entt::compressed_pair<int, empty_type>) == sizeof(int));
+    static_assert(sizeof(entt::compressed_pair<int, empty_type>) != sizeof(local));
+}
+
+TEST(CompressedPair, ConstructCopyMove) {
+    static_assert(!std::is_default_constructible_v<entt::compressed_pair<non_default_constructible, empty_type>>);
+    static_assert(std::is_default_constructible_v<entt::compressed_pair<move_only_type, empty_type>>);
+
+    static_assert(std::is_copy_constructible_v<entt::compressed_pair<non_default_constructible, empty_type>>);
+    static_assert(!std::is_copy_constructible_v<entt::compressed_pair<move_only_type, empty_type>>);
+    static_assert(std::is_copy_assignable_v<entt::compressed_pair<non_default_constructible, empty_type>>);
+    static_assert(!std::is_copy_assignable_v<entt::compressed_pair<move_only_type, empty_type>>);
+
+    static_assert(std::is_move_constructible_v<entt::compressed_pair<move_only_type, empty_type>>);
+    static_assert(std::is_move_assignable_v<entt::compressed_pair<move_only_type, empty_type>>);
+
+    entt::compressed_pair copyable{non_default_constructible{42}, empty_type{}};
+    auto by_copy{copyable};
+
+    ASSERT_EQ(by_copy.first().value, 42);
+
+    by_copy.first().value = 3;
+    copyable = by_copy;
+
+    ASSERT_EQ(copyable.first().value, 3);
+
+    entt::compressed_pair<empty_type, move_only_type> movable{};
+    auto by_move{std::move(movable)};
+
+    ASSERT_EQ(*by_move.second().value, 99);
+    ASSERT_EQ(movable.second().value, nullptr);
+
+    *by_move.second().value = 3;
+    movable = std::move(by_move);
+
+    ASSERT_EQ(*movable.second().value, 3);
+    ASSERT_EQ(by_move.second().value, nullptr);
+}
+
+TEST(CompressedPair, PiecewiseConstruct) {
+    entt::compressed_pair<empty_type, int> pair{std::piecewise_construct, std::make_tuple(), std::make_tuple(3)};
+
+    ASSERT_EQ(pair.second(), 3);
+}
+
+TEST(CompressedPair, DeductionGuide) {
+    int value = 42;
+    empty_type empty{};
+    entt::compressed_pair pair{value, 3};
+
+    static_assert(std::is_same_v<decltype(entt::compressed_pair{empty_type{}, empty}), entt::compressed_pair<empty_type, empty_type>>);
+
+    ASSERT_TRUE((std::is_same_v<decltype(pair), entt::compressed_pair<int, int>>));
+    ASSERT_EQ(pair.first(), 42);
+    ASSERT_EQ(pair.second(), 3);
+}
+
+TEST(CompressedPair, Getters) {
+    entt::compressed_pair pair{3, empty_type{}};
+    const auto &cpair = pair;
+
+    static_assert(std::is_same_v<decltype(pair.first()), int &>);
+    static_assert(std::is_same_v<decltype(pair.second()), empty_type &>);
+
+    static_assert(std::is_same_v<decltype(cpair.first()), const int &>);
+    static_assert(std::is_same_v<decltype(cpair.second()), const empty_type &>);
+
+    ASSERT_EQ(pair.first(), cpair.first());
+    ASSERT_EQ(&pair.second(), &cpair.second());
+}
+
+TEST(CompressedPair, Swap) {
+    entt::compressed_pair pair{1, 2};
+    entt::compressed_pair other{3, 4};
+
+    swap(pair, other);
+
+    ASSERT_EQ(pair.first(), 3);
+    ASSERT_EQ(pair.second(), 4);
+    ASSERT_EQ(other.first(), 1);
+    ASSERT_EQ(other.second(), 2);
+
+    pair.swap(other);
+
+    ASSERT_EQ(pair.first(), 1);
+    ASSERT_EQ(pair.second(), 2);
+    ASSERT_EQ(other.first(), 3);
+    ASSERT_EQ(other.second(), 4);
+}