• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 // Copyright 2018 The Abseil Authors.
2 //
3 // Licensed under the Apache License, Version 2.0 (the "License");
4 // you may not use this file except in compliance with the License.
5 // You may obtain a copy of the License at
6 //
7 //      https://www.apache.org/licenses/LICENSE-2.0
8 //
9 // Unless required by applicable law or agreed to in writing, software
10 // distributed under the License is distributed on an "AS IS" BASIS,
11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 // See the License for the specific language governing permissions and
13 // limitations under the License.
14 //
15 // Helper class to perform the Empty Base Optimization.
16 // Ts can contain classes and non-classes, empty or not. For the ones that
17 // are empty classes, we perform the optimization. If all types in Ts are empty
18 // classes, then CompressedTuple<Ts...> is itself an empty class.
19 //
20 // To access the members, use member get<N>() function.
21 //
22 // Eg:
23 //   absl::container_internal::CompressedTuple<int, T1, T2, T3> value(7, t1, t2,
24 //                                                                    t3);
25 //   assert(value.get<0>() == 7);
26 //   T1& t1 = value.get<1>();
27 //   const T2& t2 = value.get<2>();
28 //   ...
29 //
30 // https://en.cppreference.com/w/cpp/language/ebo
31 
32 #ifndef ABSL_CONTAINER_INTERNAL_COMPRESSED_TUPLE_H_
33 #define ABSL_CONTAINER_INTERNAL_COMPRESSED_TUPLE_H_
34 
35 #include <initializer_list>
36 #include <tuple>
37 #include <type_traits>
38 #include <utility>
39 
40 #include "absl/utility/utility.h"
41 
42 #if defined(_MSC_VER) && !defined(__NVCC__)
43 // We need to mark these classes with this declspec to ensure that
44 // CompressedTuple happens.
45 #define ABSL_INTERNAL_COMPRESSED_TUPLE_DECLSPEC __declspec(empty_bases)
46 #else
47 #define ABSL_INTERNAL_COMPRESSED_TUPLE_DECLSPEC
48 #endif
49 
50 namespace absl {
51 ABSL_NAMESPACE_BEGIN
52 namespace container_internal {
53 
54 template <typename... Ts>
55 class CompressedTuple;
56 
57 namespace internal_compressed_tuple {
58 
59 template <typename D, size_t I>
60 struct Elem;
61 template <typename... B, size_t I>
62 struct Elem<CompressedTuple<B...>, I>
63     : std::tuple_element<I, std::tuple<B...>> {};
64 template <typename D, size_t I>
65 using ElemT = typename Elem<D, I>::type;
66 
67 // Use the __is_final intrinsic if available. Where it's not available, classes
68 // declared with the 'final' specifier cannot be used as CompressedTuple
69 // elements.
70 // TODO(sbenza): Replace this with std::is_final in C++14.
71 template <typename T>
72 constexpr bool IsFinal() {
73 #if defined(__clang__) || defined(__GNUC__)
74   return __is_final(T);
75 #else
76   return false;
77 #endif
78 }
79 
80 // We can't use EBCO on other CompressedTuples because that would mean that we
81 // derive from multiple Storage<> instantiations with the same I parameter,
82 // and potentially from multiple identical Storage<> instantiations.  So anytime
83 // we use type inheritance rather than encapsulation, we mark
84 // CompressedTupleImpl, to make this easy to detect.
85 struct uses_inheritance {};
86 
87 template <typename T>
88 constexpr bool ShouldUseBase() {
89   return std::is_class<T>::value && std::is_empty<T>::value && !IsFinal<T>() &&
90          !std::is_base_of<uses_inheritance, T>::value;
91 }
92 
93 // The storage class provides two specializations:
94 //  - For empty classes, it stores T as a base class.
95 //  - For everything else, it stores T as a member.
96 template <typename T, size_t I,
97 #if defined(_MSC_VER)
98           bool UseBase =
99               ShouldUseBase<typename std::enable_if<true, T>::type>()>
100 #else
101           bool UseBase = ShouldUseBase<T>()>
102 #endif
103 struct Storage {
104   T value;
105   constexpr Storage() = default;
106   template <typename V>
107   explicit constexpr Storage(absl::in_place_t, V&& v)
108       : value(absl::forward<V>(v)) {}
109   constexpr const T& get() const& { return value; }
110   T& get() & { return value; }
111   constexpr const T&& get() const&& { return absl::move(*this).value; }
112   T&& get() && { return std::move(*this).value; }
113 };
114 
115 template <typename T, size_t I>
116 struct ABSL_INTERNAL_COMPRESSED_TUPLE_DECLSPEC Storage<T, I, true> : T {
117   constexpr Storage() = default;
118 
119   template <typename V>
120   explicit constexpr Storage(absl::in_place_t, V&& v)
121       : T(absl::forward<V>(v)) {}
122 
123   constexpr const T& get() const& { return *this; }
124   T& get() & { return *this; }
125   constexpr const T&& get() const&& { return absl::move(*this); }
126   T&& get() && { return std::move(*this); }
127 };
128 
129 template <typename D, typename I, bool ShouldAnyUseBase>
130 struct ABSL_INTERNAL_COMPRESSED_TUPLE_DECLSPEC CompressedTupleImpl;
131 
132 template <typename... Ts, size_t... I, bool ShouldAnyUseBase>
133 struct ABSL_INTERNAL_COMPRESSED_TUPLE_DECLSPEC CompressedTupleImpl<
134     CompressedTuple<Ts...>, absl::index_sequence<I...>, ShouldAnyUseBase>
135     // We use the dummy identity function through std::integral_constant to
136     // convince MSVC of accepting and expanding I in that context. Without it
137     // you would get:
138     //   error C3548: 'I': parameter pack cannot be used in this context
139     : uses_inheritance,
140       Storage<Ts, std::integral_constant<size_t, I>::value>... {
141   constexpr CompressedTupleImpl() = default;
142   template <typename... Vs>
143   explicit constexpr CompressedTupleImpl(absl::in_place_t, Vs&&... args)
144       : Storage<Ts, I>(absl::in_place, absl::forward<Vs>(args))... {}
145   friend CompressedTuple<Ts...>;
146 };
147 
148 template <typename... Ts, size_t... I>
149 struct ABSL_INTERNAL_COMPRESSED_TUPLE_DECLSPEC CompressedTupleImpl<
150     CompressedTuple<Ts...>, absl::index_sequence<I...>, false>
151     // We use the dummy identity function as above...
152     : Storage<Ts, std::integral_constant<size_t, I>::value, false>... {
153   constexpr CompressedTupleImpl() = default;
154   template <typename... Vs>
155   explicit constexpr CompressedTupleImpl(absl::in_place_t, Vs&&... args)
156       : Storage<Ts, I, false>(absl::in_place, absl::forward<Vs>(args))... {}
157   friend CompressedTuple<Ts...>;
158 };
159 
160 std::false_type Or(std::initializer_list<std::false_type>);
161 std::true_type Or(std::initializer_list<bool>);
162 
163 // MSVC requires this to be done separately rather than within the declaration
164 // of CompressedTuple below.
165 template <typename... Ts>
166 constexpr bool ShouldAnyUseBase() {
167   return decltype(
168       Or({std::integral_constant<bool, ShouldUseBase<Ts>()>()...})){};
169 }
170 
171 template <typename T, typename V>
172 using TupleElementMoveConstructible =
173     typename std::conditional<std::is_reference<T>::value,
174                               std::is_convertible<V, T>,
175                               std::is_constructible<T, V&&>>::type;
176 
177 template <bool SizeMatches, class T, class... Vs>
178 struct TupleMoveConstructible : std::false_type {};
179 
180 template <class... Ts, class... Vs>
181 struct TupleMoveConstructible<true, CompressedTuple<Ts...>, Vs...>
182     : std::integral_constant<
183           bool, absl::conjunction<
184                     TupleElementMoveConstructible<Ts, Vs&&>...>::value> {};
185 
186 template <typename T>
187 struct compressed_tuple_size;
188 
189 template <typename... Es>
190 struct compressed_tuple_size<CompressedTuple<Es...>>
191     : public std::integral_constant<std::size_t, sizeof...(Es)> {};
192 
193 template <class T, class... Vs>
194 struct TupleItemsMoveConstructible
195     : std::integral_constant<
196           bool, TupleMoveConstructible<compressed_tuple_size<T>::value ==
197                                            sizeof...(Vs),
198                                        T, Vs...>::value> {};
199 
200 }  // namespace internal_compressed_tuple
201 
202 // Helper class to perform the Empty Base Class Optimization.
203 // Ts can contain classes and non-classes, empty or not. For the ones that
204 // are empty classes, we perform the CompressedTuple. If all types in Ts are
205 // empty classes, then CompressedTuple<Ts...> is itself an empty class.  (This
206 // does not apply when one or more of those empty classes is itself an empty
207 // CompressedTuple.)
208 //
209 // To access the members, use member .get<N>() function.
210 //
211 // Eg:
212 //   absl::container_internal::CompressedTuple<int, T1, T2, T3> value(7, t1, t2,
213 //                                                                    t3);
214 //   assert(value.get<0>() == 7);
215 //   T1& t1 = value.get<1>();
216 //   const T2& t2 = value.get<2>();
217 //   ...
218 //
219 // https://en.cppreference.com/w/cpp/language/ebo
220 template <typename... Ts>
221 class ABSL_INTERNAL_COMPRESSED_TUPLE_DECLSPEC CompressedTuple
222     : private internal_compressed_tuple::CompressedTupleImpl<
223           CompressedTuple<Ts...>, absl::index_sequence_for<Ts...>,
224           internal_compressed_tuple::ShouldAnyUseBase<Ts...>()> {
225  private:
226   template <int I>
227   using ElemT = internal_compressed_tuple::ElemT<CompressedTuple, I>;
228 
229   template <int I>
230   using StorageT = internal_compressed_tuple::Storage<ElemT<I>, I>;
231 
232  public:
233   // There seems to be a bug in MSVC dealing in which using '=default' here will
234   // cause the compiler to ignore the body of other constructors. The work-
235   // around is to explicitly implement the default constructor.
236 #if defined(_MSC_VER)
237   constexpr CompressedTuple() : CompressedTuple::CompressedTupleImpl() {}
238 #else
239   constexpr CompressedTuple() = default;
240 #endif
241   explicit constexpr CompressedTuple(const Ts&... base)
242       : CompressedTuple::CompressedTupleImpl(absl::in_place, base...) {}
243 
244   template <typename First, typename... Vs,
245             absl::enable_if_t<
246                 absl::conjunction<
247                     // Ensure we are not hiding default copy/move constructors.
248                     absl::negation<std::is_same<void(CompressedTuple),
249                                                 void(absl::decay_t<First>)>>,
250                     internal_compressed_tuple::TupleItemsMoveConstructible<
251                         CompressedTuple<Ts...>, First, Vs...>>::value,
252                 bool> = true>
253   explicit constexpr CompressedTuple(First&& first, Vs&&... base)
254       : CompressedTuple::CompressedTupleImpl(absl::in_place,
255                                              absl::forward<First>(first),
256                                              absl::forward<Vs>(base)...) {}
257 
258   template <int I>
259   ElemT<I>& get() & {
260     return StorageT<I>::get();
261   }
262 
263   template <int I>
264   constexpr const ElemT<I>& get() const& {
265     return StorageT<I>::get();
266   }
267 
268   template <int I>
269   ElemT<I>&& get() && {
270     return std::move(*this).StorageT<I>::get();
271   }
272 
273   template <int I>
274   constexpr const ElemT<I>&& get() const&& {
275     return absl::move(*this).StorageT<I>::get();
276   }
277 };
278 
279 // Explicit specialization for a zero-element tuple
280 // (needed to avoid ambiguous overloads for the default constructor).
281 template <>
282 class ABSL_INTERNAL_COMPRESSED_TUPLE_DECLSPEC CompressedTuple<> {};
283 
284 }  // namespace container_internal
285 ABSL_NAMESPACE_END
286 }  // namespace absl
287 
288 #undef ABSL_INTERNAL_COMPRESSED_TUPLE_DECLSPEC
289 
290 #endif  // ABSL_CONTAINER_INTERNAL_COMPRESSED_TUPLE_H_
291