You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
This is a Circle implementation of C++20's [`std::variant`](http://eel.is/c++draft/variant) class. The goal of this exercise isn't about providing a faster-compiling variant, although it it that. Like my [mdspan implementation](https://github.com/seanbaxter/mdspan#mdspan-circle), working through variant is an opportunity to extend the language so that writing such advanced code no longer poses a challenge.
6
6
@@ -317,6 +317,58 @@ The trailing expand operator `...` substitutes the predicate for each pack eleme
317
317
318
318
## Visit
319
319
320
+
By far the most troublesome function in the C++ variant is its [visit](http://eel.is/c++draft/variant.visit) function. This function is tasked with multi-dimensional control flow, invoking an argument callable with the active variant members extracted from a parameter of variant arguments. Michael Park [documents](https://mpark.github.io/programming/2019/01/22/variant-visitation-v2/) the ongoing struggles of implementing even a one-dimensional visitor.
321
+
322
+
The Circle builtins `__visit` and `__visit_r` (which adds an explicit return type, see [visit<R>: Explicit Return Type for `visit`](http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2018/p0655r1.pdf)) generate n-dimensional control flow to map runtime variables to n constants, exposed as a parameter pack named `indices`. The user supplies an expression which is substituted for each combination of constants.
using YSequence = std::index_sequence<1, 3, 5, 7, 9>;
347
+
int y = 7;
348
+
349
+
// Invoke with an element from an enumeration.
350
+
shapes_t z = rhombus;
351
+
352
+
__visit<XDim, YSequence, shapes_t>(
353
+
f<indices...>(),
354
+
x, y, z
355
+
);
356
+
}
357
+
```
358
+
```
359
+
$ circle enum.cxx && ./enum
360
+
3 7 rhombus
361
+
```
362
+
363
+
`__visit` is pretty generic. It supports three kinds of parameterizations:
364
+
1. Specify a constant for the right-hand side of an interval. Passing an integral N generates control flow for integral values between 0 and N-1. This is all we need for a variant visit, where the dimensions are provided by `variant_size_v`.
365
+
2. Specify a collection of integral/enum types in an `std::integer_sequence` container. These constants don't have to be ordered.
366
+
3. Specify an enumeration type. Reflection causes each enumerator in the type to be visited.
367
+
368
+
The builtin takes N template arguments, one for each dimension, and N + 1 function arguments. The first function argument is a dependent expression that's substituted for each combination of constant values. The `indices` pack declaration is visible only in this context. The subsequent N function arguments are integral or enum values bounded by their corresponding template arguments.
369
+
370
+
Note that the visitor expression is not put into a lambda. There is no closure. In the code above, the expression is substituted 10 * 5 * 6 = 300 times, all in the scope of the `main` function. Making n-dimensional visitation a builtin allows the compiler to most efficiently implement this intricate control flow.
The variant `std::visit` function now has a trivial implementation. We return the result of a call to `__visit`, and that's all. The n-dimensional collection of variant sizes is expressed with `variant_size_v<Variants.remove_reference>...`. The corresponding collection of runtime index values is expressed with `vars.index()...`. The result expression extracts each combination of indices by calling the `get` member function on each forwarded variant (to maintain its value category), passing the implicitly-declared `indices` pack as the `get` template argument, and expanding this pack in the callable's function argument list.
0 commit comments