Date: 2025-03-28T21:10:42+00:00

Actual source code: utility.hpp

      1: #pragma once

      3: #include <petsc/private/cpp/macros.hpp>
      4: #include <petsc/private/cpp/type_traits.hpp>

      6: #include <utility>
      7: #include <cstdint> // std::uint32_t

      9: namespace Petsc
     10: {

     12: namespace util
     13: {

     15: #if PETSC_CPP_VERSION >= 14 // C++14
     16: using std::exchange;
     17: using std::integer_sequence;
     18: using std::make_integer_sequence;
     19: #else
     20: template <class T, class U = T>
     21: inline T exchange(T &orig, U &&new_value)
     22: {
     23:   T old_value = std::move(orig);
     24:   orig        = std::forward<U>(new_value);
     25:   return old_value;
     26: }

     28: template <class T, T... idx>
     29: struct integer_sequence {
     30:   static_assert(std::is_integral<T>::value, "");

     32:   using value_type = T;

     34:   static constexpr std::size_t size() noexcept { return sizeof...(idx); }
     35: };

     37:   #ifndef __has_builtin
     39:   #endif

     41:   #if __has_builtin(__make_integer_seq)    // clang, MSVC
     42: template <class T, T N>
     43: using make_integer_sequence = __make_integer_seq<integer_sequence, T, N>;
     44:   #elif defined(__GNUC__) && __GNUC__ >= 8 // gcc
     45: template <class T, T N>
     46: using make_integer_sequence = integer_sequence<T, __integer_pack(N)...>;
     47:   #else                                    // __slow__ version
     48: namespace detail
     49: {

     51: template <class T, int N, T... idx>
     52: struct make_sequence : make_sequence<T, N - 1, T(N - 1), idx...> { };

     54: template <class T, T... idx>
     55: struct make_sequence<T, 0, idx...> {
     56:   using type = integer_sequence<T, idx...>;
     57: };

     59: } // namespace detail

     61: template <class T, T N>
     62: using make_integer_sequence = typename detail::make_sequence<T, int(N)>::type;
     63:   #endif                                   // __has_builtin(__make_integer_seq)
     64: #endif                                     // C++14

     66: template <std::size_t... idx>
     67: using index_sequence = integer_sequence<std::size_t, idx...>;
     68: template <std::size_t N>
     69: using make_index_sequence = make_integer_sequence<std::size_t, N>;
     70: template <class... T>
     71: using index_sequence_for = make_index_sequence<sizeof...(T)>;

     73: // ==========================================================================================
     74: // compressed_pair
     75: //
     76: // Like std::pair except that it potentially stores both first and second as base classes if
     77: // either or both are "empty" classes. This allows empty-base-optimization to kick in. Normally
     78: // in C++ a structure must have a minimum memory footprint of 1 byte. For example
     79: //
     80: // struct Foo { }; // empty!
     81: //
     82: // struct Bar
     83: // {
     84: //   Foo f; // even though Foo is empty, member 'f' will always occupy 1 byte in memory
     85: // };
     86: //
     87: // This restriction does not hold for base classes however, so changing the above declarations
     88: // to
     89: //
     90: // struct Foo { }; // empty!
     91: //
     92: // struct Bar : Foo
     93: // {
     94: //
     95: // };
     96: //
     97: // Results in Bar now potentially occupying no space whatsoever.
     98: // ==========================================================================================

    100: namespace detail
    101: {

    103: template <bool t_empty, bool u_empty>
    104: struct compressed_pair_selector;

    106: template <>
    107: struct compressed_pair_selector<false, false> : std::integral_constant<int, 0> { };

    109: template <>
    110: struct compressed_pair_selector<true, false> : std::integral_constant<int, 1> { };

    112: template <>
    113: struct compressed_pair_selector<false, true> : std::integral_constant<int, 2> { };

    115: template <>
    116: struct compressed_pair_selector<true, true> : std::integral_constant<int, 3> { };

    118: template <typename T, typename U, int selector>
    119: class compressed_pair_impl;

    121: // selector = 0, neither are empty, derive directly from std::pair
    122: template <typename T, typename U>
    123: class compressed_pair_impl<T, U, 0> : std::pair<T, U> {
    124:   using base_type = std::pair<T, U>;

    126: public:
    127:   using base_type::base_type;
    128:   using typename base_type::first_type;
    129:   using typename base_type::second_type;

    131:   first_type       &first() noexcept { return static_cast<base_type &>(*this).first; }
    132:   const first_type &first() const noexcept { return static_cast<const base_type &>(*this).first; }

    134:   second_type       &second() noexcept { return static_cast<base_type &>(*this).second; }
    135:   const second_type &second() const noexcept { return static_cast<const base_type &>(*this).second; }
    136: };

    138: // selector = 1, T is empty
    139: template <typename T, typename U>
    140: class compressed_pair_impl<T, U, 1> : T {
    141:   using base_type = T;

    143: public:
    144:   using base_type::base_type;
    145:   using first_type  = T;
    146:   using second_type = U;

    148:   compressed_pair_impl() = default;

    150:   compressed_pair_impl(first_type x, second_type y) : base_type(std::move_if_noexcept(x)), second_(std::move_if_noexcept(y)) { }

    152:   compressed_pair_impl(second_type x) : second_(std::move_if_noexcept(x)) { }

    154:   first_type       &first() noexcept { return *this; }
    155:   const first_type &first() const noexcept { return *this; }

    157:   second_type       &second() noexcept { return second_; }
    158:   const second_type &second() const noexcept { return second_; }

    160: private:
    161:   second_type second_;
    162: };

    164: // selector = 2, U is empty
    165: template <typename T, typename U>
    166: class compressed_pair_impl<T, U, 2> : U {
    167:   using base_type = U;

    169: public:
    170:   using base_type::base_type;
    171:   using first_type  = T;
    172:   using second_type = U;

    174:   compressed_pair_impl() = default;

    176:   compressed_pair_impl(first_type x, second_type y) : base_type(std::move_if_noexcept(y)), first_(std::move_if_noexcept(x)) { }

    178:   compressed_pair_impl(first_type x) : first_(std::move_if_noexcept(x)) { }

    180:   first_type       &first() noexcept { return first_; }
    181:   const first_type &first() const noexcept { return first_; }

    183:   second_type       &second() noexcept { return *this; }
    184:   const second_type &second() const noexcept { return *this; }

    186: private:
    187:   first_type first_;
    188: };

    190: // selector = 3, T and U are both empty
    191: template <typename T, typename U>
    192: class compressed_pair_impl<T, U, 3> : T, U {
    193:   using first_base_type  = T;
    194:   using second_base_type = U;

    196: public:
    197:   using first_type  = T;
    198:   using second_type = U;

    200:   using first_type::first_type;
    201:   using second_type::second_type;

    203:   compressed_pair_impl() = default;

    205:   compressed_pair_impl(first_type x, second_type y) : first_type(std::move_if_noexcept(x)), second_type(std::move_if_noexcept(y)) { }

    207:   // Casts are needed to disambiguate case where T or U derive from one another, for example
    208:   //
    209:   // struct T { };
    210:   // struct U : T { };
    211:   //
    212:   // In this case both U and T are able to satisfy "conversion" to T
    213:   first_type       &first() noexcept { return static_cast<first_type &>(*this); }
    214:   const first_type &first() const noexcept { return static_cast<const first_type &>(*this); }

    216:   second_type       &second() noexcept { return static_cast<second_type &>(*this); }
    217:   const second_type &second() const noexcept { return static_cast<const second_type &>(*this); }
    218: };

    220: } // namespace detail

    222: // clang-format off
    223: template <typename T, typename U>
    224: class compressed_pair
    225:   : public detail::compressed_pair_impl<
    226:       T, U,
    227:       detail::compressed_pair_selector<std::is_empty<T>::value, std::is_empty<U>::value>::value
    228:     >
    229: // clang-format on
    230: {
    231:   using base_type = detail::compressed_pair_impl<T, U, detail::compressed_pair_selector<std::is_empty<T>::value, std::is_empty<U>::value>::value>;

    233: public:
    234:   using base_type::base_type;
    235: };

    237: // intel compilers don't implement empty base optimization, so these tests fail

    240: namespace compressed_pair_test
    241: {

    243: struct Empty { };

    245: static_assert(std::is_empty<Empty>::value, "");
    246: static_assert(sizeof(Empty) == 1, "");

    248: struct Empty2 { };

    250: static_assert(std::is_empty<Empty2>::value, "");
    251: static_assert(sizeof(Empty2) == 1, "");

    253: struct NotEmpty {
    254:   std::uint32_t d{};
    255: };

    257: static_assert(!std::is_empty<NotEmpty>::value, "");
    258: static_assert(sizeof(NotEmpty) > 1, "");

    260: struct EmptyMember {
    261:   Empty  m{};
    262:   Empty2 m2{};
    263: };

    265: static_assert(!std::is_empty<EmptyMember>::value, "");
    266: static_assert(sizeof(EmptyMember) > 1, "");

    268: // empty-empty should only be 1 byte since both are compressed out
    269: static_assert(std::is_empty<compressed_pair<Empty, Empty2>>::value, "");
    270: static_assert(sizeof(compressed_pair<Empty, Empty2>) == 1, "");

    272: // flipping template param order changes nothing
    273: static_assert(std::is_empty<compressed_pair<Empty2, Empty>>::value, "");
    274: static_assert(sizeof(compressed_pair<Empty2, Empty>) == 1, "");

    276: // empty-not_empty should be less than sum of sizes, since empty is compressed out
    277: static_assert(!std::is_empty<compressed_pair<Empty, NotEmpty>>::value, "");
    278: static_assert(sizeof(compressed_pair<Empty, NotEmpty>) < (sizeof(Empty) + sizeof(NotEmpty)), "");

    280: // flipping template param order changes nothing
    281: static_assert(!std::is_empty<compressed_pair<NotEmpty, Empty>>::value, "");
    282: static_assert(sizeof(compressed_pair<NotEmpty, Empty>) < (sizeof(NotEmpty) + sizeof(Empty)), "");

    284: // empty_member-not_empty should also be greater than or equal to sum of sizes (g.t. because
    285: // potential padding) because neither is compressed away
    286: static_assert(!std::is_empty<compressed_pair<EmptyMember, NotEmpty>>::value, "");
    287: static_assert(sizeof(compressed_pair<EmptyMember, NotEmpty>) >= (sizeof(EmptyMember) + sizeof(NotEmpty)), "");

    289: // flipping template param order changes nothing
    290: static_assert(!std::is_empty<compressed_pair<NotEmpty, EmptyMember>>::value, "");
    291: static_assert(sizeof(compressed_pair<NotEmpty, EmptyMember>) >= (sizeof(NotEmpty) + sizeof(EmptyMember)), "");

    293: } // namespace compressed_pair_test

    295: #endif

    297: } // namespace util

    299: } // namespace Petsc