xref: /petsc/include/petsc/private/cpp/utility.hpp (revision 9d47de495d3c23378050c1b4a410c12a375cb6c6)
1 #pragma once
2 
3 #include <petsc/private/cpp/macros.hpp>
4 #include <petsc/private/cpp/type_traits.hpp>
5 
6 #include <utility>
7 #include <cstdint> // std::uint32_t
8 
9 namespace Petsc
10 {
11 
12 namespace util
13 {
14 
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 }
27 
28 template <class T, T... idx>
29 struct integer_sequence {
30   static_assert(std::is_integral<T>::value, "");
31 
32   using value_type = T;
33 
34   static constexpr std::size_t size() noexcept { return sizeof...(idx); }
35 };
36 
37   #if !defined(__has_builtin)
38     #define __has_builtin(x) 0
39   #endif
40 
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 {
50 
51 template <class T, int N, T... idx>
52 struct make_sequence : make_sequence<T, N - 1, T(N - 1), idx...> { };
53 
54 template <class T, T... idx>
55 struct make_sequence<T, 0, idx...> {
56   using type = integer_sequence<T, idx...>;
57 };
58 
59 } // namespace detail
60 
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
65 
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)>;
72 
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 // ==========================================================================================
99 
100 namespace detail
101 {
102 
103 template <bool t_empty, bool u_empty>
104 struct compressed_pair_selector;
105 
106 template <>
107 struct compressed_pair_selector<false, false> : std::integral_constant<int, 0> { };
108 
109 template <>
110 struct compressed_pair_selector<true, false> : std::integral_constant<int, 1> { };
111 
112 template <>
113 struct compressed_pair_selector<false, true> : std::integral_constant<int, 2> { };
114 
115 template <>
116 struct compressed_pair_selector<true, true> : std::integral_constant<int, 3> { };
117 
118 template <typename T, typename U, int selector>
119 class compressed_pair_impl;
120 
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>;
125 
126 public:
127   using base_type::base_type;
128   using typename base_type::first_type;
129   using typename base_type::second_type;
130 
first()131   first_type       &first() noexcept { return static_cast<base_type &>(*this).first; }
first() const132   const first_type &first() const noexcept { return static_cast<const base_type &>(*this).first; }
133 
second()134   second_type       &second() noexcept { return static_cast<base_type &>(*this).second; }
second() const135   const second_type &second() const noexcept { return static_cast<const base_type &>(*this).second; }
136 };
137 
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;
142 
143 public:
144   using base_type::base_type;
145   using first_type  = T;
146   using second_type = U;
147 
148   compressed_pair_impl() = default;
149 
compressed_pair_impl(first_type x,second_type y)150   compressed_pair_impl(first_type x, second_type y) : base_type(std::move_if_noexcept(x)), second_(std::move_if_noexcept(y)) { }
151 
compressed_pair_impl(second_type x)152   compressed_pair_impl(second_type x) : second_(std::move_if_noexcept(x)) { }
153 
first()154   first_type       &first() noexcept { return *this; }
first() const155   const first_type &first() const noexcept { return *this; }
156 
second()157   second_type       &second() noexcept { return second_; }
second() const158   const second_type &second() const noexcept { return second_; }
159 
160 private:
161   second_type second_;
162 };
163 
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;
168 
169 public:
170   using base_type::base_type;
171   using first_type  = T;
172   using second_type = U;
173 
174   compressed_pair_impl() = default;
175 
compressed_pair_impl(first_type x,second_type y)176   compressed_pair_impl(first_type x, second_type y) : base_type(std::move_if_noexcept(y)), first_(std::move_if_noexcept(x)) { }
177 
compressed_pair_impl(first_type x)178   compressed_pair_impl(first_type x) : first_(std::move_if_noexcept(x)) { }
179 
first()180   first_type       &first() noexcept { return first_; }
first() const181   const first_type &first() const noexcept { return first_; }
182 
second()183   second_type       &second() noexcept { return *this; }
second() const184   const second_type &second() const noexcept { return *this; }
185 
186 private:
187   first_type first_;
188 };
189 
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;
195 
196 public:
197   using first_type  = T;
198   using second_type = U;
199 
200   using first_type::first_type;
201   using second_type::second_type;
202 
203   compressed_pair_impl() = default;
204 
compressed_pair_impl(first_type x,second_type y)205   compressed_pair_impl(first_type x, second_type y) : first_type(std::move_if_noexcept(x)), second_type(std::move_if_noexcept(y)) { }
206 
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
first()213   first_type       &first() noexcept { return static_cast<first_type &>(*this); }
first() const214   const first_type &first() const noexcept { return static_cast<const first_type &>(*this); }
215 
second()216   second_type       &second() noexcept { return static_cast<second_type &>(*this); }
second() const217   const second_type &second() const noexcept { return static_cast<const second_type &>(*this); }
218 };
219 
220 } // namespace detail
221 
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>;
232 
233 public:
234   using base_type::base_type;
235 };
236 
237 // intel compilers don't implement empty base optimization, so these tests fail
238 #if !defined(__INTEL_COMPILER) && !defined(__ICL)
239 
240 namespace compressed_pair_test
241 {
242 
243 struct Empty { };
244 
245 static_assert(std::is_empty<Empty>::value, "");
246 static_assert(sizeof(Empty) == 1, "");
247 
248 struct Empty2 { };
249 
250 static_assert(std::is_empty<Empty2>::value, "");
251 static_assert(sizeof(Empty2) == 1, "");
252 
253 struct NotEmpty {
254   std::uint32_t d{};
255 };
256 
257 static_assert(!std::is_empty<NotEmpty>::value, "");
258 static_assert(sizeof(NotEmpty) > 1, "");
259 
260 struct EmptyMember {
261   Empty  m{};
262   Empty2 m2{};
263 };
264 
265 static_assert(!std::is_empty<EmptyMember>::value, "");
266 static_assert(sizeof(EmptyMember) > 1, "");
267 
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, "");
271 
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, "");
275 
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)), "");
279 
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)), "");
283 
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)), "");
288 
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)), "");
292 
293 } // namespace compressed_pair_test
294 
295 #endif
296 
297 } // namespace util
298 
299 } // namespace Petsc
300