xref: /freebsd/contrib/llvm-project/llvm/include/llvm/Frontend/OpenMP/ClauseT.h (revision 0fca6ea1d4eea4c934cfff25ac9ee8ad6fe95583)
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 // V5.2: [15.9.1] `task-dependence-type` modifier
242 ENUM(TaskDependenceType, In, Out, Inout, Mutexinoutset, Inoutset, Depobj);
243 
244 template <typename I, typename E> //
245 struct LoopIterationT {
246   struct Distance {
247     using TupleTrait = std::true_type;
248     std::tuple<DefinedOperatorT<I, E>, E> t;
249   };
250   using TupleTrait = std::true_type;
251   std::tuple<ObjectT<I, E>, OPT(Distance)> t;
252 };
253 
254 template <typename I, typename E> //
255 struct ProcedureDesignatorT {
256   using WrapperTrait = std::true_type;
257   ObjectT<I, E> v;
258 };
259 
260 // Note:
261 // For reduction clauses the OpenMP spec allows a unique reduction identifier.
262 // For reasons analogous to those listed for the MapperT type, clauses that
263 // according to the spec contain a reduction identifier will contain a list of
264 // reduction identifiers. The same constraints apply: there is either a single
265 // identifier that applies to all objects, or there are as many identifiers
266 // as there are objects.
267 template <typename I, typename E> //
268 struct ReductionIdentifierT {
269   using UnionTrait = std::true_type;
270   std::variant<DefinedOperatorT<I, E>, ProcedureDesignatorT<I, E>> u;
271 };
272 
273 template <typename T, typename I, typename E> //
274 using IteratorT = ListT<IteratorSpecifierT<T, I, E>>;
275 
276 template <typename T>
277 std::enable_if_t<T::EmptyTrait::value, bool> operator==(const T &a,
278                                                         const T &b) {
279   return true;
280 }
281 template <typename T>
282 std::enable_if_t<T::IncompleteTrait::value, bool> operator==(const T &a,
283                                                              const T &b) {
284   return true;
285 }
286 template <typename T>
287 std::enable_if_t<T::WrapperTrait::value, bool> operator==(const T &a,
288                                                           const T &b) {
289   return a.v == b.v;
290 }
291 template <typename T>
292 std::enable_if_t<T::TupleTrait::value, bool> operator==(const T &a,
293                                                         const T &b) {
294   return a.t == b.t;
295 }
296 template <typename T>
297 std::enable_if_t<T::UnionTrait::value, bool> operator==(const T &a,
298                                                         const T &b) {
299   return a.u == b.u;
300 }
301 } // namespace type
302 
303 template <typename T> using ListT = type::ListT<T>;
304 
305 template <typename I, typename E> using ObjectT = type::ObjectT<I, E>;
306 template <typename I, typename E> using ObjectListT = type::ObjectListT<I, E>;
307 
308 template <typename T, typename I, typename E>
309 using IteratorT = type::IteratorT<T, I, E>;
310 
311 template <
312     typename ContainerTy, typename FunctionTy,
313     typename ElemTy = typename llvm::remove_cvref_t<ContainerTy>::value_type,
314     typename ResultTy = std::invoke_result_t<FunctionTy, ElemTy>>
315 ListT<ResultTy> makeList(ContainerTy &&container, FunctionTy &&func) {
316   ListT<ResultTy> v;
317   llvm::transform(container, std::back_inserter(v), func);
318   return v;
319 }
320 
321 namespace clause {
322 using type::operator==;
323 
324 // V5.2: [8.3.1] `assumption` clauses
325 template <typename T, typename I, typename E> //
326 struct AbsentT {
327   using List = ListT<type::DirectiveName>;
328   using WrapperTrait = std::true_type;
329   List v;
330 };
331 
332 // V5.2: [15.8.1] `memory-order` clauses
333 template <typename T, typename I, typename E> //
334 struct AcqRelT {
335   using EmptyTrait = std::true_type;
336 };
337 
338 // V5.2: [15.8.1] `memory-order` clauses
339 template <typename T, typename I, typename E> //
340 struct AcquireT {
341   using EmptyTrait = std::true_type;
342 };
343 
344 // V5.2: [7.5.2] `adjust_args` clause
345 template <typename T, typename I, typename E> //
346 struct AdjustArgsT {
347   using IncompleteTrait = std::true_type;
348 };
349 
350 // V5.2: [12.5.1] `affinity` clause
351 template <typename T, typename I, typename E> //
352 struct AffinityT {
353   using Iterator = type::IteratorT<T, I, E>;
354   using LocatorList = ObjectListT<I, E>;
355 
356   using TupleTrait = std::true_type;
357   std::tuple<OPT(Iterator), LocatorList> t;
358 };
359 
360 // V5.2: [6.3] `align` clause
361 template <typename T, typename I, typename E> //
362 struct AlignT {
363   using Alignment = E;
364 
365   using WrapperTrait = std::true_type;
366   Alignment v;
367 };
368 
369 // V5.2: [5.11] `aligned` clause
370 template <typename T, typename I, typename E> //
371 struct AlignedT {
372   using Alignment = E;
373   using List = ObjectListT<I, E>;
374 
375   using TupleTrait = std::true_type;
376   std::tuple<OPT(Alignment), List> t;
377 };
378 
379 template <typename T, typename I, typename E> //
380 struct AllocatorT;
381 
382 // V5.2: [6.6] `allocate` clause
383 template <typename T, typename I, typename E> //
384 struct AllocateT {
385   using AllocatorSimpleModifier = E;
386   using AllocatorComplexModifier = AllocatorT<T, I, E>;
387   using AlignModifier = AlignT<T, I, E>;
388   using List = ObjectListT<I, E>;
389 
390   using TupleTrait = std::true_type;
391   std::tuple<OPT(AllocatorSimpleModifier), OPT(AllocatorComplexModifier),
392              OPT(AlignModifier), List>
393       t;
394 };
395 
396 // V5.2: [6.4] `allocator` clause
397 template <typename T, typename I, typename E> //
398 struct AllocatorT {
399   using Allocator = E;
400   using WrapperTrait = std::true_type;
401   Allocator v;
402 };
403 
404 // V5.2: [7.5.3] `append_args` clause
405 template <typename T, typename I, typename E> //
406 struct AppendArgsT {
407   using IncompleteTrait = std::true_type;
408 };
409 
410 // V5.2: [8.1] `at` clause
411 template <typename T, typename I, typename E> //
412 struct AtT {
413   ENUM(ActionTime, Compilation, Execution);
414   using WrapperTrait = std::true_type;
415   ActionTime v;
416 };
417 
418 // V5.2: [8.2.1] `requirement` clauses
419 template <typename T, typename I, typename E> //
420 struct AtomicDefaultMemOrderT {
421   using MemoryOrder = type::MemoryOrder;
422   using WrapperTrait = std::true_type;
423   MemoryOrder v; // Name not provided in spec
424 };
425 
426 // V5.2: [11.7.1] `bind` clause
427 template <typename T, typename I, typename E> //
428 struct BindT {
429   ENUM(Binding, Teams, Parallel, Thread);
430   using WrapperTrait = std::true_type;
431   Binding v;
432 };
433 
434 // V5.2: [15.8.3] `extended-atomic` clauses
435 template <typename T, typename I, typename E> //
436 struct CaptureT {
437   using EmptyTrait = std::true_type;
438 };
439 
440 // V5.2: [4.4.3] `collapse` clause
441 template <typename T, typename I, typename E> //
442 struct CollapseT {
443   using N = E;
444   using WrapperTrait = std::true_type;
445   N v;
446 };
447 
448 // V5.2: [15.8.3] `extended-atomic` clauses
449 template <typename T, typename I, typename E> //
450 struct CompareT {
451   using EmptyTrait = std::true_type;
452 };
453 
454 // V5.2: [8.3.1] `assumption` clauses
455 template <typename T, typename I, typename E> //
456 struct ContainsT {
457   using List = ListT<type::DirectiveName>;
458   using WrapperTrait = std::true_type;
459   List v;
460 };
461 
462 // V5.2: [5.7.1] `copyin` clause
463 template <typename T, typename I, typename E> //
464 struct CopyinT {
465   using List = ObjectListT<I, E>;
466   using WrapperTrait = std::true_type;
467   List v;
468 };
469 
470 // V5.2: [5.7.2] `copyprivate` clause
471 template <typename T, typename I, typename E> //
472 struct CopyprivateT {
473   using List = ObjectListT<I, E>;
474   using WrapperTrait = std::true_type;
475   List v;
476 };
477 
478 // V5.2: [5.4.1] `default` clause
479 template <typename T, typename I, typename E> //
480 struct DefaultT {
481   ENUM(DataSharingAttribute, Firstprivate, None, Private, Shared);
482   using WrapperTrait = std::true_type;
483   DataSharingAttribute v;
484 };
485 
486 // V5.2: [5.8.7] `defaultmap` clause
487 template <typename T, typename I, typename E> //
488 struct DefaultmapT {
489   ENUM(ImplicitBehavior, Alloc, To, From, Tofrom, Firstprivate, None, Default,
490        Present);
491   ENUM(VariableCategory, Scalar, Aggregate, Pointer, Allocatable);
492   using TupleTrait = std::true_type;
493   std::tuple<ImplicitBehavior, OPT(VariableCategory)> t;
494 };
495 
496 template <typename T, typename I, typename E> //
497 struct DoacrossT;
498 
499 // V5.2: [15.9.5] `depend` clause
500 template <typename T, typename I, typename E> //
501 struct DependT {
502   using Iterator = type::IteratorT<T, I, E>;
503   using LocatorList = ObjectListT<I, E>;
504   using TaskDependenceType = tomp::type::TaskDependenceType;
505 
506   struct WithLocators { // Modern form
507     using TupleTrait = std::true_type;
508     // Empty LocatorList means "omp_all_memory".
509     std::tuple<TaskDependenceType, OPT(Iterator), LocatorList> t;
510   };
511 
512   using Doacross = DoacrossT<T, I, E>;
513   using UnionTrait = std::true_type;
514   std::variant<Doacross, WithLocators> u; // Doacross form is legacy
515 };
516 
517 // V5.2: [3.5] `destroy` clause
518 template <typename T, typename I, typename E> //
519 struct DestroyT {
520   using DestroyVar = ObjectT<I, E>;
521   using WrapperTrait = std::true_type;
522   // DestroyVar can be ommitted in "depobj destroy".
523   OPT(DestroyVar) v;
524 };
525 
526 // V5.2: [12.5.2] `detach` clause
527 template <typename T, typename I, typename E> //
528 struct DetachT {
529   using EventHandle = ObjectT<I, E>;
530   using WrapperTrait = std::true_type;
531   EventHandle v;
532 };
533 
534 // V5.2: [13.2] `device` clause
535 template <typename T, typename I, typename E> //
536 struct DeviceT {
537   using DeviceDescription = E;
538   ENUM(DeviceModifier, Ancestor, DeviceNum);
539   using TupleTrait = std::true_type;
540   std::tuple<OPT(DeviceModifier), DeviceDescription> t;
541 };
542 
543 // V5.2: [13.1] `device_type` clause
544 template <typename T, typename I, typename E> //
545 struct DeviceTypeT {
546   ENUM(DeviceTypeDescription, Any, Host, Nohost);
547   using WrapperTrait = std::true_type;
548   DeviceTypeDescription v;
549 };
550 
551 // V5.2: [11.6.1] `dist_schedule` clause
552 template <typename T, typename I, typename E> //
553 struct DistScheduleT {
554   ENUM(Kind, Static);
555   using ChunkSize = E;
556   using TupleTrait = std::true_type;
557   std::tuple<Kind, OPT(ChunkSize)> t;
558 };
559 
560 // V5.2: [15.9.6] `doacross` clause
561 template <typename T, typename I, typename E> //
562 struct DoacrossT {
563   using Vector = ListT<type::LoopIterationT<I, E>>;
564   ENUM(DependenceType, Source, Sink);
565   using TupleTrait = std::true_type;
566   // Empty Vector means "omp_cur_iteration"
567   std::tuple<DependenceType, Vector> t;
568 };
569 
570 // V5.2: [8.2.1] `requirement` clauses
571 template <typename T, typename I, typename E> //
572 struct DynamicAllocatorsT {
573   using EmptyTrait = std::true_type;
574 };
575 
576 // V5.2: [5.8.4] `enter` clause
577 template <typename T, typename I, typename E> //
578 struct EnterT {
579   using List = ObjectListT<I, E>;
580   using WrapperTrait = std::true_type;
581   List v;
582 };
583 
584 // V5.2: [5.6.2] `exclusive` clause
585 template <typename T, typename I, typename E> //
586 struct ExclusiveT {
587   using WrapperTrait = std::true_type;
588   using List = ObjectListT<I, E>;
589   List v;
590 };
591 
592 // V5.2: [15.8.3] `extended-atomic` clauses
593 template <typename T, typename I, typename E> //
594 struct FailT {
595   using MemoryOrder = type::MemoryOrder;
596   using WrapperTrait = std::true_type;
597   MemoryOrder v;
598 };
599 
600 // V5.2: [10.5.1] `filter` clause
601 template <typename T, typename I, typename E> //
602 struct FilterT {
603   using ThreadNum = E;
604   using WrapperTrait = std::true_type;
605   ThreadNum v;
606 };
607 
608 // V5.2: [12.3] `final` clause
609 template <typename T, typename I, typename E> //
610 struct FinalT {
611   using Finalize = E;
612   using WrapperTrait = std::true_type;
613   Finalize v;
614 };
615 
616 // V5.2: [5.4.4] `firstprivate` clause
617 template <typename T, typename I, typename E> //
618 struct FirstprivateT {
619   using List = ObjectListT<I, E>;
620   using WrapperTrait = std::true_type;
621   List v;
622 };
623 
624 // V5.2: [5.9.2] `from` clause
625 template <typename T, typename I, typename E> //
626 struct FromT {
627   using LocatorList = ObjectListT<I, E>;
628   using Expectation = type::MotionExpectation;
629   using Iterator = type::IteratorT<T, I, E>;
630   // See note at the definition of the MapperT type.
631   using Mappers = ListT<type::MapperT<I, E>>; // Not a spec name
632 
633   using TupleTrait = std::true_type;
634   std::tuple<OPT(Expectation), OPT(Mappers), OPT(Iterator), LocatorList> t;
635 };
636 
637 // V5.2: [9.2.1] `full` clause
638 template <typename T, typename I, typename E> //
639 struct FullT {
640   using EmptyTrait = std::true_type;
641 };
642 
643 // V5.2: [12.6.1] `grainsize` clause
644 template <typename T, typename I, typename E> //
645 struct GrainsizeT {
646   ENUM(Prescriptiveness, Strict);
647   using GrainSize = E;
648   using TupleTrait = std::true_type;
649   std::tuple<OPT(Prescriptiveness), GrainSize> t;
650 };
651 
652 // V5.2: [5.4.9] `has_device_addr` clause
653 template <typename T, typename I, typename E> //
654 struct HasDeviceAddrT {
655   using List = ObjectListT<I, E>;
656   using WrapperTrait = std::true_type;
657   List v;
658 };
659 
660 // V5.2: [15.1.2] `hint` clause
661 template <typename T, typename I, typename E> //
662 struct HintT {
663   using HintExpr = E;
664   using WrapperTrait = std::true_type;
665   HintExpr v;
666 };
667 
668 // V5.2: [8.3.1] Assumption clauses
669 template <typename T, typename I, typename E> //
670 struct HoldsT {
671   using WrapperTrait = std::true_type;
672   E v; // No argument name in spec 5.2
673 };
674 
675 // V5.2: [3.4] `if` clause
676 template <typename T, typename I, typename E> //
677 struct IfT {
678   using DirectiveNameModifier = type::DirectiveName;
679   using IfExpression = E;
680   using TupleTrait = std::true_type;
681   std::tuple<OPT(DirectiveNameModifier), IfExpression> t;
682 };
683 
684 // V5.2: [7.7.1] `branch` clauses
685 template <typename T, typename I, typename E> //
686 struct InbranchT {
687   using EmptyTrait = std::true_type;
688 };
689 
690 // V5.2: [5.6.1] `exclusive` clause
691 template <typename T, typename I, typename E> //
692 struct InclusiveT {
693   using List = ObjectListT<I, E>;
694   using WrapperTrait = std::true_type;
695   List v;
696 };
697 
698 // V5.2: [7.8.3] `indirect` clause
699 template <typename T, typename I, typename E> //
700 struct IndirectT {
701   using InvokedByFptr = E;
702   using WrapperTrait = std::true_type;
703   InvokedByFptr v;
704 };
705 
706 // V5.2: [14.1.2] `init` clause
707 template <typename T, typename I, typename E> //
708 struct InitT {
709   using ForeignRuntimeId = E;
710   using InteropVar = ObjectT<I, E>;
711   using InteropPreference = ListT<ForeignRuntimeId>;
712   ENUM(InteropType, Target, Targetsync);   // Repeatable
713   using InteropTypes = ListT<InteropType>; // Not a spec name
714 
715   using TupleTrait = std::true_type;
716   std::tuple<OPT(InteropPreference), InteropTypes, InteropVar> t;
717 };
718 
719 // V5.2: [5.5.4] `initializer` clause
720 template <typename T, typename I, typename E> //
721 struct InitializerT {
722   using InitializerExpr = E;
723   using WrapperTrait = std::true_type;
724   InitializerExpr v;
725 };
726 
727 // V5.2: [5.5.10] `in_reduction` clause
728 template <typename T, typename I, typename E> //
729 struct InReductionT {
730   using List = ObjectListT<I, E>;
731   // See note at the definition of the ReductionIdentifierT type.
732   // The name ReductionIdentifiers is not a spec name.
733   using ReductionIdentifiers = ListT<type::ReductionIdentifierT<I, E>>;
734   using TupleTrait = std::true_type;
735   std::tuple<ReductionIdentifiers, List> t;
736 };
737 
738 // V5.2: [5.4.7] `is_device_ptr` clause
739 template <typename T, typename I, typename E> //
740 struct IsDevicePtrT {
741   using List = ObjectListT<I, E>;
742   using WrapperTrait = std::true_type;
743   List v;
744 };
745 
746 // V5.2: [5.4.5] `lastprivate` clause
747 template <typename T, typename I, typename E> //
748 struct LastprivateT {
749   using List = ObjectListT<I, E>;
750   ENUM(LastprivateModifier, Conditional);
751   using TupleTrait = std::true_type;
752   std::tuple<OPT(LastprivateModifier), List> t;
753 };
754 
755 // V5.2: [5.4.6] `linear` clause
756 template <typename T, typename I, typename E> //
757 struct LinearT {
758   // std::get<type> won't work here due to duplicate types in the tuple.
759   using List = ObjectListT<I, E>;
760   using StepSimpleModifier = E;
761   using StepComplexModifier = E;
762   ENUM(LinearModifier, Ref, Val, Uval);
763 
764   using TupleTrait = std::true_type;
765   // Step == nullopt means 1.
766   std::tuple<OPT(StepSimpleModifier), OPT(StepComplexModifier),
767              OPT(LinearModifier), List>
768       t;
769 };
770 
771 // V5.2: [5.8.5] `link` clause
772 template <typename T, typename I, typename E> //
773 struct LinkT {
774   using List = ObjectListT<I, E>;
775   using WrapperTrait = std::true_type;
776   List v;
777 };
778 
779 // V5.2: [5.8.3] `map` clause
780 template <typename T, typename I, typename E> //
781 struct MapT {
782   using LocatorList = ObjectListT<I, E>;
783   ENUM(MapType, To, From, Tofrom, Alloc, Release, Delete);
784   ENUM(MapTypeModifier, Always, Close, Present, OmpxHold);
785   // See note at the definition of the MapperT type.
786   using Mappers = ListT<type::MapperT<I, E>>; // Not a spec name
787   using Iterator = type::IteratorT<T, I, E>;
788   using MapTypeModifiers = ListT<MapTypeModifier>; // Not a spec name
789 
790   using TupleTrait = std::true_type;
791   std::tuple<OPT(MapType), OPT(MapTypeModifiers), OPT(Mappers), OPT(Iterator),
792              LocatorList>
793       t;
794 };
795 
796 // V5.2: [7.5.1] `match` clause
797 template <typename T, typename I, typename E> //
798 struct MatchT {
799   using IncompleteTrait = std::true_type;
800 };
801 
802 // V5.2: [12.2] `mergeable` clause
803 template <typename T, typename I, typename E> //
804 struct MergeableT {
805   using EmptyTrait = std::true_type;
806 };
807 
808 // V5.2: [8.5.2] `message` clause
809 template <typename T, typename I, typename E> //
810 struct MessageT {
811   using MsgString = E;
812   using WrapperTrait = std::true_type;
813   MsgString v;
814 };
815 
816 // V5.2: [7.6.2] `nocontext` clause
817 template <typename T, typename I, typename E> //
818 struct NocontextT {
819   using DoNotUpdateContext = E;
820   using WrapperTrait = std::true_type;
821   DoNotUpdateContext v;
822 };
823 
824 // V5.2: [15.7] `nowait` clause
825 template <typename T, typename I, typename E> //
826 struct NogroupT {
827   using EmptyTrait = std::true_type;
828 };
829 
830 // V5.2: [10.4.1] `nontemporal` clause
831 template <typename T, typename I, typename E> //
832 struct NontemporalT {
833   using List = ObjectListT<I, E>;
834   using WrapperTrait = std::true_type;
835   List v;
836 };
837 
838 // V5.2: [8.3.1] `assumption` clauses
839 template <typename T, typename I, typename E> //
840 struct NoOpenmpT {
841   using EmptyTrait = std::true_type;
842 };
843 
844 // V5.2: [8.3.1] `assumption` clauses
845 template <typename T, typename I, typename E> //
846 struct NoOpenmpRoutinesT {
847   using EmptyTrait = std::true_type;
848 };
849 
850 // V5.2: [8.3.1] `assumption` clauses
851 template <typename T, typename I, typename E> //
852 struct NoParallelismT {
853   using EmptyTrait = std::true_type;
854 };
855 
856 // V5.2: [7.7.1] `branch` clauses
857 template <typename T, typename I, typename E> //
858 struct NotinbranchT {
859   using EmptyTrait = std::true_type;
860 };
861 
862 // V5.2: [7.6.1] `novariants` clause
863 template <typename T, typename I, typename E> //
864 struct NovariantsT {
865   using DoNotUseVariant = E;
866   using WrapperTrait = std::true_type;
867   DoNotUseVariant v;
868 };
869 
870 // V5.2: [15.6] `nowait` clause
871 template <typename T, typename I, typename E> //
872 struct NowaitT {
873   using EmptyTrait = std::true_type;
874 };
875 
876 // V5.2: [12.6.2] `num_tasks` clause
877 template <typename T, typename I, typename E> //
878 struct NumTasksT {
879   using NumTasks = E;
880   ENUM(Prescriptiveness, Strict);
881   using TupleTrait = std::true_type;
882   std::tuple<OPT(Prescriptiveness), NumTasks> t;
883 };
884 
885 // V5.2: [10.2.1] `num_teams` clause
886 template <typename T, typename I, typename E> //
887 struct NumTeamsT {
888   using TupleTrait = std::true_type;
889   using LowerBound = E;
890   using UpperBound = E;
891   std::tuple<OPT(LowerBound), UpperBound> t;
892 };
893 
894 // V5.2: [10.1.2] `num_threads` clause
895 template <typename T, typename I, typename E> //
896 struct NumThreadsT {
897   using Nthreads = E;
898   using WrapperTrait = std::true_type;
899   Nthreads v;
900 };
901 
902 template <typename T, typename I, typename E> //
903 struct OmpxAttributeT {
904   using EmptyTrait = std::true_type;
905 };
906 
907 template <typename T, typename I, typename E> //
908 struct OmpxBareT {
909   using EmptyTrait = std::true_type;
910 };
911 
912 template <typename T, typename I, typename E> //
913 struct OmpxDynCgroupMemT {
914   using WrapperTrait = std::true_type;
915   E v;
916 };
917 
918 // V5.2: [10.3] `order` clause
919 template <typename T, typename I, typename E> //
920 struct OrderT {
921   ENUM(OrderModifier, Reproducible, Unconstrained);
922   ENUM(Ordering, Concurrent);
923   using TupleTrait = std::true_type;
924   std::tuple<OPT(OrderModifier), Ordering> t;
925 };
926 
927 // V5.2: [4.4.4] `ordered` clause
928 template <typename T, typename I, typename E> //
929 struct OrderedT {
930   using N = E;
931   using WrapperTrait = std::true_type;
932   OPT(N) v;
933 };
934 
935 // V5.2: [7.4.2] `otherwise` clause
936 template <typename T, typename I, typename E> //
937 struct OtherwiseT {
938   using IncompleteTrait = std::true_type;
939 };
940 
941 // V5.2: [9.2.2] `partial` clause
942 template <typename T, typename I, typename E> //
943 struct PartialT {
944   using UnrollFactor = E;
945   using WrapperTrait = std::true_type;
946   OPT(UnrollFactor) v;
947 };
948 
949 // V5.2: [12.4] `priority` clause
950 template <typename T, typename I, typename E> //
951 struct PriorityT {
952   using PriorityValue = E;
953   using WrapperTrait = std::true_type;
954   PriorityValue v;
955 };
956 
957 // V5.2: [5.4.3] `private` clause
958 template <typename T, typename I, typename E> //
959 struct PrivateT {
960   using List = ObjectListT<I, E>;
961   using WrapperTrait = std::true_type;
962   List v;
963 };
964 
965 // V5.2: [10.1.4] `proc_bind` clause
966 template <typename T, typename I, typename E> //
967 struct ProcBindT {
968   ENUM(AffinityPolicy, Close, Master, Spread, Primary);
969   using WrapperTrait = std::true_type;
970   AffinityPolicy v;
971 };
972 
973 // V5.2: [15.8.2] Atomic clauses
974 template <typename T, typename I, typename E> //
975 struct ReadT {
976   using EmptyTrait = std::true_type;
977 };
978 
979 // V5.2: [5.5.8] `reduction` clause
980 template <typename T, typename I, typename E> //
981 struct ReductionT {
982   using List = ObjectListT<I, E>;
983   // See note at the definition of the ReductionIdentifierT type.
984   // The name ReductionIdentifiers is not a spec name.
985   using ReductionIdentifiers = ListT<type::ReductionIdentifierT<I, E>>;
986   ENUM(ReductionModifier, Default, Inscan, Task);
987   using TupleTrait = std::true_type;
988   std::tuple<OPT(ReductionModifier), ReductionIdentifiers, List> t;
989 };
990 
991 // V5.2: [15.8.1] `memory-order` clauses
992 template <typename T, typename I, typename E> //
993 struct RelaxedT {
994   using EmptyTrait = std::true_type;
995 };
996 
997 // V5.2: [15.8.1] `memory-order` clauses
998 template <typename T, typename I, typename E> //
999 struct ReleaseT {
1000   using EmptyTrait = std::true_type;
1001 };
1002 
1003 // V5.2: [8.2.1] `requirement` clauses
1004 template <typename T, typename I, typename E> //
1005 struct ReverseOffloadT {
1006   using EmptyTrait = std::true_type;
1007 };
1008 
1009 // V5.2: [10.4.2] `safelen` clause
1010 template <typename T, typename I, typename E> //
1011 struct SafelenT {
1012   using Length = E;
1013   using WrapperTrait = std::true_type;
1014   Length v;
1015 };
1016 
1017 // V5.2: [11.5.3] `schedule` clause
1018 template <typename T, typename I, typename E> //
1019 struct ScheduleT {
1020   ENUM(Kind, Static, Dynamic, Guided, Auto, Runtime);
1021   using ChunkSize = E;
1022   ENUM(OrderingModifier, Monotonic, Nonmonotonic);
1023   ENUM(ChunkModifier, Simd);
1024   using TupleTrait = std::true_type;
1025   std::tuple<Kind, OPT(OrderingModifier), OPT(ChunkModifier), OPT(ChunkSize)> t;
1026 };
1027 
1028 // V5.2: [15.8.1] Memory-order clauses
1029 template <typename T, typename I, typename E> //
1030 struct SeqCstT {
1031   using EmptyTrait = std::true_type;
1032 };
1033 
1034 // V5.2: [8.5.1] `severity` clause
1035 template <typename T, typename I, typename E> //
1036 struct SeverityT {
1037   ENUM(SevLevel, Fatal, Warning);
1038   using WrapperTrait = std::true_type;
1039   SevLevel v;
1040 };
1041 
1042 // V5.2: [5.4.2] `shared` clause
1043 template <typename T, typename I, typename E> //
1044 struct SharedT {
1045   using List = ObjectListT<I, E>;
1046   using WrapperTrait = std::true_type;
1047   List v;
1048 };
1049 
1050 // V5.2: [15.10.3] `parallelization-level` clauses
1051 template <typename T, typename I, typename E> //
1052 struct SimdT {
1053   using EmptyTrait = std::true_type;
1054 };
1055 
1056 // V5.2: [10.4.3] `simdlen` clause
1057 template <typename T, typename I, typename E> //
1058 struct SimdlenT {
1059   using Length = E;
1060   using WrapperTrait = std::true_type;
1061   Length v;
1062 };
1063 
1064 // V5.2: [9.1.1] `sizes` clause
1065 template <typename T, typename I, typename E> //
1066 struct SizesT {
1067   using SizeList = ListT<E>;
1068   using WrapperTrait = std::true_type;
1069   SizeList v;
1070 };
1071 
1072 // V5.2: [5.5.9] `task_reduction` clause
1073 template <typename T, typename I, typename E> //
1074 struct TaskReductionT {
1075   using List = ObjectListT<I, E>;
1076   // See note at the definition of the ReductionIdentifierT type.
1077   // The name ReductionIdentifiers is not a spec name.
1078   using ReductionIdentifiers = ListT<type::ReductionIdentifierT<I, E>>;
1079   using TupleTrait = std::true_type;
1080   std::tuple<ReductionIdentifiers, List> t;
1081 };
1082 
1083 // V5.2: [13.3] `thread_limit` clause
1084 template <typename T, typename I, typename E> //
1085 struct ThreadLimitT {
1086   using Threadlim = E;
1087   using WrapperTrait = std::true_type;
1088   Threadlim v;
1089 };
1090 
1091 // V5.2: [15.10.3] `parallelization-level` clauses
1092 template <typename T, typename I, typename E> //
1093 struct ThreadsT {
1094   using EmptyTrait = std::true_type;
1095 };
1096 
1097 // V5.2: [5.9.1] `to` clause
1098 template <typename T, typename I, typename E> //
1099 struct ToT {
1100   using LocatorList = ObjectListT<I, E>;
1101   using Expectation = type::MotionExpectation;
1102   // See note at the definition of the MapperT type.
1103   using Mappers = ListT<type::MapperT<I, E>>; // Not a spec name
1104   using Iterator = type::IteratorT<T, I, E>;
1105 
1106   using TupleTrait = std::true_type;
1107   std::tuple<OPT(Expectation), OPT(Mappers), OPT(Iterator), LocatorList> t;
1108 };
1109 
1110 // V5.2: [8.2.1] `requirement` clauses
1111 template <typename T, typename I, typename E> //
1112 struct UnifiedAddressT {
1113   using EmptyTrait = std::true_type;
1114 };
1115 
1116 // V5.2: [8.2.1] `requirement` clauses
1117 template <typename T, typename I, typename E> //
1118 struct UnifiedSharedMemoryT {
1119   using EmptyTrait = std::true_type;
1120 };
1121 
1122 // V5.2: [5.10] `uniform` clause
1123 template <typename T, typename I, typename E> //
1124 struct UniformT {
1125   using ParameterList = ObjectListT<I, E>;
1126   using WrapperTrait = std::true_type;
1127   ParameterList v;
1128 };
1129 
1130 template <typename T, typename I, typename E> //
1131 struct UnknownT {
1132   using EmptyTrait = std::true_type;
1133 };
1134 
1135 // V5.2: [12.1] `untied` clause
1136 template <typename T, typename I, typename E> //
1137 struct UntiedT {
1138   using EmptyTrait = std::true_type;
1139 };
1140 
1141 // Both of the following
1142 // V5.2: [15.8.2] `atomic` clauses
1143 // V5.2: [15.9.3] `update` clause
1144 template <typename T, typename I, typename E> //
1145 struct UpdateT {
1146   using TaskDependenceType = tomp::type::TaskDependenceType;
1147   using WrapperTrait = std::true_type;
1148   OPT(TaskDependenceType) v;
1149 };
1150 
1151 // V5.2: [14.1.3] `use` clause
1152 template <typename T, typename I, typename E> //
1153 struct UseT {
1154   using InteropVar = ObjectT<I, E>;
1155   using WrapperTrait = std::true_type;
1156   InteropVar v;
1157 };
1158 
1159 // V5.2: [5.4.10] `use_device_addr` clause
1160 template <typename T, typename I, typename E> //
1161 struct UseDeviceAddrT {
1162   using List = ObjectListT<I, E>;
1163   using WrapperTrait = std::true_type;
1164   List v;
1165 };
1166 
1167 // V5.2: [5.4.8] `use_device_ptr` clause
1168 template <typename T, typename I, typename E> //
1169 struct UseDevicePtrT {
1170   using List = ObjectListT<I, E>;
1171   using WrapperTrait = std::true_type;
1172   List v;
1173 };
1174 
1175 // V5.2: [6.8] `uses_allocators` clause
1176 template <typename T, typename I, typename E> //
1177 struct UsesAllocatorsT {
1178   using MemSpace = E;
1179   using TraitsArray = ObjectT<I, E>;
1180   using Allocator = E;
1181   struct AllocatorSpec { // Not a spec name
1182     using TupleTrait = std::true_type;
1183     std::tuple<OPT(MemSpace), OPT(TraitsArray), Allocator> t;
1184   };
1185   using Allocators = ListT<AllocatorSpec>; // Not a spec name
1186   using WrapperTrait = std::true_type;
1187   Allocators v;
1188 };
1189 
1190 // V5.2: [15.8.3] `extended-atomic` clauses
1191 template <typename T, typename I, typename E> //
1192 struct WeakT {
1193   using EmptyTrait = std::true_type;
1194 };
1195 
1196 // V5.2: [7.4.1] `when` clause
1197 template <typename T, typename I, typename E> //
1198 struct WhenT {
1199   using IncompleteTrait = std::true_type;
1200 };
1201 
1202 // V5.2: [15.8.2] Atomic clauses
1203 template <typename T, typename I, typename E> //
1204 struct WriteT {
1205   using EmptyTrait = std::true_type;
1206 };
1207 
1208 // ---
1209 
1210 template <typename T, typename I, typename E>
1211 using ExtensionClausesT =
1212     std::variant<OmpxAttributeT<T, I, E>, OmpxBareT<T, I, E>,
1213                  OmpxDynCgroupMemT<T, I, E>>;
1214 
1215 template <typename T, typename I, typename E>
1216 using EmptyClausesT = std::variant<
1217     AcqRelT<T, I, E>, AcquireT<T, I, E>, CaptureT<T, I, E>, CompareT<T, I, E>,
1218     DynamicAllocatorsT<T, I, E>, FullT<T, I, E>, InbranchT<T, I, E>,
1219     MergeableT<T, I, E>, NogroupT<T, I, E>, NoOpenmpRoutinesT<T, I, E>,
1220     NoOpenmpT<T, I, E>, NoParallelismT<T, I, E>, NotinbranchT<T, I, E>,
1221     NowaitT<T, I, E>, ReadT<T, I, E>, RelaxedT<T, I, E>, ReleaseT<T, I, E>,
1222     ReverseOffloadT<T, I, E>, SeqCstT<T, I, E>, SimdT<T, I, E>,
1223     ThreadsT<T, I, E>, UnifiedAddressT<T, I, E>, UnifiedSharedMemoryT<T, I, E>,
1224     UnknownT<T, I, E>, UntiedT<T, I, E>, UseT<T, I, E>, WeakT<T, I, E>,
1225     WriteT<T, I, E>>;
1226 
1227 template <typename T, typename I, typename E>
1228 using IncompleteClausesT =
1229     std::variant<AdjustArgsT<T, I, E>, AppendArgsT<T, I, E>, MatchT<T, I, E>,
1230                  OtherwiseT<T, I, E>, WhenT<T, I, E>>;
1231 
1232 template <typename T, typename I, typename E>
1233 using TupleClausesT =
1234     std::variant<AffinityT<T, I, E>, AlignedT<T, I, E>, AllocateT<T, I, E>,
1235                  DefaultmapT<T, I, E>, DeviceT<T, I, E>, DistScheduleT<T, I, E>,
1236                  DoacrossT<T, I, E>, FromT<T, I, E>, GrainsizeT<T, I, E>,
1237                  IfT<T, I, E>, InitT<T, I, E>, InReductionT<T, I, E>,
1238                  LastprivateT<T, I, E>, LinearT<T, I, E>, MapT<T, I, E>,
1239                  NumTasksT<T, I, E>, OrderT<T, I, E>, ReductionT<T, I, E>,
1240                  ScheduleT<T, I, E>, TaskReductionT<T, I, E>, ToT<T, I, E>>;
1241 
1242 template <typename T, typename I, typename E>
1243 using UnionClausesT = std::variant<DependT<T, I, E>>;
1244 
1245 template <typename T, typename I, typename E>
1246 using WrapperClausesT = std::variant<
1247     AbsentT<T, I, E>, AlignT<T, I, E>, AllocatorT<T, I, E>,
1248     AtomicDefaultMemOrderT<T, I, E>, AtT<T, I, E>, BindT<T, I, E>,
1249     CollapseT<T, I, E>, ContainsT<T, I, E>, CopyinT<T, I, E>,
1250     CopyprivateT<T, I, E>, DefaultT<T, I, E>, DestroyT<T, I, E>,
1251     DetachT<T, I, E>, DeviceTypeT<T, I, E>, EnterT<T, I, E>,
1252     ExclusiveT<T, I, E>, FailT<T, I, E>, FilterT<T, I, E>, FinalT<T, I, E>,
1253     FirstprivateT<T, I, E>, HasDeviceAddrT<T, I, E>, HintT<T, I, E>,
1254     HoldsT<T, I, E>, InclusiveT<T, I, E>, IndirectT<T, I, E>,
1255     InitializerT<T, I, E>, IsDevicePtrT<T, I, E>, LinkT<T, I, E>,
1256     MessageT<T, I, E>, NocontextT<T, I, E>, NontemporalT<T, I, E>,
1257     NovariantsT<T, I, E>, NumTeamsT<T, I, E>, NumThreadsT<T, I, E>,
1258     OrderedT<T, I, E>, PartialT<T, I, E>, PriorityT<T, I, E>, PrivateT<T, I, E>,
1259     ProcBindT<T, I, E>, SafelenT<T, I, E>, SeverityT<T, I, E>, SharedT<T, I, E>,
1260     SimdlenT<T, I, E>, SizesT<T, I, E>, ThreadLimitT<T, I, E>,
1261     UniformT<T, I, E>, UpdateT<T, I, E>, UseDeviceAddrT<T, I, E>,
1262     UseDevicePtrT<T, I, E>, UsesAllocatorsT<T, I, E>>;
1263 
1264 template <typename T, typename I, typename E>
1265 using UnionOfAllClausesT = typename type::Union< //
1266     EmptyClausesT<T, I, E>,                      //
1267     ExtensionClausesT<T, I, E>,                  //
1268     IncompleteClausesT<T, I, E>,                 //
1269     TupleClausesT<T, I, E>,                      //
1270     UnionClausesT<T, I, E>,                      //
1271     WrapperClausesT<T, I, E>                     //
1272     >::type;
1273 } // namespace clause
1274 
1275 using type::operator==;
1276 
1277 // The variant wrapper that encapsulates all possible specific clauses.
1278 // The `Extras` arguments are additional types representing local extensions
1279 // to the clause set, e.g.
1280 //
1281 // using Clause = ClauseT<Type, Id, Expr,
1282 //                        MyClause1, MyClause2>;
1283 //
1284 // The member Clause::u will be a variant containing all specific clauses
1285 // defined above, plus MyClause1 and MyClause2.
1286 //
1287 // Note: Any derived class must be constructible from the base class
1288 // ClauseT<...>.
1289 template <typename TypeType, typename IdType, typename ExprType,
1290           typename... Extras>
1291 struct ClauseT {
1292   using TypeTy = TypeType;
1293   using IdTy = IdType;
1294   using ExprTy = ExprType;
1295 
1296   // Type of "self" to specify this type given a derived class type.
1297   using BaseT = ClauseT<TypeType, IdType, ExprType, Extras...>;
1298 
1299   using VariantTy = typename type::Union<
1300       clause::UnionOfAllClausesT<TypeType, IdType, ExprType>,
1301       std::variant<Extras...>>::type;
1302 
1303   llvm::omp::Clause id; // The numeric id of the clause
1304   using UnionTrait = std::true_type;
1305   VariantTy u;
1306 };
1307 
1308 template <typename ClauseType> struct DirectiveWithClauses {
1309   llvm::omp::Directive id = llvm::omp::Directive::OMPD_unknown;
1310   tomp::type::ListT<ClauseType> clauses;
1311 };
1312 
1313 } // namespace tomp
1314 
1315 #undef OPT
1316 #undef ENUM
1317 
1318 #endif // LLVM_FRONTEND_OPENMP_CLAUSET_H
1319