diff --git a/2996_reflection/reflection.md b/2996_reflection/reflection.md index 84a0d118..1ebbd873 100644 --- a/2996_reflection/reflection.md +++ b/2996_reflection/reflection.md @@ -53,6 +53,10 @@ Specifically, we are mostly proposing a subset of features suggested in [@P1240R - a number of `consteval` _metafunctions_ to work with reflections (including deriving other reflections), and - constructs called _splicers_ to produce grammatical elements from reflections (e.g., `[: $refl$ :]`). +(Note that this aims at something a little broader than pure "reflection". + We not only want to observe the structure of the program: We also want to ease generating code that depends on those observations. + That combination is sometimes referred to as "reflective metaprogramming", but within WG21 discussion the term "reflection" has often been used informally to refer to the same general idea.) + This proposal is not intended to be the end-game as far as reflection and compile-time metaprogramming are concerned. Instead, we expect it will be a useful core around which more powerful features will be added incrementally over time. In particular, we believe that most @@ -151,7 +155,7 @@ Used like: template requires std::is_enum_v constexpr std::string enum_to_string(E value) { - template for (constexpr auto e : std::meta::members_of(^E)) { + template for (constexpr auto e : std::meta::enumerators_of(^E)) { if (value == [:e:]) { return std::string(std::meta::name_of(e)); } @@ -200,7 +204,8 @@ typename[:^char:] c = '*'; // Same as: char c = '*'; ``` ::: -The `typename` prefix can be omitted in the same contexts as with dependent qualified names. For example: +The `typename` prefix can be omitted in the same contexts as with dependent qualified names (i.e., in what the standard calls _type-only contexts_. +For example: :::bq ```c++ @@ -236,6 +241,58 @@ This example also illustrates that bit fields are not beyond the reach of this p [On Compiler Explorer](https://godbolt.org/z/vT4rbva7M) +Note that a "member access splice" like `s.[:member_number(1):]` is a more direct member access mechanism than the traditional syntax. +It doesn't involve member name lookup, access checking, or --- if the spliced reflection value denotes a member function --- overload resolution. + +This proposal includes a number of consteval "metafunctions" that enable the introspection of various language constructs. +Among those metafunctions is `std::meta::nonstatic_data_members_of` which returns a vector of reflection values that describe the nonstatic members of a given type. +We could thus rewrite the above example as: + +:::bq +```c++ +struct S { unsigned i:2, j:6; }; + +consteval auto member_number(int n) { + return std::meta::nonstatic_data_members_of(^S)[n]; +} + +int main() { + S s{0, 0}; + s.[:member_number(1):] = 42; // Same as: s.j = 42; + s.[:member_number(5):] = 0; // Error (member_number(5) is not a constant). +} +``` +::: + +[On Compiler Explorer](https://godbolt.org/z/Wb1vx7jqb) + +This proposal specifies that namespace `std::meta` is associated with the reflection type (`std::meta::info`); the `std::meta::` qualification can therefore be omitted in the example above. + +Another frequently-useful metafunction is `std::meta::name_of`, which returns a `std::string_view` describing the unqualified name of an entity denoted by a given reflection value. +With such a facility, we could conceivably access nonstatic data members "by string": + +:::bq +```c++ +struct S { unsigned i:2, j:6; }; + +consteval auto member_named(std::string_view name) { + for (std::meta::info field : nonstatic_data_members_of(^S)) { + if (name_of(field) == name) return field; + } +} + +int main() { + S s{0, 0}; + s.[:member_named("j"):] = 42; // Same as: s.j = 42; + s.[:member_named("x"):] = 0; // Error (member_named("x") is not a constant). +} +``` +::: + + +[On Compiler Explorer](https://godbolt.org/z/dvYoreK9E) + + ## List of Types to List of Sizes Here, `sizes` will be a `std::array` initialized with `{sizeof(int), sizeof(float), sizeof(double)}`: @@ -292,6 +349,10 @@ template [On Compiler Explorer](https://godbolt.org/z/bvPeqvaK5). +Note that the memoization implicit in the template substitution process still applies. +So having multiple uses of, e.g., `make_integer_sequence` will only involve one evaluation of `make_integer_seq_refl(20)`. + + ## Getting Class Layout ::: bq @@ -341,7 +402,7 @@ One of the most commonly requested facilities is to convert an enum value to a s template requires std::is_enum_v constexpr std::string enum_to_string(E value) { - template for (constexpr auto e : std::meta::members_of(^E)) { + template for (constexpr auto e : std::meta::enumerators_of(^E)) { if (value == [:e:]) { return std::string(std::meta::name_of(e)); } @@ -363,7 +424,7 @@ We can also do the reverse in pretty much the same way: template requires std::is_enum_v constexpr std::optional string_to_enum(std::string_view name) { - template for (constexpr auto e : std::meta::members_of(^E)) { + template for (constexpr auto e : std::meta::enumerators_of(^E)) { if (name == std::meta::name_of(e)) { return [:e:]; } @@ -382,7 +443,7 @@ template requires std::is_enum_v constexpr std::string enum_to_string(E value) { constexpr auto enumerators = - std::meta::members_of(^E) + std::meta::enumerators_of(^E) | std::views::transform([](std::meta::info e){ return std::pair(std::meta::value_of(e), std::meta::name_of(e)); }) @@ -402,6 +463,16 @@ Note that this last version has lower complexity: While the versions using an ex [On Compiler Explorer](https://godbolt.org/z/Y5va8MqzG). + +Many many variations of these functions are possible and beneficial depending on the needs of the client code. +For example: + + - the "" case could instead output a valid cast expression like "E(5)" + - a more sophisticated lookup algorithm could be selected at compile time depending on the length of `enumerators_of(^E)` + - a compact two-way persistent data structure could be generated to support both `enum_to_string` and `string_to_enum` with a minimal footprint + - etc. + + ## Parsing Command-Line Options Our next example shows how a command-line option parser could work by automatically inferring flags based on member names. A real command-line parser would of course be more complex, this is just the beginning. @@ -451,6 +522,7 @@ This example is based on a presentation by Matúš Chochlík. [On Compiler Explorer](https://godbolt.org/z/G4dh3jq8a). + ## A Simple Tuple Type :::bq @@ -1448,6 +1520,13 @@ This paper is proposing that: * Meanwhile, `template_arguments_of(^C)` yields `{^int}` while `template_arguments_of(^std::unique_ptr)` yields `{^int, ^std::default_deleter}`. This is `C` has its own template arguments that can be reflected on. +### Freestanding implementations + + +Several important metafunctions, such as `std::meta::_nonstatic_data_members_of`, return a `std::vector` value. +Unfortunately, that means that they are currently not usable in a freestanding environment. +That is an highly undesirable limitation that we believe should be addressed by imbuing freestanding implementations with a more restricted `std::vector` (e.g., one that can only allocate at compile time). + ### Synopsis Here is a synopsis for the proposed library API. The functions will be explained below.