xref: /llvm-project/llvm/include/llvm/Frontend/OpenMP/ClauseT.h (revision 03cbe42627c7a7940b47cc1a2cda0120bc9c6d5e)
1 //===- ClauseT.h -- clause template definitions ---------------------------===//
2 //
3 // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4 // See https://llvm.org/LICENSE.txt for license information.
5 // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6 //
7 //===----------------------------------------------------------------------===//
8 // This file contains template classes that represent OpenMP clauses, as
9 // described in the OpenMP API specification.
10 //
11 // The general structure of any specific clause class is that it is either
12 // empty, or it consists of a single data member, which can take one of these
13 // three forms:
14 // - a value member, named `v`, or
15 // - a tuple of values, named `t`, or
16 // - a variant (i.e. union) of values, named `u`.
17 // To assist with generic visit algorithms, classes define one of the following
18 // traits:
19 // - EmptyTrait: the class has no data members.
20 // - WrapperTrait: the class has a single member `v`
21 // - TupleTrait: the class has a tuple member `t`
22 // - UnionTrait the class has a variant member `u`
23 // - IncompleteTrait: the class is a placeholder class that is currently empty,
24 //   but will be completed at a later time.
25 // Note: This structure follows the one used in flang parser.
26 //
27 // The types used in the class definitions follow the names used in the spec
28 // (there are a few exceptions to this). For example, given
29 //   Clause `foo`
30 //   - foo-modifier : description...
31 //   - list         : list of variables
32 // the corresponding class would be
33 //   template <...>
34 //   struct FooT {
35 //     using FooModifier = type that can represent the modifier
36 //     using List = ListT<ObjectT<...>>;
37 //     using TupleTrait = std::true_type;
38 //     std::tuple<std::optional<FooModifier>, List> t;
39 //   };
40 //===----------------------------------------------------------------------===//
41 #ifndef LLVM_FRONTEND_OPENMP_CLAUSET_H
42 #define LLVM_FRONTEND_OPENMP_CLAUSET_H
43 
44 #include "llvm/ADT/ArrayRef.h"
45 #include "llvm/ADT/DenseMap.h"
46 #include "llvm/ADT/DenseSet.h"
47 #include "llvm/ADT/STLExtras.h"
48 #include "llvm/ADT/SmallVector.h"
49 #include "llvm/Frontend/OpenMP/OMP.h"
50 #include "llvm/Support/ErrorHandling.h"
51 #include "llvm/Support/raw_ostream.h"
52 
53 #include <algorithm>
54 #include <iterator>
55 #include <optional>
56 #include <tuple>
57 #include <type_traits>
58 #include <utility>
59 #include <variant>
60 
61 #define ENUM(Name, ...) enum class Name { __VA_ARGS__ }
62 #define OPT(x) std::optional<x>
63 
64 // A number of OpenMP clauses contain values that come from a given set of
65 // possibilities. In the IR these are usually represented by enums. Both
66 // clang and flang use different types for the enums, and the enum elements
67 // representing the same thing may have different values between clang and
68 // flang.
69 // Since the representation below tries to adhere to the spec, and be source
70 // language agnostic, it defines its own enums, independent from any language
71 // frontend. As a consequence, when instantiating the templates below,
72 // frontend-specific enums need to be translated into the representation
73 // used here. The macros below are intended to assist with the conversion.
74 
75 // Helper macro for enum-class conversion.
76 #define CLAUSET_SCOPED_ENUM_MEMBER_CONVERT(Ov, Tv)                             \
77   if (v == OtherEnum::Ov) {                                                    \
78     return ThisEnum::Tv;                                                       \
79   }
80 
81 // Helper macro for enum (non-class) conversion.
82 #define CLAUSET_UNSCOPED_ENUM_MEMBER_CONVERT(Ov, Tv)                           \
83   if (v == Ov) {                                                               \
84     return ThisEnum::Tv;                                                       \
85   }
86 
87 #define CLAUSET_ENUM_CONVERT(func, OtherE, ThisE, Maps)                        \
88   auto func = [](OtherE v) -> ThisE {                                          \
89     using ThisEnum = ThisE;                                                    \
90     using OtherEnum = OtherE;                                                  \
91     (void)sizeof(OtherEnum); /*Avoid "unused local typedef" warning*/          \
92     Maps;                                                                      \
93     llvm_unreachable("Unexpected value in " #OtherE);                          \
94   }
95 
96 // Usage:
97 //
98 // Given two enums,
99 //   enum class Other { o1, o2 };
100 //   enum class This { t1, t2 };
101 // generate conversion function "Func : Other -> This" with
102 //   CLAUSET_ENUM_CONVERT(
103 //       Func, Other, This,
104 //       CLAUSET_ENUM_MEMBER_CONVERT(o1, t1)      // <- No comma
105 //       CLAUSET_ENUM_MEMBER_CONVERT(o2, t2)
106 //       ...
107 //   )
108 //
109 // Note that the sequence of M(other-value, this-value) is separated
110 // with _spaces_, not commas.
111 
112 namespace detail {
113 // Type trait to determine whether T is a specialization of std::variant.
114 template <typename T> struct is_variant {
115   static constexpr bool value = false;
116 };
117 
118 template <typename... Ts> struct is_variant<std::variant<Ts...>> {
119   static constexpr bool value = true;
120 };
121 
122 template <typename T> constexpr bool is_variant_v = is_variant<T>::value;
123 
124 // Helper utility to create a type which is a union of two given variants.
125 template <typename...> struct UnionOfTwo;
126 
127 template <typename... Types1, typename... Types2>
128 struct UnionOfTwo<std::variant<Types1...>, std::variant<Types2...>> {
129   using type = std::variant<Types1..., Types2...>;
130 };
131 } // namespace detail
132 
133 namespace tomp {
134 namespace type {
135 
136 // Helper utility to create a type which is a union of an arbitrary number
137 // of variants.
138 template <typename...> struct Union;
139 
140 template <> struct Union<> {
141   // Legal to define, illegal to instantiate.
142   using type = std::variant<>;
143 };
144 
145 template <typename T, typename... Ts> struct Union<T, Ts...> {
146   static_assert(detail::is_variant_v<T>);
147   using type =
148       typename detail::UnionOfTwo<T, typename Union<Ts...>::type>::type;
149 };
150 
151 template <typename T> using ListT = llvm::SmallVector<T, 0>;
152 
153 // The ObjectT class represents a variable or a locator (as defined in
154 // the OpenMP spec).
155 // Note: the ObjectT template is not defined. Any user of it is expected to
156 // provide their own specialization that conforms to the requirements listed
157 // below.
158 //
159 // Let ObjectS be any specialization of ObjectT:
160 //
161 // ObjectS must provide the following definitions:
162 // {
163 //    using IdTy = Id;
164 //    using ExprTy = Expr;
165 //
166 //    auto id() const -> IdTy {
167 //      // Return a value such that a.id() == b.id() if and only if:
168 //      // (1) both `a` and `b` represent the same variable or location, or
169 //      // (2) bool(a.id()) == false and bool(b.id()) == false
170 //    }
171 // }
172 //
173 // The type IdTy should be hashable (usable as key in unordered containers).
174 //
175 // Values of type IdTy should be contextually convertible to `bool`.
176 //
177 // If S is an object of type ObjectS, then `bool(S.id())` is `false` if
178 // and only if S does not represent any variable or location.
179 //
180 // ObjectS should be copyable, movable, and default-constructible.
181 template <typename IdType, typename ExprType> struct ObjectT;
182 
183 // By default, object equality is only determined by its identity.
184 template <typename I, typename E>
185 bool operator==(const ObjectT<I, E> &o1, const ObjectT<I, E> &o2) {
186   return o1.id() == o2.id();
187 }
188 
189 template <typename I, typename E> using ObjectListT = ListT<ObjectT<I, E>>;
190 
191 using DirectiveName = llvm::omp::Directive;
192 
193 template <typename I, typename E> //
194 struct DefinedOperatorT {
195   struct DefinedOpName {
196     using WrapperTrait = std::true_type;
197     ObjectT<I, E> v;
198   };
199   ENUM(IntrinsicOperator, Power, Multiply, Divide, Add, Subtract, Concat, LT,
200        LE, EQ, NE, GE, GT, NOT, AND, OR, EQV, NEQV, Min, Max);
201   using UnionTrait = std::true_type;
202   std::variant<DefinedOpName, IntrinsicOperator> u;
203 };
204 
205 // V5.2: [3.2.6] `iterator` modifier
206 template <typename E> //
207 struct RangeT {
208   // range-specification: begin : end[: step]
209   using TupleTrait = std::true_type;
210   std::tuple<E, E, OPT(E)> t;
211 };
212 
213 // V5.2: [3.2.6] `iterator` modifier
214 template <typename TypeType, typename IdType, typename ExprType> //
215 struct IteratorSpecifierT {
216   // iterators-specifier: [ iterator-type ] identifier = range-specification
217   using TupleTrait = std::true_type;
218   std::tuple<OPT(TypeType), ObjectT<IdType, ExprType>, RangeT<ExprType>> t;
219 };
220 
221 // Note:
222 // For motion or map clauses the OpenMP spec allows a unique mapper modifier.
223 // In practice, since these clauses apply to multiple objects, there can be
224 // multiple effective mappers applicable to these objects (due to overloads,
225 // etc.). Because of that store a list of mappers every time a mapper modifier
226 // is allowed. If the mapper list contains a single element, it applies to
227 // all objects in the clause, otherwise there should be as many mappers as
228 // there are objects.
229 // V5.2: [5.8.2] Mapper identifiers and `mapper` modifiers
230 template <typename I, typename E> //
231 struct MapperT {
232   using MapperIdentifier = ObjectT<I, E>;
233   using WrapperTrait = std::true_type;
234   MapperIdentifier v;
235 };
236 
237 // V5.2: [15.8.1] `memory-order` clauses
238 // When used as arguments for other clauses, e.g. `fail`.
239 ENUM(MemoryOrder, AcqRel, Acquire, Relaxed, Release, SeqCst);
240 ENUM(MotionExpectation, Present);
241 // Union of `dependence-type` and `task-depenence-type`.
242 // V5.2: [15.9.1] `task-dependence-type` modifier
243 ENUM(DependenceType, Depobj, In, Inout, Inoutset, Mutexinoutset, Out, Sink,
244      Source);
245 ENUM(Prescriptiveness, Strict);
246 
247 template <typename I, typename E> //
248 struct LoopIterationT {
249   struct Distance {
250     using TupleTrait = std::true_type;
251     std::tuple<DefinedOperatorT<I, E>, E> t;
252   };
253   using TupleTrait = std::true_type;
254   std::tuple<ObjectT<I, E>, OPT(Distance)> t;
255 };
256 
257 template <typename I, typename E> //
258 struct ProcedureDesignatorT {
259   using WrapperTrait = std::true_type;
260   ObjectT<I, E> v;
261 };
262 
263 // Note:
264 // For reduction clauses the OpenMP spec allows a unique reduction identifier.
265 // For reasons analogous to those listed for the MapperT type, clauses that
266 // according to the spec contain a reduction identifier will contain a list of
267 // reduction identifiers. The same constraints apply: there is either a single
268 // identifier that applies to all objects, or there are as many identifiers
269 // as there are objects.
270 template <typename I, typename E> //
271 struct ReductionIdentifierT {
272   using UnionTrait = std::true_type;
273   std::variant<DefinedOperatorT<I, E>, ProcedureDesignatorT<I, E>> u;
274 };
275 
276 template <typename T, typename I, typename E> //
277 using IteratorT = ListT<IteratorSpecifierT<T, I, E>>;
278 
279 template <typename T>
280 std::enable_if_t<T::EmptyTrait::value, bool> operator==(const T &a,
281                                                         const T &b) {
282   return true;
283 }
284 template <typename T>
285 std::enable_if_t<T::IncompleteTrait::value, bool> operator==(const T &a,
286                                                              const T &b) {
287   return true;
288 }
289 template <typename T>
290 std::enable_if_t<T::WrapperTrait::value, bool> operator==(const T &a,
291                                                           const T &b) {
292   return a.v == b.v;
293 }
294 template <typename T>
295 std::enable_if_t<T::TupleTrait::value, bool> operator==(const T &a,
296                                                         const T &b) {
297   return a.t == b.t;
298 }
299 template <typename T>
300 std::enable_if_t<T::UnionTrait::value, bool> operator==(const T &a,
301                                                         const T &b) {
302   return a.u == b.u;
303 }
304 } // namespace type
305 
306 template <typename T> using ListT = type::ListT<T>;
307 
308 template <typename I, typename E> using ObjectT = type::ObjectT<I, E>;
309 template <typename I, typename E> using ObjectListT = type::ObjectListT<I, E>;
310 
311 template <typename T, typename I, typename E>
312 using IteratorT = type::IteratorT<T, I, E>;
313 
314 template <
315     typename ContainerTy, typename FunctionTy,
316     typename ElemTy = typename llvm::remove_cvref_t<ContainerTy>::value_type,
317     typename ResultTy = std::invoke_result_t<FunctionTy, ElemTy>>
318 ListT<ResultTy> makeList(ContainerTy &&container, FunctionTy &&func) {
319   ListT<ResultTy> v;
320   llvm::transform(container, std::back_inserter(v), func);
321   return v;
322 }
323 
324 namespace clause {
325 using type::operator==;
326 
327 // V5.2: [8.3.1] `assumption` clauses
328 template <typename T, typename I, typename E> //
329 struct AbsentT {
330   using List = ListT<type::DirectiveName>;
331   using WrapperTrait = std::true_type;
332   List v;
333 };
334 
335 // V5.2: [15.8.1] `memory-order` clauses
336 template <typename T, typename I, typename E> //
337 struct AcqRelT {
338   using EmptyTrait = std::true_type;
339 };
340 
341 // V5.2: [15.8.1] `memory-order` clauses
342 template <typename T, typename I, typename E> //
343 struct AcquireT {
344   using EmptyTrait = std::true_type;
345 };
346 
347 // V5.2: [7.5.2] `adjust_args` clause
348 template <typename T, typename I, typename E> //
349 struct AdjustArgsT {
350   using IncompleteTrait = std::true_type;
351 };
352 
353 // V5.2: [12.5.1] `affinity` clause
354 template <typename T, typename I, typename E> //
355 struct AffinityT {
356   using Iterator = type::IteratorT<T, I, E>;
357   using LocatorList = ObjectListT<I, E>;
358 
359   using TupleTrait = std::true_type;
360   std::tuple<OPT(Iterator), LocatorList> t;
361 };
362 
363 // V5.2: [6.3] `align` clause
364 template <typename T, typename I, typename E> //
365 struct AlignT {
366   using Alignment = E;
367 
368   using WrapperTrait = std::true_type;
369   Alignment v;
370 };
371 
372 // V5.2: [5.11] `aligned` clause
373 template <typename T, typename I, typename E> //
374 struct AlignedT {
375   using Alignment = E;
376   using List = ObjectListT<I, E>;
377 
378   using TupleTrait = std::true_type;
379   std::tuple<OPT(Alignment), List> t;
380 };
381 
382 template <typename T, typename I, typename E> //
383 struct AllocatorT;
384 
385 // V5.2: [6.6] `allocate` clause
386 template <typename T, typename I, typename E> //
387 struct AllocateT {
388   // AllocatorSimpleModifier is same as AllocatorComplexModifier.
389   using AllocatorComplexModifier = AllocatorT<T, I, E>;
390   using AlignModifier = AlignT<T, I, E>;
391   using List = ObjectListT<I, E>;
392 
393   using TupleTrait = std::true_type;
394   std::tuple<OPT(AllocatorComplexModifier), OPT(AlignModifier), List> t;
395 };
396 
397 // V5.2: [6.4] `allocator` clause
398 template <typename T, typename I, typename E> //
399 struct AllocatorT {
400   using Allocator = E;
401   using WrapperTrait = std::true_type;
402   Allocator v;
403 };
404 
405 // V5.2: [7.5.3] `append_args` clause
406 template <typename T, typename I, typename E> //
407 struct AppendArgsT {
408   using IncompleteTrait = std::true_type;
409 };
410 
411 // V5.2: [8.1] `at` clause
412 template <typename T, typename I, typename E> //
413 struct AtT {
414   ENUM(ActionTime, Compilation, Execution);
415   using WrapperTrait = std::true_type;
416   ActionTime v;
417 };
418 
419 // V5.2: [8.2.1] `requirement` clauses
420 template <typename T, typename I, typename E> //
421 struct AtomicDefaultMemOrderT {
422   using MemoryOrder = type::MemoryOrder;
423   using WrapperTrait = std::true_type;
424   MemoryOrder v; // Name not provided in spec
425 };
426 
427 // V5.2: [11.7.1] `bind` clause
428 template <typename T, typename I, typename E> //
429 struct BindT {
430   ENUM(Binding, Teams, Parallel, Thread);
431   using WrapperTrait = std::true_type;
432   Binding v;
433 };
434 
435 // V5.2: [15.8.3] `extended-atomic` clauses
436 template <typename T, typename I, typename E> //
437 struct CaptureT {
438   using EmptyTrait = std::true_type;
439 };
440 
441 // V5.2: [4.4.3] `collapse` clause
442 template <typename T, typename I, typename E> //
443 struct CollapseT {
444   using N = E;
445   using WrapperTrait = std::true_type;
446   N v;
447 };
448 
449 // V5.2: [15.8.3] `extended-atomic` clauses
450 template <typename T, typename I, typename E> //
451 struct CompareT {
452   using EmptyTrait = std::true_type;
453 };
454 
455 // V5.2: [8.3.1] `assumption` clauses
456 template <typename T, typename I, typename E> //
457 struct ContainsT {
458   using List = ListT<type::DirectiveName>;
459   using WrapperTrait = std::true_type;
460   List v;
461 };
462 
463 // V5.2: [5.7.1] `copyin` clause
464 template <typename T, typename I, typename E> //
465 struct CopyinT {
466   using List = ObjectListT<I, E>;
467   using WrapperTrait = std::true_type;
468   List v;
469 };
470 
471 // V5.2: [5.7.2] `copyprivate` clause
472 template <typename T, typename I, typename E> //
473 struct CopyprivateT {
474   using List = ObjectListT<I, E>;
475   using WrapperTrait = std::true_type;
476   List v;
477 };
478 
479 // V5.2: [5.4.1] `default` clause
480 template <typename T, typename I, typename E> //
481 struct DefaultT {
482   ENUM(DataSharingAttribute, Firstprivate, None, Private, Shared);
483   using WrapperTrait = std::true_type;
484   DataSharingAttribute v;
485 };
486 
487 // V5.2: [5.8.7] `defaultmap` clause
488 template <typename T, typename I, typename E> //
489 struct DefaultmapT {
490   ENUM(ImplicitBehavior, Alloc, To, From, Tofrom, Firstprivate, None, Default,
491        Present);
492   ENUM(VariableCategory, All, Scalar, Aggregate, Pointer, Allocatable);
493   using TupleTrait = std::true_type;
494   std::tuple<ImplicitBehavior, OPT(VariableCategory)> t;
495 };
496 
497 template <typename T, typename I, typename E> //
498 struct DoacrossT;
499 
500 // V5.2: [15.9.5] `depend` clause
501 template <typename T, typename I, typename E> //
502 struct DependT {
503   using Iterator = type::IteratorT<T, I, E>;
504   using LocatorList = ObjectListT<I, E>;
505   using DependenceType = tomp::type::DependenceType;
506 
507   struct TaskDep { // The form with task dependence type.
508     using TupleTrait = std::true_type;
509     // Empty LocatorList means "omp_all_memory".
510     std::tuple<DependenceType, OPT(Iterator), LocatorList> t;
511   };
512 
513   using Doacross = DoacrossT<T, I, E>;
514   using UnionTrait = std::true_type;
515   std::variant<Doacross, TaskDep> u; // Doacross form is legacy
516 };
517 
518 // V5.2: [3.5] `destroy` clause
519 template <typename T, typename I, typename E> //
520 struct DestroyT {
521   using DestroyVar = ObjectT<I, E>;
522   using WrapperTrait = std::true_type;
523   // DestroyVar can be ommitted in "depobj destroy".
524   OPT(DestroyVar) v;
525 };
526 
527 // V5.2: [12.5.2] `detach` clause
528 template <typename T, typename I, typename E> //
529 struct DetachT {
530   using EventHandle = ObjectT<I, E>;
531   using WrapperTrait = std::true_type;
532   EventHandle v;
533 };
534 
535 // V5.2: [13.2] `device` clause
536 template <typename T, typename I, typename E> //
537 struct DeviceT {
538   using DeviceDescription = E;
539   ENUM(DeviceModifier, Ancestor, DeviceNum);
540   using TupleTrait = std::true_type;
541   std::tuple<OPT(DeviceModifier), DeviceDescription> t;
542 };
543 
544 // V5.2: [13.1] `device_type` clause
545 template <typename T, typename I, typename E> //
546 struct DeviceTypeT {
547   ENUM(DeviceTypeDescription, Any, Host, Nohost);
548   using WrapperTrait = std::true_type;
549   DeviceTypeDescription v;
550 };
551 
552 // V5.2: [11.6.1] `dist_schedule` clause
553 template <typename T, typename I, typename E> //
554 struct DistScheduleT {
555   ENUM(Kind, Static);
556   using ChunkSize = E;
557   using TupleTrait = std::true_type;
558   std::tuple<Kind, OPT(ChunkSize)> t;
559 };
560 
561 // V5.2: [15.9.6] `doacross` clause
562 template <typename T, typename I, typename E> //
563 struct DoacrossT {
564   using Vector = ListT<type::LoopIterationT<I, E>>;
565   using DependenceType = tomp::type::DependenceType;
566   using TupleTrait = std::true_type;
567   // Empty Vector means "omp_cur_iteration"
568   std::tuple<DependenceType, Vector> t;
569 };
570 
571 // V5.2: [8.2.1] `requirement` clauses
572 template <typename T, typename I, typename E> //
573 struct DynamicAllocatorsT {
574   using EmptyTrait = std::true_type;
575 };
576 
577 // V5.2: [5.8.4] `enter` clause
578 template <typename T, typename I, typename E> //
579 struct EnterT {
580   using List = ObjectListT<I, E>;
581   using WrapperTrait = std::true_type;
582   List v;
583 };
584 
585 // V5.2: [5.6.2] `exclusive` clause
586 template <typename T, typename I, typename E> //
587 struct ExclusiveT {
588   using WrapperTrait = std::true_type;
589   using List = ObjectListT<I, E>;
590   List v;
591 };
592 
593 // V5.2: [15.8.3] `extended-atomic` clauses
594 template <typename T, typename I, typename E> //
595 struct FailT {
596   using MemoryOrder = type::MemoryOrder;
597   using WrapperTrait = std::true_type;
598   MemoryOrder v;
599 };
600 
601 // V5.2: [10.5.1] `filter` clause
602 template <typename T, typename I, typename E> //
603 struct FilterT {
604   using ThreadNum = E;
605   using WrapperTrait = std::true_type;
606   ThreadNum v;
607 };
608 
609 // V5.2: [12.3] `final` clause
610 template <typename T, typename I, typename E> //
611 struct FinalT {
612   using Finalize = E;
613   using WrapperTrait = std::true_type;
614   Finalize v;
615 };
616 
617 // V5.2: [5.4.4] `firstprivate` clause
618 template <typename T, typename I, typename E> //
619 struct FirstprivateT {
620   using List = ObjectListT<I, E>;
621   using WrapperTrait = std::true_type;
622   List v;
623 };
624 
625 // V5.2: [5.9.2] `from` clause
626 template <typename T, typename I, typename E> //
627 struct FromT {
628   using LocatorList = ObjectListT<I, E>;
629   using Expectation = type::MotionExpectation;
630   using Iterator = type::IteratorT<T, I, E>;
631   // See note at the definition of the MapperT type.
632   using Mappers = ListT<type::MapperT<I, E>>; // Not a spec name
633 
634   using TupleTrait = std::true_type;
635   std::tuple<OPT(Expectation), OPT(Mappers), OPT(Iterator), LocatorList> t;
636 };
637 
638 // V5.2: [9.2.1] `full` clause
639 template <typename T, typename I, typename E> //
640 struct FullT {
641   using EmptyTrait = std::true_type;
642 };
643 
644 // V5.2: [12.6.1] `grainsize` clause
645 template <typename T, typename I, typename E> //
646 struct GrainsizeT {
647   using Prescriptiveness = type::Prescriptiveness;
648   using GrainSize = E;
649   using TupleTrait = std::true_type;
650   std::tuple<OPT(Prescriptiveness), GrainSize> t;
651 };
652 
653 // V5.2: [5.4.9] `has_device_addr` clause
654 template <typename T, typename I, typename E> //
655 struct HasDeviceAddrT {
656   using List = ObjectListT<I, E>;
657   using WrapperTrait = std::true_type;
658   List v;
659 };
660 
661 // V5.2: [15.1.2] `hint` clause
662 template <typename T, typename I, typename E> //
663 struct HintT {
664   using HintExpr = E;
665   using WrapperTrait = std::true_type;
666   HintExpr v;
667 };
668 
669 // V5.2: [8.3.1] Assumption clauses
670 template <typename T, typename I, typename E> //
671 struct HoldsT {
672   using WrapperTrait = std::true_type;
673   E v; // No argument name in spec 5.2
674 };
675 
676 // V5.2: [3.4] `if` clause
677 template <typename T, typename I, typename E> //
678 struct IfT {
679   using DirectiveNameModifier = type::DirectiveName;
680   using IfExpression = E;
681   using TupleTrait = std::true_type;
682   std::tuple<OPT(DirectiveNameModifier), IfExpression> t;
683 };
684 
685 // V5.2: [7.7.1] `branch` clauses
686 template <typename T, typename I, typename E> //
687 struct InbranchT {
688   using EmptyTrait = std::true_type;
689 };
690 
691 // V5.2: [5.6.1] `exclusive` clause
692 template <typename T, typename I, typename E> //
693 struct InclusiveT {
694   using List = ObjectListT<I, E>;
695   using WrapperTrait = std::true_type;
696   List v;
697 };
698 
699 // V5.2: [7.8.3] `indirect` clause
700 template <typename T, typename I, typename E> //
701 struct IndirectT {
702   using InvokedByFptr = E;
703   using WrapperTrait = std::true_type;
704   InvokedByFptr v;
705 };
706 
707 // V5.2: [14.1.2] `init` clause
708 template <typename T, typename I, typename E> //
709 struct InitT {
710   using ForeignRuntimeId = E;
711   using InteropVar = ObjectT<I, E>;
712   using InteropPreference = ListT<ForeignRuntimeId>;
713   ENUM(InteropType, Target, Targetsync);   // Repeatable
714   using InteropTypes = ListT<InteropType>; // Not a spec name
715 
716   using TupleTrait = std::true_type;
717   std::tuple<OPT(InteropPreference), InteropTypes, InteropVar> t;
718 };
719 
720 // V5.2: [5.5.4] `initializer` clause
721 template <typename T, typename I, typename E> //
722 struct InitializerT {
723   using InitializerExpr = E;
724   using WrapperTrait = std::true_type;
725   InitializerExpr v;
726 };
727 
728 // V5.2: [5.5.10] `in_reduction` clause
729 template <typename T, typename I, typename E> //
730 struct InReductionT {
731   using List = ObjectListT<I, E>;
732   // See note at the definition of the ReductionIdentifierT type.
733   // The name ReductionIdentifiers is not a spec name.
734   using ReductionIdentifiers = ListT<type::ReductionIdentifierT<I, E>>;
735   using TupleTrait = std::true_type;
736   std::tuple<ReductionIdentifiers, List> t;
737 };
738 
739 // V5.2: [5.4.7] `is_device_ptr` clause
740 template <typename T, typename I, typename E> //
741 struct IsDevicePtrT {
742   using List = ObjectListT<I, E>;
743   using WrapperTrait = std::true_type;
744   List v;
745 };
746 
747 // V5.2: [5.4.5] `lastprivate` clause
748 template <typename T, typename I, typename E> //
749 struct LastprivateT {
750   using List = ObjectListT<I, E>;
751   ENUM(LastprivateModifier, Conditional);
752   using TupleTrait = std::true_type;
753   std::tuple<OPT(LastprivateModifier), List> t;
754 };
755 
756 // V5.2: [5.4.6] `linear` clause
757 template <typename T, typename I, typename E> //
758 struct LinearT {
759   // std::get<type> won't work here due to duplicate types in the tuple.
760   using List = ObjectListT<I, E>;
761   // StepSimpleModifier is same as StepComplexModifier.
762   using StepComplexModifier = E;
763   ENUM(LinearModifier, Ref, Val, Uval);
764 
765   using TupleTrait = std::true_type;
766   // Step == nullopt means 1.
767   std::tuple<OPT(StepComplexModifier), OPT(LinearModifier), List> t;
768 };
769 
770 // V5.2: [5.8.5] `link` clause
771 template <typename T, typename I, typename E> //
772 struct LinkT {
773   using List = ObjectListT<I, E>;
774   using WrapperTrait = std::true_type;
775   List v;
776 };
777 
778 // V5.2: [5.8.3] `map` clause
779 template <typename T, typename I, typename E> //
780 struct MapT {
781   using LocatorList = ObjectListT<I, E>;
782   ENUM(MapType, To, From, Tofrom, Alloc, Release, Delete);
783   ENUM(MapTypeModifier, Always, Close, Present, OmpxHold);
784   // See note at the definition of the MapperT type.
785   using Mappers = ListT<type::MapperT<I, E>>; // Not a spec name
786   using Iterator = type::IteratorT<T, I, E>;
787   using MapTypeModifiers = ListT<MapTypeModifier>; // Not a spec name
788 
789   using TupleTrait = std::true_type;
790   std::tuple<OPT(MapType), OPT(MapTypeModifiers), OPT(Mappers), OPT(Iterator),
791              LocatorList>
792       t;
793 };
794 
795 // V5.2: [7.5.1] `match` clause
796 template <typename T, typename I, typename E> //
797 struct MatchT {
798   using IncompleteTrait = std::true_type;
799 };
800 
801 // V5.2: [12.2] `mergeable` clause
802 template <typename T, typename I, typename E> //
803 struct MergeableT {
804   using EmptyTrait = std::true_type;
805 };
806 
807 // V5.2: [8.5.2] `message` clause
808 template <typename T, typename I, typename E> //
809 struct MessageT {
810   using MsgString = E;
811   using WrapperTrait = std::true_type;
812   MsgString v;
813 };
814 
815 // V5.2: [7.6.2] `nocontext` clause
816 template <typename T, typename I, typename E> //
817 struct NocontextT {
818   using DoNotUpdateContext = E;
819   using WrapperTrait = std::true_type;
820   DoNotUpdateContext v;
821 };
822 
823 // V5.2: [15.7] `nowait` clause
824 template <typename T, typename I, typename E> //
825 struct NogroupT {
826   using EmptyTrait = std::true_type;
827 };
828 
829 // V5.2: [10.4.1] `nontemporal` clause
830 template <typename T, typename I, typename E> //
831 struct NontemporalT {
832   using List = ObjectListT<I, E>;
833   using WrapperTrait = std::true_type;
834   List v;
835 };
836 
837 // V5.2: [8.3.1] `assumption` clauses
838 template <typename T, typename I, typename E> //
839 struct NoOpenmpT {
840   using EmptyTrait = std::true_type;
841 };
842 
843 // V5.2: [8.3.1] `assumption` clauses
844 template <typename T, typename I, typename E> //
845 struct NoOpenmpRoutinesT {
846   using EmptyTrait = std::true_type;
847 };
848 
849 // V5.2: [8.3.1] `assumption` clauses
850 template <typename T, typename I, typename E> //
851 struct NoParallelismT {
852   using EmptyTrait = std::true_type;
853 };
854 
855 // V5.2: [7.7.1] `branch` clauses
856 template <typename T, typename I, typename E> //
857 struct NotinbranchT {
858   using EmptyTrait = std::true_type;
859 };
860 
861 // V5.2: [7.6.1] `novariants` clause
862 template <typename T, typename I, typename E> //
863 struct NovariantsT {
864   using DoNotUseVariant = E;
865   using WrapperTrait = std::true_type;
866   DoNotUseVariant v;
867 };
868 
869 // V5.2: [15.6] `nowait` clause
870 template <typename T, typename I, typename E> //
871 struct NowaitT {
872   using EmptyTrait = std::true_type;
873 };
874 
875 // V5.2: [12.6.2] `num_tasks` clause
876 template <typename T, typename I, typename E> //
877 struct NumTasksT {
878   using Prescriptiveness = type::Prescriptiveness;
879   using NumTasks = E;
880   using TupleTrait = std::true_type;
881   std::tuple<OPT(Prescriptiveness), NumTasks> t;
882 };
883 
884 // V5.2: [10.2.1] `num_teams` clause
885 template <typename T, typename I, typename E> //
886 struct NumTeamsT {
887   using LowerBound = E;
888   using UpperBound = E;
889 
890   // The name Range is not a spec name.
891   struct Range {
892     using TupleTrait = std::true_type;
893     std::tuple<OPT(LowerBound), UpperBound> t;
894   };
895 
896   // The name List is not a spec name. The list is an extension to allow
897   // specifying a grid with connection with the ompx_bare clause.
898   using List = ListT<Range>;
899   using WrapperTrait = std::true_type;
900   List v;
901 };
902 
903 // V5.2: [10.1.2] `num_threads` clause
904 template <typename T, typename I, typename E> //
905 struct NumThreadsT {
906   using Nthreads = E;
907   using WrapperTrait = std::true_type;
908   Nthreads v;
909 };
910 
911 template <typename T, typename I, typename E> //
912 struct OmpxAttributeT {
913   using EmptyTrait = std::true_type;
914 };
915 
916 template <typename T, typename I, typename E> //
917 struct OmpxBareT {
918   using EmptyTrait = std::true_type;
919 };
920 
921 template <typename T, typename I, typename E> //
922 struct OmpxDynCgroupMemT {
923   using WrapperTrait = std::true_type;
924   E v;
925 };
926 
927 // V5.2: [10.3] `order` clause
928 template <typename T, typename I, typename E> //
929 struct OrderT {
930   ENUM(OrderModifier, Reproducible, Unconstrained);
931   ENUM(Ordering, Concurrent);
932   using TupleTrait = std::true_type;
933   std::tuple<OPT(OrderModifier), Ordering> t;
934 };
935 
936 // V5.2: [4.4.4] `ordered` clause
937 template <typename T, typename I, typename E> //
938 struct OrderedT {
939   using N = E;
940   using WrapperTrait = std::true_type;
941   OPT(N) v;
942 };
943 
944 // V5.2: [7.4.2] `otherwise` clause
945 template <typename T, typename I, typename E> //
946 struct OtherwiseT {
947   using IncompleteTrait = std::true_type;
948 };
949 
950 // V5.2: [9.2.2] `partial` clause
951 template <typename T, typename I, typename E> //
952 struct PartialT {
953   using UnrollFactor = E;
954   using WrapperTrait = std::true_type;
955   OPT(UnrollFactor) v;
956 };
957 
958 // V6.0:  `permutation` clause
959 template <typename T, typename I, typename E> //
960 struct PermutationT {
961   using ArgList = ListT<E>;
962   using WrapperTrait = std::true_type;
963   ArgList v;
964 };
965 
966 // V5.2: [12.4] `priority` clause
967 template <typename T, typename I, typename E> //
968 struct PriorityT {
969   using PriorityValue = E;
970   using WrapperTrait = std::true_type;
971   PriorityValue v;
972 };
973 
974 // V5.2: [5.4.3] `private` clause
975 template <typename T, typename I, typename E> //
976 struct PrivateT {
977   using List = ObjectListT<I, E>;
978   using WrapperTrait = std::true_type;
979   List v;
980 };
981 
982 // V5.2: [10.1.4] `proc_bind` clause
983 template <typename T, typename I, typename E> //
984 struct ProcBindT {
985   ENUM(AffinityPolicy, Close, Master, Spread, Primary);
986   using WrapperTrait = std::true_type;
987   AffinityPolicy v;
988 };
989 
990 // V5.2: [15.8.2] Atomic clauses
991 template <typename T, typename I, typename E> //
992 struct ReadT {
993   using EmptyTrait = std::true_type;
994 };
995 
996 // V5.2: [5.5.8] `reduction` clause
997 template <typename T, typename I, typename E> //
998 struct ReductionT {
999   using List = ObjectListT<I, E>;
1000   // See note at the definition of the ReductionIdentifierT type.
1001   // The name ReductionIdentifiers is not a spec name.
1002   using ReductionIdentifiers = ListT<type::ReductionIdentifierT<I, E>>;
1003   ENUM(ReductionModifier, Default, Inscan, Task);
1004   using TupleTrait = std::true_type;
1005   std::tuple<OPT(ReductionModifier), ReductionIdentifiers, List> t;
1006 };
1007 
1008 // V5.2: [15.8.1] `memory-order` clauses
1009 template <typename T, typename I, typename E> //
1010 struct RelaxedT {
1011   using EmptyTrait = std::true_type;
1012 };
1013 
1014 // V5.2: [15.8.1] `memory-order` clauses
1015 template <typename T, typename I, typename E> //
1016 struct ReleaseT {
1017   using EmptyTrait = std::true_type;
1018 };
1019 
1020 // V5.2: [8.2.1] `requirement` clauses
1021 template <typename T, typename I, typename E> //
1022 struct ReverseOffloadT {
1023   using EmptyTrait = std::true_type;
1024 };
1025 
1026 // V5.2: [10.4.2] `safelen` clause
1027 template <typename T, typename I, typename E> //
1028 struct SafelenT {
1029   using Length = E;
1030   using WrapperTrait = std::true_type;
1031   Length v;
1032 };
1033 
1034 // V5.2: [11.5.3] `schedule` clause
1035 template <typename T, typename I, typename E> //
1036 struct ScheduleT {
1037   ENUM(Kind, Static, Dynamic, Guided, Auto, Runtime);
1038   using ChunkSize = E;
1039   ENUM(OrderingModifier, Monotonic, Nonmonotonic);
1040   ENUM(ChunkModifier, Simd);
1041   using TupleTrait = std::true_type;
1042   std::tuple<Kind, OPT(OrderingModifier), OPT(ChunkModifier), OPT(ChunkSize)> t;
1043 };
1044 
1045 // V5.2: [15.8.1] Memory-order clauses
1046 template <typename T, typename I, typename E> //
1047 struct SeqCstT {
1048   using EmptyTrait = std::true_type;
1049 };
1050 
1051 // V5.2: [8.5.1] `severity` clause
1052 template <typename T, typename I, typename E> //
1053 struct SeverityT {
1054   ENUM(SevLevel, Fatal, Warning);
1055   using WrapperTrait = std::true_type;
1056   SevLevel v;
1057 };
1058 
1059 // V5.2: [5.4.2] `shared` clause
1060 template <typename T, typename I, typename E> //
1061 struct SharedT {
1062   using List = ObjectListT<I, E>;
1063   using WrapperTrait = std::true_type;
1064   List v;
1065 };
1066 
1067 // V5.2: [15.10.3] `parallelization-level` clauses
1068 template <typename T, typename I, typename E> //
1069 struct SimdT {
1070   using EmptyTrait = std::true_type;
1071 };
1072 
1073 // V5.2: [10.4.3] `simdlen` clause
1074 template <typename T, typename I, typename E> //
1075 struct SimdlenT {
1076   using Length = E;
1077   using WrapperTrait = std::true_type;
1078   Length v;
1079 };
1080 
1081 // V5.2: [9.1.1] `sizes` clause
1082 template <typename T, typename I, typename E> //
1083 struct SizesT {
1084   using SizeList = ListT<E>;
1085   using WrapperTrait = std::true_type;
1086   SizeList v;
1087 };
1088 
1089 // V5.2: [5.5.9] `task_reduction` clause
1090 template <typename T, typename I, typename E> //
1091 struct TaskReductionT {
1092   using List = ObjectListT<I, E>;
1093   // See note at the definition of the ReductionIdentifierT type.
1094   // The name ReductionIdentifiers is not a spec name.
1095   using ReductionIdentifiers = ListT<type::ReductionIdentifierT<I, E>>;
1096   using TupleTrait = std::true_type;
1097   std::tuple<ReductionIdentifiers, List> t;
1098 };
1099 
1100 // V5.2: [13.3] `thread_limit` clause
1101 template <typename T, typename I, typename E> //
1102 struct ThreadLimitT {
1103   using Threadlim = E;
1104   using WrapperTrait = std::true_type;
1105   Threadlim v;
1106 };
1107 
1108 // V5.2: [15.10.3] `parallelization-level` clauses
1109 template <typename T, typename I, typename E> //
1110 struct ThreadsT {
1111   using EmptyTrait = std::true_type;
1112 };
1113 
1114 // V5.2: [5.9.1] `to` clause
1115 template <typename T, typename I, typename E> //
1116 struct ToT {
1117   using LocatorList = ObjectListT<I, E>;
1118   using Expectation = type::MotionExpectation;
1119   // See note at the definition of the MapperT type.
1120   using Mappers = ListT<type::MapperT<I, E>>; // Not a spec name
1121   using Iterator = type::IteratorT<T, I, E>;
1122 
1123   using TupleTrait = std::true_type;
1124   std::tuple<OPT(Expectation), OPT(Mappers), OPT(Iterator), LocatorList> t;
1125 };
1126 
1127 // V5.2: [8.2.1] `requirement` clauses
1128 template <typename T, typename I, typename E> //
1129 struct UnifiedAddressT {
1130   using EmptyTrait = std::true_type;
1131 };
1132 
1133 // V5.2: [8.2.1] `requirement` clauses
1134 template <typename T, typename I, typename E> //
1135 struct UnifiedSharedMemoryT {
1136   using EmptyTrait = std::true_type;
1137 };
1138 
1139 // V5.2: [5.10] `uniform` clause
1140 template <typename T, typename I, typename E> //
1141 struct UniformT {
1142   using ParameterList = ObjectListT<I, E>;
1143   using WrapperTrait = std::true_type;
1144   ParameterList v;
1145 };
1146 
1147 template <typename T, typename I, typename E> //
1148 struct UnknownT {
1149   using EmptyTrait = std::true_type;
1150 };
1151 
1152 // V5.2: [12.1] `untied` clause
1153 template <typename T, typename I, typename E> //
1154 struct UntiedT {
1155   using EmptyTrait = std::true_type;
1156 };
1157 
1158 // Both of the following
1159 // V5.2: [15.8.2] `atomic` clauses
1160 // V5.2: [15.9.3] `update` clause
1161 template <typename T, typename I, typename E> //
1162 struct UpdateT {
1163   using DependenceType = tomp::type::DependenceType;
1164   using WrapperTrait = std::true_type;
1165   OPT(DependenceType) v;
1166 };
1167 
1168 // V5.2: [14.1.3] `use` clause
1169 template <typename T, typename I, typename E> //
1170 struct UseT {
1171   using InteropVar = ObjectT<I, E>;
1172   using WrapperTrait = std::true_type;
1173   InteropVar v;
1174 };
1175 
1176 // V5.2: [5.4.10] `use_device_addr` clause
1177 template <typename T, typename I, typename E> //
1178 struct UseDeviceAddrT {
1179   using List = ObjectListT<I, E>;
1180   using WrapperTrait = std::true_type;
1181   List v;
1182 };
1183 
1184 // V5.2: [5.4.8] `use_device_ptr` clause
1185 template <typename T, typename I, typename E> //
1186 struct UseDevicePtrT {
1187   using List = ObjectListT<I, E>;
1188   using WrapperTrait = std::true_type;
1189   List v;
1190 };
1191 
1192 // V5.2: [6.8] `uses_allocators` clause
1193 template <typename T, typename I, typename E> //
1194 struct UsesAllocatorsT {
1195   using MemSpace = E;
1196   using TraitsArray = ObjectT<I, E>;
1197   using Allocator = E;
1198   struct AllocatorSpec { // Not a spec name
1199     using TupleTrait = std::true_type;
1200     std::tuple<OPT(MemSpace), OPT(TraitsArray), Allocator> t;
1201   };
1202   using Allocators = ListT<AllocatorSpec>; // Not a spec name
1203   using WrapperTrait = std::true_type;
1204   Allocators v;
1205 };
1206 
1207 // V5.2: [15.8.3] `extended-atomic` clauses
1208 template <typename T, typename I, typename E> //
1209 struct WeakT {
1210   using EmptyTrait = std::true_type;
1211 };
1212 
1213 // V5.2: [7.4.1] `when` clause
1214 template <typename T, typename I, typename E> //
1215 struct WhenT {
1216   using IncompleteTrait = std::true_type;
1217 };
1218 
1219 // V5.2: [15.8.2] Atomic clauses
1220 template <typename T, typename I, typename E> //
1221 struct WriteT {
1222   using EmptyTrait = std::true_type;
1223 };
1224 
1225 // ---
1226 
1227 template <typename T, typename I, typename E>
1228 using ExtensionClausesT =
1229     std::variant<OmpxAttributeT<T, I, E>, OmpxBareT<T, I, E>,
1230                  OmpxDynCgroupMemT<T, I, E>>;
1231 
1232 template <typename T, typename I, typename E>
1233 using EmptyClausesT = std::variant<
1234     AcqRelT<T, I, E>, AcquireT<T, I, E>, CaptureT<T, I, E>, CompareT<T, I, E>,
1235     DynamicAllocatorsT<T, I, E>, FullT<T, I, E>, InbranchT<T, I, E>,
1236     MergeableT<T, I, E>, NogroupT<T, I, E>, NoOpenmpRoutinesT<T, I, E>,
1237     NoOpenmpT<T, I, E>, NoParallelismT<T, I, E>, NotinbranchT<T, I, E>,
1238     NowaitT<T, I, E>, ReadT<T, I, E>, RelaxedT<T, I, E>, ReleaseT<T, I, E>,
1239     ReverseOffloadT<T, I, E>, SeqCstT<T, I, E>, SimdT<T, I, E>,
1240     ThreadsT<T, I, E>, UnifiedAddressT<T, I, E>, UnifiedSharedMemoryT<T, I, E>,
1241     UnknownT<T, I, E>, UntiedT<T, I, E>, UseT<T, I, E>, WeakT<T, I, E>,
1242     WriteT<T, I, E>>;
1243 
1244 template <typename T, typename I, typename E>
1245 using IncompleteClausesT =
1246     std::variant<AdjustArgsT<T, I, E>, AppendArgsT<T, I, E>, MatchT<T, I, E>,
1247                  OtherwiseT<T, I, E>, WhenT<T, I, E>>;
1248 
1249 template <typename T, typename I, typename E>
1250 using TupleClausesT =
1251     std::variant<AffinityT<T, I, E>, AlignedT<T, I, E>, AllocateT<T, I, E>,
1252                  DefaultmapT<T, I, E>, DeviceT<T, I, E>, DistScheduleT<T, I, E>,
1253                  DoacrossT<T, I, E>, FromT<T, I, E>, GrainsizeT<T, I, E>,
1254                  IfT<T, I, E>, InitT<T, I, E>, InReductionT<T, I, E>,
1255                  LastprivateT<T, I, E>, LinearT<T, I, E>, MapT<T, I, E>,
1256                  NumTasksT<T, I, E>, OrderT<T, I, E>, ReductionT<T, I, E>,
1257                  ScheduleT<T, I, E>, TaskReductionT<T, I, E>, ToT<T, I, E>>;
1258 
1259 template <typename T, typename I, typename E>
1260 using UnionClausesT = std::variant<DependT<T, I, E>>;
1261 
1262 template <typename T, typename I, typename E>
1263 using WrapperClausesT = std::variant<
1264     AbsentT<T, I, E>, AlignT<T, I, E>, AllocatorT<T, I, E>,
1265     AtomicDefaultMemOrderT<T, I, E>, AtT<T, I, E>, BindT<T, I, E>,
1266     CollapseT<T, I, E>, ContainsT<T, I, E>, CopyinT<T, I, E>,
1267     CopyprivateT<T, I, E>, DefaultT<T, I, E>, DestroyT<T, I, E>,
1268     DetachT<T, I, E>, DeviceTypeT<T, I, E>, EnterT<T, I, E>,
1269     ExclusiveT<T, I, E>, FailT<T, I, E>, FilterT<T, I, E>, FinalT<T, I, E>,
1270     FirstprivateT<T, I, E>, HasDeviceAddrT<T, I, E>, HintT<T, I, E>,
1271     HoldsT<T, I, E>, InclusiveT<T, I, E>, IndirectT<T, I, E>,
1272     InitializerT<T, I, E>, IsDevicePtrT<T, I, E>, LinkT<T, I, E>,
1273     MessageT<T, I, E>, NocontextT<T, I, E>, NontemporalT<T, I, E>,
1274     NovariantsT<T, I, E>, NumTeamsT<T, I, E>, NumThreadsT<T, I, E>,
1275     OrderedT<T, I, E>, PartialT<T, I, E>, PriorityT<T, I, E>, PrivateT<T, I, E>,
1276     ProcBindT<T, I, E>, SafelenT<T, I, E>, SeverityT<T, I, E>, SharedT<T, I, E>,
1277     SimdlenT<T, I, E>, SizesT<T, I, E>, PermutationT<T, I, E>,
1278     ThreadLimitT<T, I, E>, UniformT<T, I, E>, UpdateT<T, I, E>,
1279     UseDeviceAddrT<T, I, E>, UseDevicePtrT<T, I, E>, UsesAllocatorsT<T, I, E>>;
1280 
1281 template <typename T, typename I, typename E>
1282 using UnionOfAllClausesT = typename type::Union< //
1283     EmptyClausesT<T, I, E>,                      //
1284     ExtensionClausesT<T, I, E>,                  //
1285     IncompleteClausesT<T, I, E>,                 //
1286     TupleClausesT<T, I, E>,                      //
1287     UnionClausesT<T, I, E>,                      //
1288     WrapperClausesT<T, I, E>                     //
1289     >::type;
1290 } // namespace clause
1291 
1292 using type::operator==;
1293 
1294 // The variant wrapper that encapsulates all possible specific clauses.
1295 // The `Extras` arguments are additional types representing local extensions
1296 // to the clause set, e.g.
1297 //
1298 // using Clause = ClauseT<Type, Id, Expr,
1299 //                        MyClause1, MyClause2>;
1300 //
1301 // The member Clause::u will be a variant containing all specific clauses
1302 // defined above, plus MyClause1 and MyClause2.
1303 //
1304 // Note: Any derived class must be constructible from the base class
1305 // ClauseT<...>.
1306 template <typename TypeType, typename IdType, typename ExprType,
1307           typename... Extras>
1308 struct ClauseT {
1309   using TypeTy = TypeType;
1310   using IdTy = IdType;
1311   using ExprTy = ExprType;
1312 
1313   // Type of "self" to specify this type given a derived class type.
1314   using BaseT = ClauseT<TypeType, IdType, ExprType, Extras...>;
1315 
1316   using VariantTy = typename type::Union<
1317       clause::UnionOfAllClausesT<TypeType, IdType, ExprType>,
1318       std::variant<Extras...>>::type;
1319 
1320   llvm::omp::Clause id; // The numeric id of the clause
1321   using UnionTrait = std::true_type;
1322   VariantTy u;
1323 };
1324 
1325 template <typename ClauseType> struct DirectiveWithClauses {
1326   llvm::omp::Directive id = llvm::omp::Directive::OMPD_unknown;
1327   tomp::type::ListT<ClauseType> clauses;
1328 };
1329 
1330 } // namespace tomp
1331 
1332 #undef OPT
1333 #undef ENUM
1334 
1335 #endif // LLVM_FRONTEND_OPENMP_CLAUSET_H
1336