Line data Source code
1 0 : // Distributed under the MIT License.
2 : // See LICENSE.txt for details.
3 :
4 : #pragma once
5 :
6 : #include <array>
7 : #include <cstddef>
8 : #include <functional>
9 : #include <type_traits>
10 : #include <utility>
11 : #include <variant>
12 :
13 : #include "Options/String.hpp"
14 : #include "Utilities/ErrorHandling/Error.hpp"
15 : #include "Utilities/Gsl.hpp"
16 : #include "Utilities/Requires.hpp"
17 : #include "Utilities/Serialization/PupStlCpp17.hpp"
18 : #include "Utilities/TMPL.hpp"
19 :
20 : /// \cond
21 : namespace Options {
22 : template <typename... AlternativeLists>
23 : struct Alternatives;
24 : } // namespace Options
25 : namespace PUP {
26 : class er;
27 : } // namespace PUP
28 : /// \endcond
29 :
30 : /// \ingroup DataStructuresGroup
31 : ///
32 : /// TaggedVariant and related functionality.
33 1 : namespace variants {
34 : namespace TaggedVariant_detail {
35 : template <typename R, typename Variant, typename Tag, typename Visitor>
36 : struct VisitAlternative;
37 : } // namespace TaggedVariant_detail
38 :
39 : /// \cond
40 : template <typename... Tags>
41 : class TaggedVariant;
42 : template <typename... Tags>
43 : constexpr bool operator==(const TaggedVariant<Tags...>& a,
44 : const TaggedVariant<Tags...>& b);
45 : template <typename... Tags>
46 : constexpr bool operator<(const TaggedVariant<Tags...>& a,
47 : const TaggedVariant<Tags...>& b);
48 : /// \endcond
49 :
50 : /// \ingroup DataStructuresGroup
51 : ///
52 : /// A class similar to `std::variant`, but indexed by tag structs.
53 : ///
54 : /// \see variants::get, variants::get_if, variants::holds_alternative,
55 : /// variants::visit
56 : template <typename... Tags>
57 1 : class TaggedVariant {
58 : private:
59 : static_assert(sizeof...(Tags) > 0);
60 : static_assert(
61 : std::is_same_v<tmpl::remove_duplicates<TaggedVariant>, TaggedVariant>,
62 : "TaggedVariant cannot have duplicate tags.");
63 :
64 : template <typename Tag>
65 0 : static constexpr size_t data_index =
66 : tmpl::index_of<TaggedVariant, Tag>::value;
67 :
68 : public:
69 : /// A default constructed instance has the first tag active.
70 1 : TaggedVariant() = default;
71 0 : TaggedVariant(const TaggedVariant&) = default;
72 0 : TaggedVariant(TaggedVariant&&) = default;
73 0 : TaggedVariant& operator=(const TaggedVariant&) = default;
74 0 : TaggedVariant& operator=(TaggedVariant&&) = default;
75 0 : ~TaggedVariant() = default;
76 :
77 : /// Construct with \p Tag active, using \p args to construct the
78 : /// contained object.
79 : ///
80 : /// \snippet DataStructures/Test_TaggedVariant.cpp construct in_place_type
81 : template <
82 : typename Tag, typename... Args,
83 : Requires<(... or std::is_same_v<Tag, Tags>) and
84 : std::is_constructible_v<typename Tag::type, Args...>> = nullptr>
85 1 : constexpr explicit TaggedVariant(std::in_place_type_t<Tag> /*meta*/,
86 : Args&&... args)
87 : : data_(std::in_place_index<data_index<Tag>>,
88 : std::forward<Args>(args)...) {}
89 :
90 : /// Construct the contained object from \p args. Only available if
91 : /// the TaggedVariant only has one tag.
92 : ///
93 : /// \snippet DataStructures/Test_TaggedVariant.cpp construct single
94 : template <typename... Args,
95 : Requires<sizeof...(Tags) == 1 and
96 : std::is_constructible_v<
97 : typename tmpl::front<TaggedVariant>::type, Args...>> =
98 : nullptr>
99 1 : constexpr explicit TaggedVariant(Args&&... args)
100 : : TaggedVariant(std::in_place_type<tmpl::front<TaggedVariant>>,
101 : std::forward<Args>(args)...) {}
102 :
103 : /// A TaggedVariant can be implicitly move-converted to another
104 : /// variant with a superset of the tags.
105 : ///
106 : /// \snippet DataStructures/Test_TaggedVariant.cpp convert
107 : /// @{
108 : template <typename... OtherTags,
109 : Requires<tmpl::size<tmpl::list_difference<
110 : TaggedVariant<OtherTags...>, TaggedVariant>>::value ==
111 : 0> = nullptr>
112 : // NOLINTNEXTLINE(google-explicit-constructor)
113 1 : constexpr TaggedVariant(TaggedVariant<OtherTags...>&& other)
114 : : TaggedVariant(visit(
115 : []<typename Tag>(
116 : std::pair<tmpl::type_<Tag>, typename Tag::type&&> entry) {
117 : return TaggedVariant(std::in_place_type<Tag>,
118 : std::move(entry.second));
119 : },
120 : std::move(other))) {}
121 :
122 : template <typename... OtherTags,
123 : Requires<tmpl::size<tmpl::list_difference<
124 : TaggedVariant<OtherTags...>, TaggedVariant>>::value ==
125 : 0> = nullptr>
126 1 : constexpr TaggedVariant& operator=(TaggedVariant<OtherTags...>&& other) {
127 : visit(
128 : [&]<typename Tag>(
129 : std::pair<tmpl::type_<Tag>, typename Tag::type&&> entry) {
130 : emplace<Tag>(std::move(entry.second));
131 : },
132 : std::move(other));
133 : return *this;
134 : }
135 : /// @}
136 :
137 : /// The index into the `Tags...` of the active object.
138 1 : constexpr size_t index() const { return data_.index(); }
139 :
140 : /// See `std::variant::valueless_by_exception`.
141 1 : constexpr bool valueless_by_exception() const {
142 : return data_.valueless_by_exception();
143 : }
144 :
145 : /// Destroys the contained object and actives \p Tag, constructing a
146 : /// new value from \p args.
147 : ///
148 : /// \snippet DataStructures/Test_TaggedVariant.cpp emplace
149 : template <
150 : typename Tag, typename... Args,
151 : Requires<(... or std::is_same_v<Tag, Tags>) and
152 : std::is_constructible_v<typename Tag::type, Args...>> = nullptr>
153 1 : constexpr typename Tag::type& emplace(Args&&... args) {
154 : return data_.template emplace<data_index<Tag>>(std::forward<Args>(args)...);
155 : }
156 :
157 0 : constexpr void swap(TaggedVariant& other) noexcept(noexcept(
158 : (... and (std::is_nothrow_move_constructible_v<typename Tags::type> and
159 : std::is_nothrow_swappable_v<typename Tags::type>)))) {
160 : data_.swap(other.data_);
161 : }
162 :
163 0 : void pup(PUP::er& p) { p | data_; }
164 :
165 : /// A TaggedVariant over option tags can be parsed as any of them.
166 : /// @{
167 1 : static constexpr Options::String help = "One of multiple options";
168 1 : using options = tmpl::list<Options::Alternatives<tmpl::list<Tags>...>>;
169 : template <typename Tag>
170 1 : explicit TaggedVariant(tmpl::list<Tag> /*meta*/, typename Tag::type value)
171 : : TaggedVariant(std::in_place_type<Tag>, std::move(value)) {}
172 : /// @}
173 :
174 : private:
175 : template <typename R, typename Variant, typename Tag, typename Visitor>
176 : friend struct TaggedVariant_detail::VisitAlternative;
177 :
178 : template <typename Tag, typename... Tags2>
179 0 : friend constexpr typename Tag::type& get(TaggedVariant<Tags2...>& variant);
180 : template <typename Tag, typename... Tags2>
181 0 : friend constexpr const typename Tag::type& get(
182 : const TaggedVariant<Tags2...>& variant);
183 : template <typename Tag, typename... Tags2>
184 0 : friend constexpr typename Tag::type&& get(TaggedVariant<Tags2...>&& variant);
185 : template <typename Tag, typename... Tags2>
186 0 : friend constexpr const typename Tag::type&& get(
187 : const TaggedVariant<Tags2...>&& variant);
188 :
189 0 : friend constexpr bool operator== <Tags...>(const TaggedVariant<Tags...>& a,
190 : const TaggedVariant<Tags...>& b);
191 0 : friend constexpr bool operator< <Tags...>(const TaggedVariant<Tags...>& a,
192 : const TaggedVariant<Tags...>& b);
193 :
194 : friend struct std::hash<TaggedVariant>;
195 :
196 0 : std::variant<typename Tags::type...> data_;
197 : };
198 :
199 : namespace TaggedVariant_detail {
200 : template <typename... Tags>
201 : constexpr bool is_variant_or_derived(const TaggedVariant<Tags...>* /*meta*/) {
202 : return true;
203 : }
204 : // NOLINTNEXTLINE(cert-dcl50-cpp) - variadic function
205 : constexpr bool is_variant_or_derived(...) { return false; }
206 :
207 : template <typename Tag, typename Value>
208 : constexpr std::pair<tmpl::type_<Tag>, Value&&> make_visitor_pair(
209 : Value&& value) {
210 : return {tmpl::type_<Tag>{}, std::forward<Value>(value)};
211 : }
212 :
213 : struct DeduceReturn;
214 :
215 : template <typename R, typename Variant, typename Tag, typename Visitor>
216 : struct VisitAlternative {
217 : static constexpr R apply(Variant&& variant, const Visitor& visitor) {
218 : return std::forward<Visitor>(visitor)(make_visitor_pair<Tag>(
219 : get<std::decay_t<Variant>::template data_index<Tag>>(
220 : std::forward<Variant>(variant).data_)));
221 : }
222 : };
223 :
224 : template <typename Variant, typename Tag, typename Visitor>
225 : struct VisitAlternative<void, Variant, Tag, Visitor> {
226 : static constexpr void apply(Variant&& variant, const Visitor& visitor) {
227 : std::forward<Visitor>(visitor)(make_visitor_pair<Tag>(
228 : get<std::decay_t<Variant>::template data_index<Tag>>(
229 : std::forward<Variant>(variant).data_)));
230 : }
231 : };
232 :
233 : template <typename Variant, typename Tag, typename Visitor>
234 : struct VisitAlternative<DeduceReturn, Variant, Tag, Visitor> {
235 : static constexpr decltype(auto) apply(Variant&& variant,
236 : const Visitor& visitor) {
237 : return std::forward<Visitor>(visitor)(make_visitor_pair<Tag>(
238 : get<std::decay_t<Variant>::template data_index<Tag>>(
239 : std::forward<Variant>(variant).data_)));
240 : }
241 : };
242 :
243 : template <typename... Tags>
244 : constexpr TaggedVariant<Tags...>& as_variant(TaggedVariant<Tags...>& variant) {
245 : return variant;
246 : }
247 : template <typename... Tags>
248 : constexpr const TaggedVariant<Tags...>& as_variant(
249 : const TaggedVariant<Tags...>& variant) {
250 : return variant;
251 : }
252 : template <typename... Tags>
253 : constexpr TaggedVariant<Tags...>&& as_variant(
254 : TaggedVariant<Tags...>&& variant) {
255 : return std::move(variant);
256 : }
257 : template <typename... Tags>
258 : constexpr const TaggedVariant<Tags...>&& as_variant(
259 : const TaggedVariant<Tags...>&& variant) {
260 : return std::move(variant);
261 : }
262 :
263 : template <typename R, typename Visitor, typename... Variants>
264 : constexpr decltype(auto) visit_impl(Visitor&& visitor) {
265 : return std::forward<Visitor>(visitor)();
266 : }
267 :
268 : template <typename R, typename Variant, typename Visitor,
269 : typename DecayedVariant = std::decay_t<Variant>>
270 : struct VisitJumpTable;
271 :
272 : template <typename R, typename Variant, typename Visitor, typename... Tags>
273 : struct VisitJumpTable<R, Variant, Visitor, TaggedVariant<Tags...>> {
274 : static constexpr std::array value{
275 : VisitAlternative<R, Variant, Tags, Visitor>::apply...};
276 : };
277 :
278 : template <typename R, typename Visitor, typename FirstVariant,
279 : typename... Variants>
280 : constexpr decltype(auto) visit_impl(Visitor&& visitor,
281 : FirstVariant&& first_variant,
282 : Variants&&... variants) {
283 : const auto recurse = [&]<typename Arg>(Arg&& first_arg) {
284 : return visit_impl<R>(
285 : [&]<typename... Rest>(Rest&&... rest) {
286 : return std::forward<Visitor>(visitor)(std::forward<Arg>(first_arg),
287 : std::forward<Rest>(rest)...);
288 : },
289 : std::forward<Variants>(variants)...);
290 : };
291 : if (UNLIKELY(first_variant.valueless_by_exception())) {
292 : throw std::bad_variant_access{};
293 : }
294 : return gsl::at(VisitJumpTable<R, FirstVariant, decltype(recurse)>::value,
295 : first_variant.index())(
296 : std::forward<FirstVariant>(first_variant), recurse);
297 : }
298 : } // namespace TaggedVariant_detail
299 :
300 : /// Call \p visitor with the contents of one or more variants.
301 : ///
302 : /// Calls \p visitor with the contents of each variant as arguments,
303 : /// passed as `std::pair<tmpl::type_<Tag>, typename Tag::type ref>`,
304 : /// where `Tag` is the active tag of the variant and `ref` is a
305 : /// reference qualifier matching that of the passed variant.
306 : ///
307 : /// If the template parameter \p R is supplied, the result is
308 : /// implicitly converted to that type (which may be `void`).
309 : /// Otherwise it is deduced from the return type of \p visitor, which
310 : /// must be the same for all tags in the variant.
311 : ///
312 : /// \warning Unlike `visit` for `std::variant`, the types of the
313 : /// visitor arguments do not allow for implicit conversions between
314 : /// reference types. If the visitor expects, for example,
315 : /// `std::pair<tmpl::type_<Tag>, const typename Tag::type&>`, the caller must
316 : /// ensure that the passed variant is a const lvalue.
317 : ///
318 : /// \snippet DataStructures/Test_TaggedVariant.cpp visit
319 : /// @{
320 : template <typename Visitor, typename... Variants,
321 : Requires<(... and TaggedVariant_detail::is_variant_or_derived(
322 : std::add_pointer_t<std::remove_reference_t<
323 : Variants>>{}))> = nullptr>
324 1 : constexpr decltype(auto) visit(Visitor&& visitor, Variants&&... variants) {
325 : return TaggedVariant_detail::visit_impl<TaggedVariant_detail::DeduceReturn>(
326 : visitor,
327 : TaggedVariant_detail::as_variant(std::forward<Variants>(variants))...);
328 : }
329 :
330 : template <typename R, typename Visitor, typename... Variants,
331 : Requires<(... and TaggedVariant_detail::is_variant_or_derived(
332 : std::add_pointer_t<std::remove_reference_t<
333 : Variants>>{}))> = nullptr>
334 1 : constexpr R visit(Visitor&& visitor, Variants&&... variants) {
335 : return TaggedVariant_detail::visit_impl<R>(
336 : visitor,
337 : TaggedVariant_detail::as_variant(std::forward<Variants>(variants))...);
338 : }
339 : /// @}
340 :
341 : /// Check whether \p Tag is active.
342 : template <typename Tag, typename... Tags>
343 1 : constexpr bool holds_alternative(const TaggedVariant<Tags...>& variant) {
344 : return variant.index() == tmpl::index_of<TaggedVariant<Tags...>, Tag>::value;
345 : }
346 :
347 : /// Access the contained object. Throws `std::bad_variant_access` if
348 : /// \p Tag is not active.
349 : /// @{
350 : template <typename Tag, typename... Tags>
351 1 : constexpr typename Tag::type& get(TaggedVariant<Tags...>& variant) {
352 : return get<TaggedVariant<Tags...>::template data_index<Tag>>(variant.data_);
353 : }
354 : template <typename Tag, typename... Tags>
355 1 : constexpr const typename Tag::type& get(const TaggedVariant<Tags...>& variant) {
356 : return get<TaggedVariant<Tags...>::template data_index<Tag>>(variant.data_);
357 : }
358 : template <typename Tag, typename... Tags>
359 1 : constexpr typename Tag::type&& get(TaggedVariant<Tags...>&& variant) {
360 : return get<TaggedVariant<Tags...>::template data_index<Tag>>(
361 : std::move(variant.data_));
362 : }
363 : template <typename Tag, typename... Tags>
364 1 : constexpr const typename Tag::type&& get(
365 : const TaggedVariant<Tags...>&& variant) {
366 : return get<TaggedVariant<Tags...>::template data_index<Tag>>(
367 : std::move(variant.data_));
368 : }
369 : /// @}
370 :
371 : /// Returns a pointer to the contained object if \p variant is a
372 : /// non-null pointer and \p Tag is active. Otherwise, returns
373 : /// `nullptr`.
374 : /// @{
375 : template <typename Tag, typename... Tags>
376 1 : constexpr const typename Tag::type* get_if(
377 : const TaggedVariant<Tags...>* variant) {
378 : if (variant != nullptr and holds_alternative<Tag>(*variant)) {
379 : return &get<Tag>(*variant);
380 : } else {
381 : return nullptr;
382 : }
383 : }
384 : template <typename Tag, typename... Tags>
385 1 : constexpr typename Tag::type* get_if(TaggedVariant<Tags...>* variant) {
386 : if (variant != nullptr and holds_alternative<Tag>(*variant)) {
387 : return &get<Tag>(*variant);
388 : } else {
389 : return nullptr;
390 : }
391 : }
392 : /// @}
393 :
394 : template <typename... Tags>
395 0 : constexpr bool operator==(const TaggedVariant<Tags...>& a,
396 : const TaggedVariant<Tags...>& b) {
397 : return a.data_ == b.data_;
398 : }
399 : template <typename... Tags>
400 0 : constexpr bool operator!=(const TaggedVariant<Tags...>& a,
401 : const TaggedVariant<Tags...>& b) {
402 : return not(a == b);
403 : }
404 : template <typename... Tags>
405 0 : constexpr bool operator<(const TaggedVariant<Tags...>& a,
406 : const TaggedVariant<Tags...>& b) {
407 : return a.data_ < b.data_;
408 : }
409 : template <typename... Tags>
410 0 : constexpr bool operator>(const TaggedVariant<Tags...>& a,
411 : const TaggedVariant<Tags...>& b) {
412 : return b < a;
413 : }
414 : template <typename... Tags>
415 0 : constexpr bool operator<=(const TaggedVariant<Tags...>& a,
416 : const TaggedVariant<Tags...>& b) {
417 : return not(b < a);
418 : }
419 : template <typename... Tags>
420 0 : constexpr bool operator>=(const TaggedVariant<Tags...>& a,
421 : const TaggedVariant<Tags...>& b) {
422 : return not(a < b);
423 : }
424 :
425 : /// Comparison operators against single-tag variants. Primarily useful
426 : /// in tests.
427 : /// @{
428 : template <typename... Tags, typename Tag,
429 : Requires<(sizeof...(Tags) > 1 and
430 : (... or std::is_same_v<Tags, Tag>))> = nullptr>
431 1 : constexpr bool operator==(const TaggedVariant<Tags...>& a,
432 : const TaggedVariant<Tag>& b) {
433 : return holds_alternative<Tag>(a) and get<Tag>(a) == get<Tag>(b);
434 : }
435 : template <typename... Tags, typename Tag,
436 : Requires<(sizeof...(Tags) > 1 and
437 : (... or std::is_same_v<Tags, Tag>))> = nullptr>
438 1 : constexpr bool operator==(const TaggedVariant<Tag>& a,
439 : const TaggedVariant<Tags...>& b) {
440 : return b == a;
441 : }
442 : template <typename... Tags, typename Tag,
443 : Requires<(sizeof...(Tags) > 1 and
444 : (... or std::is_same_v<Tags, Tag>))> = nullptr>
445 1 : constexpr bool operator!=(const TaggedVariant<Tags...>& a,
446 : const TaggedVariant<Tag>& b) {
447 : return not(a == b);
448 : }
449 : template <typename... Tags, typename Tag,
450 : Requires<(sizeof...(Tags) > 1 and
451 : (... or std::is_same_v<Tags, Tag>))> = nullptr>
452 1 : constexpr bool operator!=(const TaggedVariant<Tag>& a,
453 : const TaggedVariant<Tags...>& b) {
454 : return not(a == b);
455 : }
456 : /// @}
457 :
458 : template <
459 : typename... Tags,
460 : Requires<(... and (std::is_move_constructible_v<typename Tags::type> and
461 : std::is_swappable_v<typename Tags::type>))> = nullptr>
462 0 : constexpr void swap(TaggedVariant<Tags...>& a,
463 : TaggedVariant<Tags...>& b) noexcept(noexcept(a.swap(b))) {
464 : a.swap(b);
465 : }
466 : } // namespace variants
467 :
468 : namespace std {
469 : template <typename... Tags>
470 : // https://github.com/llvm/llvm-project/issues/45454
471 : // NOLINTNEXTLINE(cert-dcl58-cpp)
472 : struct hash<::variants::TaggedVariant<Tags...>> {
473 : size_t operator()(const ::variants::TaggedVariant<Tags...>& variant) const {
474 : return std::hash<decltype(variant.data_)>{}(variant.data_);
475 : }
476 : };
477 : } // namespace std
|