diff --git a/3420_reflection_of_templates/Makefile b/3420_reflection_of_templates/Makefile index 065026e7..b749f0c7 100644 --- a/3420_reflection_of_templates/Makefile +++ b/3420_reflection_of_templates/Makefile @@ -1,2 +1,2 @@ -p3420r0.html : reflection-of-templates.md +p3420r1.html : reflection-of-templates.md include ../md/mpark-wg21.mk diff --git a/3420_reflection_of_templates/p3420r1.html b/3420_reflection_of_templates/p3420r1.html new file mode 100644 index 00000000..7822b7e3 --- /dev/null +++ b/3420_reflection_of_templates/p3420r1.html @@ -0,0 +1,2076 @@ + + + + + + + + Reflection of Templates + + + + + + + + +
+
+

Reflection of Templates

+ + + + + + + + + + + + + + + + + + + + + +
Document #:P3420R1 [Latest] [Status]
Date:2025-02-08
Project:Programming Language C++
Audience: + EWG
+
Reply-to: + Barry Revzin
<>
+ Andrei Alexandrescu, NVIDIA
<>
+ Daveed Vandevoorde, EDG
<>
+ Michael Garland, NVIDIA
<>
+
+
+
+ +

1 +Revision History

+

Since [P3420R0]:

+ +

2 +Motivation

+

A key trait that makes a reflection facility powerful is +completeness—the ability to reflect the entire source language. +Current proposals facilitate reflection of certain declarations in a +namespace or a +struct/class/union +definition. Although [P2996R7]’s +members_of metafunction includes +template members (function template and class template declarations), +that proposal does not offer primitives for reflection of template +declarations themselves. In this proposal we aim to define a +comprehensive API for reflection of C++ templates.

+

A powerful archetypal motivator—argued in [P3157R1] and at length in the CppCon +2024 talk +“Reflection Is Not Contemplation”—is the identity metafunction. +This function, given a class type, creates a replica of it, crucially +providing the ability to make minute changes to the copy, such as +changing its name, adding or removing members, changing the signature of +existing member functions, and so on. By means of example:

+
+
+
class Widget {
+  int a;
+public:
+  template <class T> requires (std::is_convertible_v<T, int>)
+  Widget(const T&);
+  const Widget& f(const Widget&) const &;
+  template <class T>
+  void g();
+};
+
+consteval {
+  identity(^^Widget, "Gadget");
+}
+// Same effect as this handwritten code:
+// class Gadget {
+//   int a;
+// public:
+//   template <class T> requires (std::is_convertible_v<T, int>)
+//   Gadget(const T&);
+//   const Gadget& f(const Gadget&) const &;
+//   template <class T>
+//   void g();
+// };
+
+
+

While an exact copy of a type is not particularly interesting—and +although a compiler could easily provide a primitive to do so, such a +feature would miss the point—the ability to deconstruct a type into its +components (bases, data members, member functions, nested types, nested +template declarations, etc.) and reassemble them in a different context +enables a vast array of applications. These include various forms of +instrumentation, creating parallel hierarchies as required by several +design patterns, generating arbitrary subsets of an interface (the +powerset of an interface), logging, tracing, debugging, timing +measurements, and many more. We consider +identity’s ability to perform the +roundtrip from a type to its components and back to the type the +quintessential goal of reflection, which is at the same time the proof +of reflection’s completeness and the fountainhead of many of its +applications.

+ +

In order to reflect on nontrivial C++ code and splice it back with +controlled modifications, the language must necessarily reflect on all +templates—class templates and specializations thereof, function +templates, variable templates, and alias templates. This is not an easy +task; templates pose unique challenges to a reflection engine because, +by their nature, they are only partially analyzed semantically. For +example, neither parameter types nor the return type of a function +template can be reliably represented as reflections of types because +they may refer dependent types, which are not known until the template +is instantiated. The same thinking goes for a variety of declarations +that can be found inside a class template; C++ templates are more akin +to patterns by which code is to be generated than to standalone, +semantically verified code. For that reason, the reflection facilities +proposed in P2996 intended for non-templated declarations are not +readily applicable to templates.

+

Conversely, reflection code would be seriously hamstrung if it lacked +the ability to reflect on template code (and subsequently manipulate and +generate related code), especially because templates are ubiquitous in +today’s C++ codebases. Furthermore, limiting reflection to +instantiations of class templates (which would fit the charter of P2996) +does not suffice because class templates commonly define inner function +templates (e.g., every instantiation of +std::vector +defines several constructor templates and other function templates such +as insert and +emplace_back). (Nontemplate classes +may, of course, also define class templates and function templates +within.) Therefore, to the extent that reflection of C++ declarations is +deemed useful, reflection of templates is essential.

+

Given that many elements of a template cannot be semantically +analyzed early (at template definition time), this proposal adopts a +simple and robust strategy for reflecting components of template +declarations. First and foremost, the unit of reflection for template +declarations is the entire declaration (as opposed to parts of the +declaration, such as the +requires +clause or the +noexcept +clause). This keeps the complexity of template declarations encapsulated +within the reflection handle and away from user code—at user level, +there’s never a need to parse a declaration and seldom a need to compose +a complex template declaration from scratch. Our proposed metafunction +declaration_of(h) +yields a reflection of the declaration of an entity reflected by +h, suitable for injection. +As a trivial example:

+
+
+
template <typename T> void f(T, int);
+consteval {
+  using namespace std::meta;
+  // Equivalent to a (re)declaration of f.
+  queue_injection(^^{ \(declaration_of(^^f)); });
+}
+
+
+

The +consteval +block above injects a declaration of +f within the current namespace, +which is correct but hardly useful. This brings us to the second +important element of this proposal’s strategy: new declarations are +obtained in a functional manner by calling metafunctions against +reflections of existing declarations (i.e., obtained by means of +declaration_of). As another example, +consider we want to declare another function +g that has the same signature as +f:

+
+
+
template <typename T> void f(T, int);
+consteval {
+  using namespace std::meta;
+  // Equivalent to the declaration:
+  // template <typename T> void g(T);
+  queue_injection(^^{ \(set_name(declaration_of(^^f), "g")); });
+}
+
+
+

One immediate question is—why couldn’t we simply write set_name(^^f, "g") +instead of set_name(declaration_of(^^f), "g")? +Clearly the set_name metafunction +could easily be made to work in both cases. We consider the longer form +clearer; a casual read of set_name(^^f, "g") +may leave the impression that the metafunction modifies the actual +function f. Throughout this proposal +we foster declaration_of as a means +to obtain a “copy” of a declaration, which can be subsequently +manipulated and spliced back into code.

+

Last but not least, let’s take a look at generating an actual +definition for g, which will +illustrate the third and last essential element of the strategy espoused +by this proposal: handling parameter names of declarations. Assume, for +example, we simply want g to forward +its arguments transparently to f. +From within g, we’d need to call +f(std::forward<decltype(x)>(x), std::forward<decltype(y)>(y)), +where x and +y are the names of +g’s parameters, and rely on +deduction. A more general approach would be to use f<T> +instead of f to make sure the +correct instantiation of f is called +(without relying on template argument deduction), so we’ll focus on +generating the latter code. Either way, the task immediately raises the +question of name assignment and lookup—where do we take the names +T, +x, and +y from? To make matters even more +challenging, note that the function parameters do not even have names in +the declaration of f that we started +with, so x and +y are nowhere to be found—reflection +must synthesize them.

+

For handling template parameter and function parameter names, this +proposal fosters metafunctions that reflect parameter lists wholesale +instead of naming schemata or conventions. Each (template and function) +parameter list can be reflected as an std::meta::info +object that can be manipulated and spliced into generated code, as shown +below:

+
+
+
template <typename T> void f(T, int);
+consteval {
+  using namespace std::meta;
+  // Equivalent to the definition:
+  // template <typename T> void g(T x, int y) {
+  //   return f<T>(std::forward<decltype(x)>(x), std::forward<decltype(y)>(y));
+  // }
+  queue_injection(^^{
+    \(set_name(declaration_of(^^f), "g")) {
+      return f<\(template_parameter_list(^^f))>(\(forward_parameter_list(^^f)));
+    }
+  });
+}
+
+
+

The proposed template_parameter_list(rt) +primitive reflects (as a spliceable std::meta::info +object) the parameter list of a template reflected by +rt. Splicing the resulting +reflection handle results in a copy of the parameter list, with commas +and ellipses inserted as appropriate. The names of the parameters are +autogenerated (user code can consider them unambiguous) and in error +messages the compiler may stylize them as +_T0, +_T1, +__p0, +__p1, etc. Given that the parameter +names are specific to a given declaration, they can be used only in a +definition obtained from +declaration_of for the same +declaration.

+

Similarly, the proposed parameter_list(rf) +metafunction offers a comma-separated list of the function or function +template reflected by rf, suitable +for splicing. Two important additional cases are also supported: +forward_parameter_list (used above) +and move_parameter_list, which pass +the parameters through +std::forward +and +std::move, +respectably. Splicing the std::meta::info +returned by these functions allows generated code to access parameters +of a function created by using declaration_of(rf), +as illustrated above. Again, the names of the parameters are +compiler-generated and can be considered world-unique.

+

The initial proposal [P3420R0] allowed accessing all parts of +a template definition as construct sequences. However, for some +implementations performing such extrication was deemed difficult. Also, +separating parts of a function declaration made it difficult to define +how names are looked up after splicing. The current approach—reflecting +entire declarations, creating new declarations from the reflection of +existing ones, and accessing parameters implicitly—eschews many of the +original proposal’s issues without giving away flexibility.

+

We paid special attention to preserve the spirit and design style of +[P2996R7] and to ensure seamless +interoperation with it.

+

2.1 Example: Logging and +Forwarding

+

As a more elaborate example, consider the matter of creating a +function template +logged::func +for any free function template func +in such a way that +logged::func +has the same signature and semantics as +func, with the added behavior that +it logs the call and its parameters prior to execution. We illustrate +the matter for function templates because it is more difficult (and more +complete) than the case of simple functions.

+
+
+
struct Widget {};  // for exposition purposes
+
+template <typename T>
+requires std::is_copy_constructible_v<T>
+void fun(const T& value, Widget&) { ... }
+
+template <typename... Ts>
+void logger(const Ts&.. values);
+
+// Introspection will add declarations here.
+namespace logged {
+  struct Widget {};  // for exposition purposes
+}
+
+// Given a function func, create a function logged::func that logs arguments.
+consteval void make_logged(std::meta::info f) {
+  using namespace std::meta;
+  namespace_inject(^^::logged, ^^{
+    \(declaration_of(f)) {
+      logger("Calling " + identifier_of(f) + "(", \(parameter_list(f)), ")");
+      return [:f:]<\(template_parameter_list(f))>(\(forward_parameter_list(f)));
+    }
+  });
+}
+
+consteval {
+  make_logged(^^fun);
+}
+
+// Equivalent hand-written code:
+// namespace logged {
+//     template <typename T>
+//     requires std::is_copy_constructible_v<T>
+//     void fun(const T& __p0, const ::Widget& __p1) {
+//         logger("Call to ", "fun", "(", _p0, __p1, ")");
+//         return ::fun<T>(std::forward<decltype(__p0)>(__p0), std::forward<decltype(__p1)>(__p1));
+//     }
+// }
+
+
+

The code uses std::meta::namespace_inject +as defined in [P3294R2] to create a function +definition from a token sequence in a given namespace, in this case +logged. The generative side of the +code uses again std::meta::declaration_of +to get a reflection handle corresponding to the declaration of the +function template reflected by f. +The template parameters, +requires and +noexcept +clauses (if present), function parameters, and return type are all part +of the returned reflection handle. Most importantly, the declaration can +be modified and spliced back into code.

+

Names used in declaration_of are +handled in a hygienic manner that prevents unintended binding. All +non-dependent names are looked up in the original declaration. Thus, +although namespace logged defines a +type called Widget, the declaration +returned by declaration_of uses the +same Widget as the original +declaration (which is why we used +::Widget in +the comment representing equivalent code). Dependent names are, as +expected, not looked up. The name of the function itself also carries +information about what scope f was +defined in (in our example the global namespace), but the primitive +namespace_inject ignores that +information and injects a new function of the same name in the given +namespace.

+

The rest of the make_logged +metafunction uses +template_parameter_list, +parameter_list, and +forward_parameter_list in concert to +first issue a call to logger and +then forward the call to the original function.

+

To recap, we propose generating new functions (or function templates) +from existing ones by splicing +declaration_of for copying the +function prototype, followed by the new function body as a construct +sequence. To access and pass around template parameters and function +parameters (when appropriate), we propose the helper metafunctions +template_parameter_list, +parameter_list, and +forward_parameter_list. These +primitives allow manipulating parameters without needing to name them +explicitly.

+

2.2 Example: +logging_vector

+

Consider a more elaborate example of a class template +logging_vector, which defines a +functional equivalent of +std::vector +but that uses a function logger to +log all calls to its member functions. Here, we need to iterate the +public members of +std::vector +and insert appropriate copies of declarations.

+
+
+
template <typename T, typename A = std::allocator<T>>
+class logging_vector {
+private:
+  std::vector<T, A> data;
+  using namespace std::meta;
+public:
+  consteval {
+    template for (constexpr auto r : get_public_members(^^std::vector<T, A>)) {
+      if (is_type_alias(r) || is_class_type(r)) {
+        queue_injection(^^{ using \id(identifier_of(r)) = \(r); });
+      } else if (is_function(r) && !is_special_member_function(r)) {
+        queue_injection(^^{
+          \(declaration_of(r)) {
+            logger("Calling ", display_string_of(^^std::vector<T, A>), "::",
+              identifier_of(r), "(", \(parameter_list(r)), ")");
+            return data.[:r:](\(forward_param_list_of(r)));
+          }
+        });
+      } else if (is_function_template(r) && !is_constructor(r)) {
+        queue_injection(^^{
+          \(declaration_of(r)) {
+            logger("Calling ", display_string_of(^^std::vector<T, A>), "::",
+              identifier_of(r), "(", \(parameter_list(r)), ")");
+            return data.template [:r:]<\(template_parameter_list(r))>(
+              \(forward_parameters_list(r))
+            );
+          }
+        });
+      } else if (is_constructor(r)) {
+        queue_injection(^^{
+          \(declaration_of(r)) : data(\(forward_parameters_list(r))) {}
+        });
+      } else {
+        // Ignore other nested declarations.
+      }
+    }
+  }
+};
+
+
+

(Refer to [P2996R7] for metafunctions +get_public_members, +is_type_alias, +is_class_type, +is_function, +is_function_template, and +identifier_of, of which semantics +should be intuitive as we discuss the context.) Here, as the code +iterates declarations in class std::vector<T, A> +(an instance of +std::vector, +not the template class itself, which simplifies a few aspects of the +example), it takes action depending on the nature of the declaration +found. For +typedef (or +equivalent +using) +declarations and for nested class declaration, a +using +declaration of the form +using +name = std::vector<T, A>::name; +is issued for each corresponding name found in std::vector<T, A>.

+

If the iterated declaration introduces a non-special function +(filtered with the test is_function(r) && !is_special_member_function(r)), +a new function is defined with the same signature. The definition issues +a call to logger passing the +parameters list, followed by a forwarding action to the original +function for the data member.

+

If the iterated declaration is that of a non-special function +template (filtered by testing is_function_template(r) && !is_constructor(r)), +the code synthesizes a function template. Although the the template +declaration has considerably more elements, the same +declaration_of metafunction +primitive is used. In the synthesized declaration, all names are already +looked up in the context of the std::vector<T, A> +instantiation; for example, +begin() +returns type std::vector<T, A>::iterator +and not logging_vector<T, A>::iterator +(even if that name has been declared as an alias).

+

Finally, if the iterated declaration is a constructor, it is handled +separately because the generated code has a distinct syntax. For +constructors (whether templated or not), the generated code simply +forwards all parameters to the constructor of the payload.

+

For seeing how variadic parameters are handled, consider the function +emplace that is declared as a +variadic function template with the signature template <typename... A> iterator emplace(const_iterator, A&&...);. +For the reflection of emplace, +calling template_parameter_list +returns the token sequence ^^{ _T0... } +(exact name chosen by the implementation), which is suitable for passing +to the instantiation of another template. Correspondingly, +parameter_list returns the token +sequence ^^{ __p0, __p1... }. +This token sequence can be expanded anywhere the arguments of the called +function are needed.

+

It is worth noting that the forwarding call syntaxes data.[:r:](...) +(for regular functions) and data.template [:r:]<...>(...) +(for function templates) are already defined with the expected semantics +in [P2996R7], and work properly in the +prototype implementations. The only added elements are the metafunctions +template_parameter_list, +parameter_list, and +forward_parameter_list, which ensure +proper passing down of parameter names from the synthesized function to +to the corresponding member function.

+

2.2.1 Renaming and Projection

+

The logging_vector example has an +issue related to member functions of +std::vector +that refer to +std::vector +itself in their signature, such as void swap(std::vector<T, A>&). +The example as written above will generate code equivalent to the +following:

+
+
+
template <typename T, typename A>
+class logging_vector {
+  ...
+  void swap(std::vector<T, A>& other) {
+    logger("Calling ", "std::vector<T, A>", "::",
+      "swap", "(", other, ")");
+    return data.swap(std::forward<decltype(other)>(other));
+  }
+  ...
+};
+
+
+

The declaration is technically correct, but not what was intended. We +need to define swap as taking +another instance of logging_vector, +i.e., void swap(logging_vector<T, A>&):

+
+
+
  void swap(logging_vector<T, A>& other) {
+    logger("Calling ", "std::vector<T, A>", "::",
+      "swap", "(", other.data, ")");
+    return data.swap(std::forward<decltype(other)>(other).data);
+  }
+
+
+

There are two fundamental transformation we need to perform to morph +the original vector<T, A>::swap +into its desired counterpart inside +logging_vector:

+ +

For replacement, we propose a primitive metafunction +replace that replaces a type with +another throughout a declaration, potentially in multiple places, in a +manner reminiscent to the alpha +renaming notion found in lambda calculus. To that end we define +replace for declarations, with the +signature:

+
+
+
info replace(info declaration, info replace_this, info with_this);
+
+
+

The metafunction returns a new declaration with the replacements +effected. To improve the +logging_vector example above, we’d +need to replace the uses of declaration_of(f) +with replace(declaration_of(f), ^^std::vector<T, A>, ^^logging_vector<T, A>)). +The construct would transform properly all declarations.

+

Projection is trickier because not all parameters should be affected, +only those whose type (after removing cvref qualifiers) is std::vector<T, A>. +To effect projection, user code defines a projection +function:

+
+
+
  // Inside logging_vector's definition
+  template <typename X>
+  decltype(auto) project(X&& x) {
+    if constexpr (std::is_same_v<remove_cvref_t<X>, std::logging_vector>) {
+      return std::forward<decltype(x.data)>(x.data);
+    } else {
+      return std::forward<X>(x);
+    }
+  }
+
+
+

We then propose a standard metafunction that applies a projection +function to each parameter of a function:

+
+
+
info project_parameters(info declaration, info projection_function);
+
+
+

Armed with these artifacts, we can have reflection generate proper +forwarding functions from existing member functions of +std::vector +like this:

+
+
+
        // For member functions of std::vector<T, A>
+        auto my_decl = replace(declaration_of(r), ^^std::vector<T, A>, ^^logging_vector<T, A>);
+        auto my_params = project_parameters(r, ^^project);
+        queue_injection(^^{
+          [:\(decl):] {
+            logger("Calling ", identifier_of(^^std::vector<T, A>), "::",
+              identifier_of(r), "(", \tokens(my_params), ")");
+            return data.[:r:](forward_param_list_of(my_params));
+          }
+        });
+
+
+

The code for member function templates is similar. One notable detail +is that forward_param_list_of works +seamlessly on function declarations and on projected parameter lists +(flexibility made possible by the uniform representation of code +artifacts as std::meta::info +objects).

+

3 Metafunctions for Template +Declarations

+

All template declarations (whether a class template or specialization +thereof, a function template, an alias template, or a variable +template), have some common elements: a template parameter list, an +optional list of attributes, and an optional template-level +requires +clause.

+

If our goal were simply to splice a template declaration back in its +entirety—possibly with a different name and/or with normalized parameter +names—the introspection metafunction declaration_of(f) +may seem sufficient. However, most often code generation needs to tweak +elements of the declaration—for example, adding a conjunction to the +requires +clause or eliminating the deprecated +attribute. Therefore, we aim to identify structural components of the +declaration and define primitives that manipulate each in turn. That +way, we offer unbounded customization opportunities by allowing users to +amend the original declaration with custom code.

+

In addition to template parameters, +requires +clause, and attributes, certain template declarations have additional +elements as follows:

+ +

Armed with a these insights and with the strategy of using token +sequences throughout, we propose the following reflection primitives for +templates.

+

3.1 Synopsis

+

The declarations below summarize the metafunctions proposed, with +full explanations for each following. Some (where noted) have identical +declarations with metafunctions proposed in P2996, to which we propose +extended semantics.

+
+
+
namespace std::meta {
+    // Returns the reflection of the declaration of a template
+    consteval auto declaration_of(info) -> info;
+    // Replacement
+    consteval auto replace(info declaration, info replace_this, info with_this) -> info;
+    // Projection
+    consteval auto project_parameters(info declaration, info projection_function) -> info;
+    // Returns given declaration with a changed name
+    consteval auto set_name(info, string_view) -> info;
+    //  Multiple explicit specializations and/or partial specializations
+    consteval auto template_alternatives_of(info) -> vector<info>;
+    // Template parameters vector
+    consteval auto template_parameters_of(info) -> vector<info>;
+    // Comma-separated template parameter list
+    consteval auto template_parameter_list(info) -> info;
+    // Attributes - extension of semantics in P3385
+    consteval auto attributes_of(info) -> vector<info>;
+    consteval auto add_attribute(info) -> info;
+    consteval auto remove_attribute(info) -> info;
+    // Template-level requires clause
+    consteval auto set_requires_clause(info, info) -> info;
+    consteval auto add_requires_clause_conjunction(info, info) -> info;
+    consteval auto add_requires_clause_disjunction(info, info) -> info;
+    // Is this the reflection of the primary template declaration?
+    consteval auto is_primary_template(info) -> bool;
+    // Overloads of a given function name
+    consteval auto overloads_of(info) -> vector<info>;
+    // Inline specifier present?
+    consteval auto is_inline(info) -> bool;
+    // cvref qualifiers and others - extensions of semantics in P2996
+    consteval auto is_const(info) -> bool;
+    consteval auto is_explicit(info) -> bool;
+    consteval auto is_volatile(info) -> bool;
+    consteval auto is_rvalue_reference_qualified(info) -> bool;
+    consteval auto is_lvalue_reference_qualified(info) -> bool;
+    consteval auto is_static_member(info) -> bool;
+    consteval auto has_static_linkage(info) -> bool;
+    consteval auto is_noexcept(info) -> bool;
+    // predicate for `noexcept`
+    consteval auto set_noexcept_clause(info, info) -> info;
+    consteval auto add_noexcept_clause_conjunction(info, info) -> info;
+    consteval auto add_noexcept_clause_disjunction(info, info) -> info;
+    // `explicit` present? - extension of P2996
+    // predicate for `explicit`
+    consteval auto set_explicit_clause(info, info) -> info;
+    consteval auto add_explicit_clause_conjunction(info, info) -> info;
+    consteval auto add_explicit_clause_disjunction(info, info) -> info;
+    // `constexpr` present?
+    consteval auto is_declared_constexpr(info) -> bool;
+    // `consteval` present?
+    consteval auto is_declared_consteval(info) -> bool;
+    // Function template parameters
+    consteval auto parameters_of(info) -> vector<info>;
+    consteval auto parameter_list(info) -> vector<info>;
+    consteval auto forward_parameter_list(info) -> vector<info>;
+    //
+    consteval auto set_trailing_requires_clause(info, info) -> info;
+    consteval auto add_trailing_requires_clause_conjunction(info, info) -> info;
+    consteval auto add_trailing_requires_clause_disjunction(info, info) -> info;
+}
+
+
+

3.1.1 +declaration_of

+
+
+
consteval auto declaration_of(info) -> info;
+
+
+

Returns the reflection of the declaration of a type or template. +Subsequently that reflection can be inserted as part of a token +sequence. Typical uses issue a call +declaration_of and then compute a +slightly modified declaration before splicing it.

+

3.1.2 +set_name

+
+
+
info set_name(info, string_view new_name);
+
+
+

Given a reflection, returns a reflection that is identical except for +the name which is new_name. +Example:

+
+
+
template <typename T>
+void fun(const T& value) { ... }
+
+consteval {
+  auto f = ^^fun;
+  queue_injection(^^{
+    [:\(set_name(declaration_of(f), "my_fun")):] {
+      return [:f:]<\tokens(template_parameters_of(f))>(\tokens(forward_parameter_list(f)));
+    }
+  });
+}
+
+// Equivalent code:
+// template <typename T>
+// void my_fun(const T& value) {
+//   return fun<T>(value);
+// }
+
+
+

3.1.3 +template_alternatives_of

+
+
+
vector<info> template_alternatives_of(info);
+
+
+

In addition to general template declarations, a class template or a +variable template may declare explicit specializations and/or partial +specializations. Example:

+
+
+
// Primary template declaration
+template <typename T, template<typename> typename A, auto x> class C;
+// Explicit specialization
+template <> class C<int, std::allocator, 42> {};
+// Partial specialization
+template <typename T> class C<T, std::allocator, 100> {};
+
+
+

Each specialization must be accessible for reflection in separation +from the others; in particular, there should be a mechanism to iterate +^^C in the +example above to reveal info handles +for the three available variants (the primary declaration, the explicit +specialization, and the partial specialization). Each specialization may +contain the same syntactic elements as the primary template declaration. +In addition to those, explicit specializations and partial +specializations also contain the specialization’s template arguments (in +the code above: <int, std::allocator, 42> +and <T, std::allocator, 100>, +respectively), which we also set out to be accessible through +reflection.

+

Given the reflection of a class template or variable template, +template_alternatives_of returns the +reflections of all explicit specializations and all partial +specializations thereof (including the primary declaration). +Declarations are returned in syntactic order. This is important because +a specialization may depend on a previous one, as shown below:

+
+
+
// Primary declaration
+template <class T> struct A { ... };
+// Explicit specialization
+template <> struct A<char> { ... };
+// Explicit specialization, depends on the previous one
+template <> struct A<int> : A<char> { ... };
+// Partial specialization, may depend on both previous ones
+template <class T, class U> struct A<std::tuple<T, U>> : A<T>, A<U> { ... };
+
+
+

As a consequence of the source-level ordering, the primary +declaration’s reflection is always the first element of the returned +vector. Example:

+
+
+
template <typename T> class C;
+template <> class C<int> {};
+template <class T> class C<std::pair<int, T>> {};
+
+// Get reflections for the primary and the two specializations
+constexpr auto r = template_alternatives_of(^^C);
+static_assert(r.size() == 3);
+static_assert(r[0] == ^^C);
+
+
+

3.1.4 +template_parameters_of

+
+
+
vector<info> template_parameters_of(info);
+
+
+

Given the reflection of a template, +template_parameters_of returns an +array of token sequences containing the template’s parameter names (a +parameter pack counts as one parameter and is followed by +...). To +avoid name clashes, the implementation chooses unique names. (Our +examples use "_T" +followed by a number.) For function templates, it is guaranteed that +declaration_of and +template_parameters_of will use the +same names. For example, template_parameters_of(^^A) +may yield the token sequences ^^{_T0}, +^^{_T1}, +and ^^{_T2...}.

+

For explicit specializations of class templates, +template_parameters_of returns an +empty vector:

+
+
+
template <typename T> class A;
+template <> class A<int>;
+
+// r refers to A<int>
+constexpr auto r = template_alternatives_of(^^A)[1];  // pick specialization
+static_assert(template_parameters_of(r).empty());
+
+
+

For partial specializations of class templates, +template_parameters_of returns the +parameters of the partial specialization (not those of the primary +template). For example:

+
+
+
template <typename T> class A;
+template <typename T, typename U> class A<std::tuple<T, U>>;
+constexpr auto r = template_alternatives_of(^^A)[1];  // pick specialization
+static_assert(template_parameters_of(r).size() == 2);
+
+
+

Given a class template A that has +explicit instantiations and/or partial specializations declared, +referring the template by name in the call template_parameters_of(^^A) +is not ambiguous—it refers to the primary declaration of the +template.

+

Calling template_parameters_of +against a non-template entity, as in template_parameters_of(^^int) +or template_parameters_of(^^std::vector<int>), +fails to evaluate to a constant expression.

+

3.1.5 +template_parameter_list

+
+
+
consteval auto template_parameter_list(info) -> info;
+
+
+

Returns the result of +template_parameters_of as a +comma-separated list in a token sequence. Variadics, if any, are +followed by +....

+

3.1.6 +attributes_of, +add_attribute, +remove_attribute

+
+
+
vector<info> attributes_of(info);
+info add_attribute(info, info);
+info remove_attribute(info, info);
+
+
+

attributes_of returns all +attributes associated with info. The +intent is to extend the functionality of +attributes_of as defined in [P3385R0] to template declarations.

+

add_attribute(r, attr) +returns a reflection that adds the attribute +attr to the given reflection +r. If +r already had the attribute, it is +returned.

+

remove_attribute returns a +reflection that removes the attribute +attr from the given reflection +r. If +r did now have the attribute, it is +returned.

+

We defer details and particulars to [P3385R0].

+

3.1.7 +set_requires_clause, +add_requires_clause_conjunction, +add_requires_clause_disjunction

+
+
+
info set_requires_clause(info, info);
+info add_requires_clause_conjunction(info, info);
+info add_requires_clause_disjunction(info, info);
+
+
+

Given the reflection of a template, +set_requires_clause returns a +reflection of an identical template but with the +requires +clause replaced by the token sequence given in the second parameter. The +existing +requires +clause in the template, if any, is ignored. Example:

+
+
+
template <typename T>
+requires (sizeof(T) > 64)
+void fun(const T& value) { ... }
+
+consteval {
+  auto f = ^^fun;
+  queue_injection(^^{
+    [:\(set_requires_clause(set_name(declaration_of(f), "my_fun")),
+      ^^{ sizeof(\tokens(template_parameters_of(f)[0])) == 128 }):];
+  });
+}
+
+// Equivalent code:
+// template <typename T>
+// requires (sizeof(T) == 128)
+// void my_fun(const T& value);
+
+
+

The functions +add_requires_clause_conjunction and +add_requires_clause_disjunction +compose the given token sequence with the existing +requires +clause. (If no such clause exists, it is assumed to be the expression +true.) The +add_requires_clause_conjunction +metafunction performs a conjunction (logical “and”) between the clause +given in the token sequence and the existing token sequence. The +add_requires_clause_disjunction +metafunction performs a disjunction (logical “or”) between the clause +given in the token sequence and the existing token sequence.

+

Calling set_requires_clause, +add_requires_clause_conjunction, or +add_requires_clause_disjunction +against a non-template, as in set_requires_clause(^^int, ^^{true}), +fails to evaluate to a constant expression.

+

3.1.8 +is_primary_template

+
+
+
bool is_primary_template(info);
+
+
+

Returns +true if +info refers to the primary +declaration of a class template or variable template, +false in all +other cases (including cases in which the query does not apply, such as +is_primary_template(^^int)). +In particular, this metafunction is useful for distinguishing between a +primary template and its explicit specializations or partial +specializations. For example:

+
+
+
template <class> class A;
+template <> class A<int>;
+static_assert(is_primary_template(^^A));
+static_assert(is_primary_template(template_alternatives_of(^^A)[0]));
+static_assert(!is_primary_template(template_alternatives_of(^^A)[1]));
+static_assert(!is_primary_template(^^int));
+
+
+

3.1.9 +overloads_of

+
+
+
vector<info> overloads_of(info);
+
+
+

Returns an array of all overloads of a given function, usually passed +in as a reflection of an identifier (e.g., +^^func). All +overloads are included, template and nontemplate. To distinguish +functions from function templates, +is_function_template ([P2996R7]) can be used. Example:

+
+
+
void f();
+template <typename T> void f(const T&);
+
+// Name f has two overloads present.
+static_assert(overloads_of(^^f).size() == 2);
+
+
+

The order of overloads in the results is the same as encountered in +source code.

+

If a function f has only one +declaration (no overloads), overloads_of(^^f) +returns a vector with one element, which is equal to +^^f. +Therefore, for such functions, +^^f and +overloads_of(^^f)[0] +can be used interchangeably.

+

Each info returned by +overloads_of is the reflection of a +fully resolved symbol that doesn’t participate in any further overload +resolution. In other words, for any function +f (overloaded or not), overloads_of(overloads_of(^^f)[i]) +is the same as overloads_of(^^f)[i] +for all appropriate values of i.

+

3.1.10 +is_inline

+
+
+
bool is_inline(info);
+
+
+

Returns +true if and +only if the given reflection handle refers to a function, function +template, or namespace that has the +inline +specifier. In all other cases, such as is_inline(^^int), +returns false.

+

Note that this metafunctions has applicability beyond templates as it +applies to regular functions and namespaces. Because of this it is +possible to migrate this metafunction to a future revision of P2996.

+

3.1.11 +is_const, +is_explicit, +is_volatile, +is_rvalue_reference_qualified, +is_lvalue_reference_qualified, +is_static_member, +has_static_linkage, +is_noexcept

+
+
+
bool is_const(info);
+bool is_explicit(info);
+bool is_volatile(info);
+bool is_rvalue_reference_qualified(info);
+bool is_lvalue_reference_qualified(info);
+bool is_static_member(info);
+bool has_static_linkage(info)
+bool is_noexcept(info);
+
+
+

Extends the homonym metafunctions defined by [P2996R7] to function templates.

+

For is_noexcept and +is_explicit, +true is +returned if the +noexcept and +explicit +clause, respectively, is predicated.

+

3.1.12 +set_noexcept_clause, +add_noexcept_clause_conjunction, +add_noexcept_clause_disjunction

+
+
+
info set_noexcept_clause(info, info);
+info add_noexcept_clause_conjunction(info, info);
+info add_noexcept_clause_disjunction(info, info);
+
+
+

Given the reflection of a template, +set_noexcept_clause returns a +reflection of an identical template but with the +noexcept +clause replaced by the token sequence given in the second parameter. The +existing +noexcept +clause in the template, if any, is ignored. Example:

+
+
+
template <typename T>
+noexcept (sizeof(T) > 64)
+void fun(const T& value) { ... }
+
+consteval {
+  auto f = ^^fun;
+  queue_injection(^^{
+    [:\(set_noexcept_clause(set_name(declaration_of(f), "my_fun")),
+      ^^{ sizeof(\tokens(template_parameters_of(f)[0])) == 128 }):];
+  });
+}
+
+// Equivalent code:
+// template <typename T>
+// noexcept (sizeof(T) == 128)
+// void my_fun(const T& value);
+
+
+

The functions +add_noexcept_clause_conjunction and +add_noexcept_clause_disjunction +compose the given token sequence with the existing +noexcept +clause. (If no such clause exists, it is assumed to be the expression +true.) The +add_noexcept_clause_conjunction +metafunction performs a conjunction (logical “and”) between the clause +given in the token sequence and the existing token sequence. The +add_noexcept_clause_disjunction +metafunction performs a disjunction (logical “or”) between the clause +given in the token sequence and the existing token sequence.

+

Calling set_noexcept_clause, +add_noexcept_clause_conjunction, or +add_noexcept_clause_disjunction +against a non-template, as in set_noexcept_clause(^^int, ^^{true}), +fails to evaluate to a constant expression.

+

3.1.13 +set_explicit_clause, +add_explicit_clause_conjunction, +add_explicit_clause_disjunction

+
+
+
info set_explicit_clause(info, info);
+info add_explicit_clause_conjunction(info, info);
+info add_explicit_clause_disjunction(info, info);
+
+
+

Given the reflection of a template, +set_explicit_clause returns a +reflection of an identical template but with the +explicit +clause replaced by the token sequence given in the second parameter. The +existing +explicit +specifier in the template, if any, is ignored.

+

The functions +add_explicit_clause_conjunction and +add_explicit_clause_disjunction +compose the given token sequence with the existing +explicit +clause. (If no such clause exists, it is assumed to be the expression +true.) The +add_explicit_clause_conjunction +metafunction performs a conjunction (logical “and”) between the clause +given in the token sequence and the existing token sequence. The +add_explicit_clause_disjunction +metafunction performs a disjunction (logical “or”) between the clause +given in the token sequence and the existing token sequence.

+

Calling set_explicit_clause, +add_explicit_clause_conjunction, or +add_explicit_clause_disjunction +against a non-template, as in set_explicit_clause(^^int, ^^{true}), +fails to evaluate to a constant expression.

+

3.1.14 +is_declared_constexpr, +is_declared_consteval

+
+
+
bool is_declared_constexpr(info);
+bool is_declared_consteval(info);
+
+
+

Returns +true if and +only if info refers to a declaration +that is +constexpr or +consteval, +respectively. In all other cases, returns +false.

+

3.1.15 +parameters_of

+
+
+
vector<info> parameters_of(info);
+
+
+

Returns an array of token sequences containing the parameter names of +the function template represented by +info. Parameter names are chosen by +the implementation. It is guaranteed that the nmes returned by parameters_of(f) +are consistent with the names returned by parameters_of(f) +for any reflection f of a function +template.

+

This function bears similarities with +parameters_of in [P3096R3]. However, one key difference +is that parameters_of returns +reflection of types, whereas +template_return_type_of returns the +token sequence of the return type (given that the type is often not +known before instantiation or may be auto& +etc).

+

3.1.16 +parameter_list

+
+
+
info parameter_list(info);
+
+
+

Convenience function that returns the parameters returned by +parameters_of in the form of a +comma-separated list. The result is returned as a token sequence.

+

3.1.17 +forward_parameter_list

+
+
+
info forward_parameter_list(info);
+
+
+

Convenience function that returns the parameters returned by +parameters_of in the form of a +comma-separated list, each passed to +std::forward +appropriately. The result is returned as a token sequence and can be +spliced in a call to foward all parameters to another function.

+

3.1.18 +set_trailing_requires_clause, add_trailing_requires_clause_conjunction, +add_trailing_requires_clause_disjunction

+
+
+
info set_trailing_requires_clause(info, info);
+info add_trailing_requires_clause_conjunction(info, info);
+info add_trailing_requires_clause_disjunction(info, info);
+
+
+

Given the reflection of a template, +set_trailing_requires_clause returns +a reflection of an identical template but with the +requires +clause replaced by the token sequence given in the second parameter. The +existing +requires +clause in the template, if any, is ignored. Example:

+
+
+
template <typename T>
+requires (sizeof(T) > 64)
+void fun(const T& value) { ... }
+
+consteval {
+  auto f = ^^fun;
+  queue_injection(^^{
+    [:\(set_trailing_requires_clause(set_name(declaration_of(f), "my_fun")),
+      ^^{ sizeof(\tokens(template_parameters_of(f)[0])) == 128 }):];
+  });
+}
+
+// Equivalent code:
+// template <typename T>
+// requires (sizeof(T) == 128)
+// void my_fun(const T& value);
+
+
+

The functions add_trailing_requires_clause_conjunction +and add_trailing_requires_clause_disjunction +compose the given token sequence with the existing +requires +clause. (If no such clause exists, it is assumed to be the expression +true.) The +add_trailing_requires_clause_conjunction +metafunction performs a conjunction (logical “and”) between the clause +given in the token sequence and the existing token sequence. The add_trailing_requires_clause_disjunction +metafunction performs a disjunction (logical “or”) between the clause +given in the token sequence and the existing token sequence.

+

Calling +set_trailing_requires_clause, add_trailing_requires_clause_conjunction, +or add_trailing_requires_clause_disjunction +against a non-template, as in set_trailing_requires_clause(^^int, ^^{true}), +fails to evaluate to a constant expression.

+

4 Metafunctions for Iterating +Members of Class Templates

+

The metafunctions described above need to be complemented with +metafunctions that enumerate the contents of a class template. +Fortunately, [P2996R7] already defines number of +metafunctions for doing so: +members_of, +static_data_members_of, +nonstatic_data_members_of, +nonstatic_data_members_of, +enumerators_of, +subobjects_of. To these, P2996 adds +access-aware metafunctions +accessible_members_of, +accessible_bases_of, +accessible_static_data_members_of, +accessible_nonstatic_data_members_of, +and accessible_subobjects_of. Here, +we propose that these functions are extended in semantics to also +support info representing class +templates. The reflections of members of class templates, however, are +not the same as the reflection of corresponding members of regular +classes;

+

One important addendum to P2996 that is crucial for code generation +is that when applied to members, these discovery metafunctions return +members in lexical order. Otherwise, attempts to implement +identity will fail for class +templates. Consider:

+
+
+
template <typename T>
+struct Container {
+    using size_type = size_t;
+    size_type size() const;
+}
+
+
+

5 Future +Work

+

Like [P2996R7], this proposal is designed to +grow by accretion. Future revisions will add more primitives that +complement the existing ones and improve the comprehensiveness of the +reflection facility for templates.

+

This proposal is designed to interoperate with [P2996R7], [P3157R1], [P3294R2], and [P3385R0]. Changes in these proposals +may influence this proposal. Also, certain metafunctions proposed herein +(such as is_inline) may migrate to +these proposals.

+

6 +References

+
+
+[P2996R7] Wyatt Childers, Peter Dimov, Dan Katz, Barry Revzin, Andrew +Sutton, Faisal Vali, and Daveed Vandevoorde. 2024-10-12. Reflection for +C++26.
https://wg21.link/p2996r7
+
+
+[P3096R3] Adam Lach, Walter Genovese. 2024-09-15. Function Parameter +Reflection in Reflection for C++26.
https://wg21.link/p3096r3
+
+
+[P3157R1] Andrei Alexandrescu, Barry Revzin, Bryce Lelbach, Michael +Garland. 2024-05-22. Generative Extensions for Reflection.
https://wg21.link/p3157r1
+
+
+[P3294R2] Andrei Alexandrescu, Barry Revzin, and Daveed Vandevoorde. +2024-10-15. Code Injection with Token Sequences.
https://wg21.link/p3294r2
+
+
+[P3385R0] Aurelien Cassagnes, Aurelien Cassagnes, Roman Khoroshikh, +Anders Johansson. 2024-09-16. Attributes reflection.
https://wg21.link/p3385r0
+
+
+[P3420R0] Andrei Alexandrescu, Barry Revzin, Daveed Vandevoorde, Michael +Garland. 2024-10-16. Reflection of Templates.
https://wg21.link/p3420r0
+
+
+
+
+ + diff --git a/3420_reflection_of_templates/reflection-of-templates.md b/3420_reflection_of_templates/reflection-of-templates.md index 0d623658..62402296 100644 --- a/3420_reflection_of_templates/reflection-of-templates.md +++ b/3420_reflection_of_templates/reflection-of-templates.md @@ -1,6 +1,6 @@ --- title: "Reflection of Templates" -document: P3420R0 +document: P3420R1 date: today audience: EWG author: @@ -8,6 +8,8 @@ author: email: - name: Andrei Alexandrescu, NVIDIA email: + - name: Daveed Vandevoorde, EDG + email: - name: Michael Garland, NVIDIA email: toc: true @@ -15,35 +17,45 @@ tag: reflection hackmd: true --- +# Revision History + +Since [@P3420R0]: + +- replaced API that returns portions of a function declaration as a token sequence with a functional-style API that takes a declaration and returns a modified declaration +- for implementation friendliness, removed metafunctions returning code that potentially contains a mix of dependent and non-dependent identifiers as token sequences +- consequently, no need for the "as-if" rule for compiler-returned token sequences +- added _replacement_ and _projection_ as fundamental transformations +- replaced "token sequence" with "construct sequence" throughout to keep in sync with [@P3294R3]. + # Motivation -A key characteristic that makes a reflection facility powerful is *completeness*, i.e., the ability to reflect the entirety of the source language. Current proposals facilitate reflection of certain declarations in a namespace or a `struct`/`class`/`union` definition. Although [@P2996R7]'s `members_of` metafunction includes template members (function template and class template declarations), it does not offer primitives for reflection of template declarations themselves. In this proposal, we aim to define a comprehensive API for reflection of C++ templates. +A key trait that makes a reflection facility powerful is *completeness*—the ability to reflect the entire source language. Current proposals facilitate reflection of certain declarations in a namespace or a `struct`/`class`/`union` definition. Although [@P2996R7]'s `members_of` metafunction includes template members (function template and class template declarations), that proposal does not offer primitives for reflection of template declarations themselves. In this proposal we aim to define a comprehensive API for reflection of C++ templates. -A powerful archetypal motivator—argued in [@P3157R1] and at length in the CppCon 2024 talk "Reflection Is Not Contemplation"— is the *identity* metafunction. This function, given a class type, creates a replica of it, crucially providing the ability to make minute changes to the copy, such as changing its name, adding or removing members, changing the signature of existing member functions, and so on. By means of example: +A powerful archetypal motivator—argued in [@P3157R1] and at length in the CppCon 2024 [talk](https://youtube.com/watch?v=H3IdVM4xoCU) "Reflection Is Not Contemplation"—is the *identity* metafunction. This function, given a class type, creates a replica of it, crucially providing the ability to make minute changes to the copy, such as changing its name, adding or removing members, changing the signature of existing member functions, and so on. By means of example: ```cpp class Widget { - int a; + int a; public: - template requires (std::is_convertible_v) - Widget(const T&); - const Widget& f(const Widget&) const &; - template - void g(); + template requires (std::is_convertible_v) + Widget(const T&); + const Widget& f(const Widget&) const &; + template + void g(); }; consteval { - identity(^^Widget, "Gadget"); + identity(^^Widget, "Gadget"); } // Same effect as this handwritten code: // class Gadget { -// int a; +// int a; // public: -// template requires (std::is_convertible_v) -// Gadget(const T&); -// const Gadget& f(const Gadget&) const &; -// template -// void g(); +// template requires (std::is_convertible_v) +// Gadget(const T&); +// const Gadget& f(const Gadget&) const &; +// template +// void g(); // }; ``` @@ -67,78 +79,255 @@ While an exact copy of a type is not particularly interesting—and although - `reflect_invoke` --> -In order to reflect on nontrivial C++ code and splice it back with controlled modifications, it is necessary to reflect all templates—class templates and specializations thereof, function templates, variable templates, and alias templates. This is not an easy task; templates pose unique challenges to a reflection engine because, by their nature, they are only partially analyzed semantically; for example, neither parameter types nor the return type of a function template can be represented as reflections of types because some of those types are often not known until the template is instantiated. The same thinking goes for a variety of declarations that can be found inside a class template; C++ templates are more akin to patterns by which code is to be generated than to standalone, semantically verified code. For that reason, the reflection facilities proposed in P2996 intended for non-templated declarations are not readily applicable to templates. +In order to reflect on nontrivial C++ code and splice it back with controlled modifications, the language must necessarily reflect on all templates—class templates and specializations thereof, function templates, variable templates, and alias templates. This is not an easy task; templates pose unique challenges to a reflection engine because, by their nature, they are only partially analyzed semantically. For example, neither parameter types nor the return type of a function template can be reliably represented as reflections of types because they may refer dependent types, which are not known until the template is instantiated. The same thinking goes for a variety of declarations that can be found inside a class template; C++ templates are more akin to patterns by which code is to be generated than to standalone, semantically verified code. For that reason, the reflection facilities proposed in P2996 intended for non-templated declarations are not readily applicable to templates. -Conversely, reflection code would be seriously hamstrung if it lacked the ability to reflect on template code (and subsequently manipulate and generate related code), especially because templates are ubiquitous in today's C++ codebases. Furthermore, limiting reflection to instantiations of class templates (which would fit the charter of P2996) does not suffice because class templates commonly define inner function templates (e.g., every instantiation of `std::vector` defines several constructor templates and other function templates such as `insert` and `emplace_back`). Therefore, to the extent reflection of C++ declarations is deemed useful, reflection of templates is essential. +Conversely, reflection code would be seriously hamstrung if it lacked the ability to reflect on template code (and subsequently manipulate and generate related code), especially because templates are ubiquitous in today's C++ codebases. Furthermore, limiting reflection to instantiations of class templates (which would fit the charter of P2996) does not suffice because class templates commonly define inner function templates (e.g., every instantiation of `std::vector` defines several constructor templates and other function templates such as `insert` and `emplace_back`). (Nontemplate classes may, of course, also define class templates and function templates within.) Therefore, to the extent that reflection of C++ declarations is deemed useful, reflection of templates is essential. -Given that many elements of a template cannot be semantically analyzed early (at template definition time), this proposal adopts a consistent strategy for reflecting components of template declarations: each element of a template declaration—such as the parameter list, constraints, and template argument list—is represented as a *token sequence*, as proposed in [@P3294R2]. We consider token sequences a key enabler of the ability to introspect templates as proposed in this paper. Though operating with token sequences is a departure from P2996's *modus operandi*, we paid special attention to ensure seamless interoperation with it. +Given that many elements of a template cannot be semantically analyzed early (at template definition time), this proposal adopts a simple and robust strategy for reflecting components of template declarations. First and foremost, the unit of reflection for template declarations is the entire declaration (as opposed to parts of the declaration, such as the `requires` clause or the `noexcept` clause). This keeps the complexity of template declarations encapsulated within the reflection handle and away from user code—at user level, there's never a need to parse a declaration and seldom a need to compose a complex template declaration from scratch. Our proposed metafunction `declaration_of($h$)` yields a reflection of the declaration of an entity reflected by `$h$`, suitable for injection. As a trivial example: -As lightly structured representations of components of template declarations, token sequences have several important advantages: +```cpp +template void f(T, int); +consteval { + using namespace std::meta; + // Equivalent to a (re)declaration of f. + queue_injection(^^{ \(declaration_of(^^f)); }); +} +``` -- *Simplicity:* C++ template declarations and the rules for substitution and looking up symbols are quite complex. Adding new language elements dedicated to handling, for example, reflection of dependent types would complicate the language immensely. In contrast, token sequences are a simple, uniform, and effective means to represent any part of a template declaration because they fundamentally are created from, and consist of, C++ source code. -- *Interoperation:* Token sequences will seamlessly interoperate with, and take advantage of, all primitives and facilities introduced in P3294 related to token sequences. -- *Code generation ability:* A key aspect of reflection is the ability to "hook into" code by generating new code that copies existing functionality and adds elements such as code instrumentation, logging, or tracing. Generating instrumented templates from components of existing template declarations is simple and straightforward—as simple as any use of token sequences for code generation. -- *Implementation friendliness:* Token sequences do not require heavy infrastructure or a specific implementation design. In addition, this proposal grants important freedom and latitude to compilers, as detailed in the next section. +The `consteval` block above injects a declaration of `f` within the current namespace, which is correct but hardly useful. This brings us to the second important element of this proposal's strategy: new declarations are obtained in a functional manner by calling metafunctions against reflections of existing declarations (i.e., obtained by means of `declaration_of`). As another example, consider we want to declare another function `g` that has the same signature as `f`: -## The "As-If" Rule for Token Sequences Returned by Reflection Metafunctions +```cpp +template void f(T, int); +consteval { + using namespace std::meta; + // Equivalent to the declaration: + // template void g(T); + queue_injection(^^{ \(set_name(declaration_of(^^f), "g")); }); +} +``` -Most metafunctions proposed in this paper return token sequences. Some implementations may not preserve the exact tokens as initially present in source code for a variety of reasons. First, it is possible that an implementation "normalizes" code that may have different forms in source, for example replacing `template` with the equivalent `template` or vice versa. Second, a compiler may run some simple front-end processing during tokenization, replacing e.g. `+1` or `01` with `1`. Third, compilers may look up some types early and replace them with the internal representation thereof, which is fully constant-folded and with all default arguments substituted—again, making it tenuous to return looked-up types exactly in the form originally present in source code. Fourth, some implementations are known to parse templates eagerly and drop the tokens early in the processing pipeline. For such a compiler, producing the tokens would be difficult and inefficient. +One immediate question is—why couldn't we simply write `set_name(^^f, "g")` instead of `set_name(declaration_of(^^f), "g")`? Clearly the `set_name` metafunction could easily be made to work in both cases. We consider the longer form clearer; a casual read of `set_name(^^f, "g")` may leave the impression that the metafunction modifies the actual function `f`. Throughout this proposal we foster `declaration_of` as a means to obtain a "copy" of a declaration, which can be subsequently manipulated and spliced back into code. -Given these realities of current compiler implementations, demanding that the compiler returns the exact tokens present in the initial source code may entail unnecessary burden on some implementations. We therefore stipulate that metafunctions that return token sequences are bound by the following minimal requirements: +Last but not least, let's take a look at generating an actual definition for `g`, which will illustrate the third and last essential element of the strategy espoused by this proposal: handling parameter names of declarations. Assume, for example, we simply want `g` to forward its arguments transparently to `f`. From within `g`, we'd need to call `f(std::forward(x), std::forward(y))`, where `x` and `y` are the names of `g`'s parameters, and rely on deduction. A more general approach would be to use `f` instead of `f` to make sure the correct instantiation of `f` is called (without relying on template argument deduction), so we'll focus on generating the latter code. Either way, the task immediately raises the question of name assignment and lookup—where do we take the names `T`, `x`, and `y` from? To make matters even more challenging, note that the function parameters do not even have names in the declaration of `f` that we started with, so `x` and `y` are nowhere to be found—reflection must synthesize them. -- Metafunctions that return token sequences originating from existing code must return code that, when spliced back into source, compiles and runs with the same semantics as the original source code. -- Implementations are allowed to return token sequences that contain nonstandard code (including handles into compiler-internal data structure), again as long as the result of splicing has the same semantics as the original source code. -- Returning empty token sequences is always observed. -- There is no other guaranteed assumption about metafunctions that return tokens. +For handling template parameter and function parameter names, this proposal fosters metafunctions that reflect parameter lists wholesale instead of naming schemata or conventions. Each (template and function) parameter list can be reflected as an `std::meta::info` object that can be manipulated and spliced into generated code, as shown below: -In particular, user code should not assume token sequences returned by implementation-defined metafunctions compare equal with tokens assumed to be equivalent. The only guarantee is that splicing the tokens back has the same effect. +```cpp +template void f(T, int); +consteval { + using namespace std::meta; + // Equivalent to the definition: + // template void g(T x, int y) { + // return f(std::forward(x), std::forward(y)); + // } + queue_injection(^^{ + \(set_name(declaration_of(^^f), "g")) { + return f<\(template_parameter_list(^^f))>(\(forward_parameter_list(^^f))); + } + }); +} +``` + +The proposed `template_parameter_list(rt)` primitive reflects (as a spliceable `std::meta::info` object) the parameter list of a template reflected by `rt`. Splicing the resulting reflection handle results in a copy of the parameter list, with commas and ellipses inserted as appropriate. The names of the parameters are autogenerated (user code can consider them unambiguous) and in error messages the compiler may stylize them as `_T0`, `_T1`, `__p0`, `__p1`, etc. Given that the parameter names are specific to a given declaration, they can be used only in a definition obtained from `declaration_of` for the same declaration. + +Similarly, the proposed `parameter_list(rf)` metafunction offers a comma-separated list of the function or function template reflected by `rf`, suitable for splicing. Two important additional cases are also supported: `forward_parameter_list` (used above) and `move_parameter_list`, which pass the parameters through `std::forward` and `std::move`, respectably. Splicing the `std::meta::info` returned by these functions allows generated code to access parameters of a function created by using `declaration_of(rf)`, as illustrated above. Again, the names of the parameters are compiler-generated and can be considered world-unique. + +The initial proposal [@P3420R0] allowed accessing all parts of a template definition as construct sequences. However, for some implementations performing such extrication was deemed difficult. Also, separating parts of a function declaration made it difficult to define how names are looked up after splicing. The current approach—reflecting entire declarations, creating new declarations from the reflection of existing ones, and accessing parameters implicitly—eschews many of the original proposal's issues without giving away flexibility. -Producing a printable token stream on demand remains important for debugging purposes. We consider it a quality of implementation matter. +We paid special attention to preserve the spirit and design style of [@P2996R7] and to ensure seamless interoperation with it. ## Example: Logging and Forwarding -As a conceptual stylized example, consider the matter of creating a function template `logged::func` for any free function template `func` in such a way that `logged::func` has the same signature and semantics as `func`, with the added behavior that it logs the call and its parameters prior to execution. We illustrate the matter for function templates because it is more difficult (and more complete) than the case of simple functions. +As a more elaborate example, consider the matter of creating a function template `logged::func` for any free function template `func` in such a way that `logged::func` has the same signature and semantics as `func`, with the added behavior that it logs the call and its parameters prior to execution. We illustrate the matter for function templates because it is more difficult (and more complete) than the case of simple functions. ```cpp +struct Widget {}; // for exposition purposes + template requires std::is_copy_constructible_v -void fun(const T& value) { ... } +void fun(const T& value, Widget&) { ... } + +template +void logger(const Ts&.. values); +// Introspection will add declarations here. +namespace logged { + struct Widget {}; // for exposition purposes +} + +// Given a function func, create a function logged::func that logs arguments. consteval void make_logged(std::meta::info f) { - queue_injection(^^{ - namespace logged { - \tokens(copy_signature(f, name_of(f))) { - logger("Calling " + name_of(f) + " with arguments: ", \tokens(params_of(f))); - return \tokens(make_fwd_call(f)); - } - } // end manespace logged - }); + using namespace std::meta; + namespace_inject(^^::logged, ^^{ + \(declaration_of(f)) { + logger("Calling " + identifier_of(f) + "(", \(parameter_list(f)), ")"); + return [:f:]<\(template_parameter_list(f))>(\(forward_parameter_list(f))); + } + }); } consteval { - make_logged(^^fun); + make_logged(^^fun); } // Equivalent hand-written code: // namespace logged { // template // requires std::is_copy_constructible_v -// void fun(const T& value) { -// logger("Call to ", "fun", " with arguments: ", value); -// return fun(std::forward(value)); +// void fun(const T& __p0, const ::Widget& __p1) { +// logger("Call to ", "fun", "(", _p0, __p1, ")"); +// return ::fun(std::forward(__p0), std::forward(__p1)); // } // } ``` -We identify a few high-level metafunctions that facilitate such manipulation of template declarations. `copy_signature(info f, std::string_view name)` produces the token sequence corresponding to the signature of the function template reflected by `f`, under a new name. (The example above uses the same name in a distinct namespace.) The template parameters, `requires` clause, function parameters, and return type are all part of the copied tokens. (In the general case, we do want to have separate access to each element for customization purposes, so `copy_signature` would combine more fine-grained primitives.) Next, `params_of(f)` produces a comma-separated list of the parameters of `f`, with pack expansion suffix where applicable. Finally, `make_fwd_call` creates the tokens of a call to the function reflected by `f` with the appropriate insertion of calls to `std::forward`, again with pack expansion suffix where appropriate. +The code uses `std::meta::namespace_inject` as defined in [@P3294R2] to create a function definition from a token sequence in a given namespace, in this case `logged`. The generative side of the code uses again `std::meta::declaration_of` to get a reflection handle corresponding to the declaration of the function template reflected by `f`. The template parameters, `requires` and `noexcept` clauses (if present), function parameters, and return type are all part of the returned reflection handle. Most importantly, the declaration can be modified and spliced back into code. + +Names used in `declaration_of` are handled in a hygienic manner that prevents unintended binding. All non-dependent names are looked up in the original declaration. Thus, although namespace `logged` defines a type called `Widget`, the declaration returned by `declaration_of` uses the same `Widget` as the original declaration (which is why we used `::Widget` in the comment representing equivalent code). Dependent names are, as expected, not looked up. The name of the function itself also carries information about what scope `f` was defined in (in our example the global namespace), but the primitive `namespace_inject` ignores that information and injects a new function of the same name in the given namespace. + +The rest of the `make_logged` metafunction uses `template_parameter_list`, `parameter_list`, and `forward_parameter_list` in concert to first issue a call to `logger` and then forward the call to the original function. + +To recap, we propose generating new functions (or function templates) from existing ones by splicing `declaration_of` for copying the function prototype, followed by the new function body as a construct sequence. To access and pass around template parameters and function parameters (when appropriate), we propose the helper metafunctions `template_parameter_list`, `parameter_list`, and `forward_parameter_list`. These primitives allow manipulating parameters without needing to name them explicitly. + +## Example: `logging_vector` + +Consider a more elaborate example of a class template `logging_vector`, which defines a functional equivalent of `std::vector` but that uses a function `logger` to log all calls to its member functions. Here, we need to iterate the public members of `std::vector` and insert appropriate copies of declarations. + +```cpp +template > +class logging_vector { +private: + std::vector data; + using namespace std::meta; +public: + consteval { + template for (constexpr auto r : get_public_members(^^std::vector)) { + if (is_type_alias(r) || is_class_type(r)) { + queue_injection(^^{ using \id(identifier_of(r)) = \(r); }); + } else if (is_function(r) && !is_special_member_function(r)) { + queue_injection(^^{ + \(declaration_of(r)) { + logger("Calling ", display_string_of(^^std::vector), "::", + identifier_of(r), "(", \(parameter_list(r)), ")"); + return data.[:r:](\(forward_param_list_of(r))); + } + }); + } else if (is_function_template(r) && !is_constructor(r)) { + queue_injection(^^{ + \(declaration_of(r)) { + logger("Calling ", display_string_of(^^std::vector), "::", + identifier_of(r), "(", \(parameter_list(r)), ")"); + return data.template [:r:]<\(template_parameter_list(r))>( + \(forward_parameters_list(r)) + ); + } + }); + } else if (is_constructor(r)) { + queue_injection(^^{ + \(declaration_of(r)) : data(\(forward_parameters_list(r))) {} + }); + } else { + // Ignore other nested declarations. + } + } + } +}; +``` + +(Refer to [@P2996R7] for metafunctions `get_public_members`, `is_type_alias`, `is_class_type`, `is_function`, `is_function_template`, and `identifier_of`, of which semantics should be intuitive as we discuss the context.) Here, as the code iterates declarations in class `std::vector` (an instance of `std::vector`, not the template class itself, which simplifies a few aspects of the example), it takes action depending on the nature of the declaration found. For `typedef` (or equivalent `using`) declarations and for nested class declaration, a `using` declaration of the form `using` _name_ `= std::vector::name;` is issued for each corresponding _name_ found in `std::vector`. + +If the iterated declaration introduces a non-special function (filtered with the test `is_function(r) && !is_special_member_function(r)`), a new function is defined with the same signature. The definition issues a call to `logger` passing the parameters list, followed by a forwarding action to the original function for the `data` member. + +If the iterated declaration is that of a non-special function template (filtered by testing `is_function_template(r) && !is_constructor(r)`), the code synthesizes a function template. Although the the template declaration has considerably more elements, the same `declaration_of` metafunction primitive is used. In the synthesized declaration, all names are already looked up in the context of the `std::vector` instantiation; for example, `begin()` returns type `std::vector::iterator` and not `logging_vector::iterator` (even if that name has been declared as an alias). + +Finally, if the iterated declaration is a constructor, it is handled separately because the generated code has a distinct syntax. For constructors (whether templated or not), the generated code simply forwards all parameters to the constructor of the payload. + +For seeing how variadic parameters are handled, consider the function `emplace` that is declared as a variadic function template with the signature `template iterator emplace(const_iterator, A&&...);`. For the reflection of `emplace`, calling `template_parameter_list` returns the token sequence `^^{ _T0... }` (exact name chosen by the implementation), which is suitable for passing to the instantiation of another template. Correspondingly, `parameter_list` returns the token sequence `^^{ __p0, __p1... }`. This token sequence can be expanded anywhere the arguments of the called function are needed. + +It is worth noting that the forwarding call syntaxes `data.[:r:](...)` (for regular functions) and `data.template [:r:]<...>(...)` (for function templates) are already defined with the expected semantics in [@P2996R7], and work properly in the prototype implementations. The only added elements are the metafunctions `template_parameter_list`, `parameter_list`, and `forward_parameter_list`, which ensure proper passing down of parameter names from the synthesized function to to the corresponding member function. + +### Renaming and Projection + +The `logging_vector` example has an issue related to member functions of `std::vector` that refer to `std::vector` itself in their signature, such as `void swap(std::vector&)`. The example as written above will generate code equivalent to the following: + +```cpp +template +class logging_vector { + ... + void swap(std::vector& other) { + logger("Calling ", "std::vector", "::", + "swap", "(", other, ")"); + return data.swap(std::forward(other)); + } + ... +}; +``` + +The declaration is technically correct, but not what was intended. We need to define `swap` as taking another instance of `logging_vector`, i.e., `void swap(logging_vector&)`: + +```cpp + void swap(logging_vector& other) { + logger("Calling ", "std::vector", "::", + "swap", "(", other.data, ")"); + return data.swap(std::forward(other).data); + } +``` + +There are two fundamental transformation we need to perform to morph the original `vector::swap` into its desired counterpart inside `logging_vector`: + +- *Replacement:* for all parameters in the signature that have type `std::vector`, change their type to `logging_vector`; and +- *Projection:* for all parameters `p` in the function body that have type `std::vector`, replace `p` with `p.data`. + +For replacement, we propose a primitive metafunction `replace` that replaces a type with another throughout a declaration, potentially in multiple places, in a manner reminiscent to the [alpha renaming](https://opendsa.cs.vt.edu/ODSA/Books/PL/html/AlphaConversion.html) notion found in lambda calculus. To that end we define `replace` for declarations, with the signature: + +```cpp +info replace(info declaration, info replace_this, info with_this); +``` + +The metafunction returns a new declaration with the replacements effected. To improve the `logging_vector` example above, we'd need to replace the uses of `declaration_of(f)` with `replace(declaration_of(f), ^^std::vector, ^^logging_vector))`. The construct would transform properly all declarations. + +Projection is trickier because not all parameters should be affected, only those whose type (after removing cvref qualifiers) is `std::vector`. To effect projection, user code defines a _projection function_: + +```cpp + // Inside logging_vector's definition + template + decltype(auto) project(X&& x) { + if constexpr (std::is_same_v, std::logging_vector>) { + return std::forward(x.data); + } else { + return std::forward(x); + } + } +``` + +We then propose a standard metafunction that applies a projection function to each parameter of a function: + +```cpp +info project_parameters(info declaration, info projection_function); +``` + +Armed with these artifacts, we can have reflection generate proper forwarding functions from existing member functions of `std::vector` like this: + +```cpp + // For member functions of std::vector + auto my_decl = replace(declaration_of(r), ^^std::vector, ^^logging_vector); + auto my_params = project_parameters(r, ^^project); + queue_injection(^^{ + [:\(decl):] { + logger("Calling ", identifier_of(^^std::vector), "::", + identifier_of(r), "(", \tokens(my_params), ")"); + return data.[:r:](forward_param_list_of(my_params)); + } + }); +``` -In order to be able to define metafunctions such as `copy_signature`, `params_of`, and `make_fwd_call`, we need access to the lower-level components of a template declaration. +The code for member function templates is similar. One notable detail is that `forward_param_list_of` works seamlessly on function declarations and on projected parameter lists (flexibility made possible by the uniform representation of code artifacts as `std::meta::info` objects). # Metafunctions for Template Declarations All template declarations (whether a class template or specialization thereof, a function template, an alias template, or a variable template), have some common elements: a template parameter list, an optional list of attributes, and an optional template-level `requires` clause. -If our goal were simply to splice a template declaration back in its entirety—possibly with a different name and/or with normalized parameter names—the introspection metafunction `copy_signature(^^X, "Y")` showcased above could be defined as an implementation-defined primitive that simply splices the entire declaration of template `X` to declare a new template `Y` that is (save for the name) identical to `X`. However, most often code generation needs to tweak elements of the declaration—for example, adding a conjunction to the `requires` clause or eliminating the `deprecated` attribute. Simply returning an opaque token sequence of the entire declaration and leaving it to user-level code to painstakingly parse it once again is self-defeating; therefore, we aim to identify structural components of the declaration and define primitives that return each in turn. That way, we offer unbounded customization opportunities by allowing users to mix and match elements of the original declaration with custom code. Once we have all components of the template declaration, implementing `copy_signature` as a shorthand for assembling them together is trivially easy. +If our goal were simply to splice a template declaration back in its entirety—possibly with a different name and/or with normalized parameter names—the introspection metafunction `declaration_of(f)` may seem sufficient. However, most often code generation needs to tweak elements of the declaration—for example, adding a conjunction to the `requires` clause or eliminating the `deprecated` attribute. Therefore, we aim to identify structural components of the declaration and define primitives that manipulate each in turn. That way, we offer unbounded customization opportunities by allowing users to amend the original declaration with custom code. In addition to template parameters, `requires` clause, and attributes, certain template declarations have additional elements as follows: @@ -166,28 +355,32 @@ The declarations below summarize the metafunctions proposed, with full explanati ```cpp namespace std::meta { + // Returns the reflection of the declaration of a template + consteval auto declaration_of(info) -> info; + // Replacement + consteval auto replace(info declaration, info replace_this, info with_this) -> info; + // Projection + consteval auto project_parameters(info declaration, info projection_function) -> info; + // Returns given declaration with a changed name + consteval auto set_name(info, string_view) -> info; // Multiple explicit specializations and/or partial specializations consteval auto template_alternatives_of(info) -> vector; - // Template parameter normalization prefix - constexpr string_view template_parameter_prefix; - // Template parameter list + // Template parameters vector consteval auto template_parameters_of(info) -> vector; + // Comma-separated template parameter list + consteval auto template_parameter_list(info) -> info; // Attributes - extension of semantics in P3385 consteval auto attributes_of(info) -> vector; + consteval auto add_attribute(info) -> info; + consteval auto remove_attribute(info) -> info; // Template-level requires clause - consteval auto template_requires_clause_of(info) -> info; + consteval auto set_requires_clause(info, info) -> info; + consteval auto add_requires_clause_conjunction(info, info) -> info; + consteval auto add_requires_clause_disjunction(info, info) -> info; // Is this the reflection of the primary template declaration? consteval auto is_primary_template(info) -> bool; - // Arguments of an explicit specialization or partial specialization - consteval auto specialization_arguments_of(info) -> vector; - // Bases of a class template - consteval auto template_bases_of(info) -> vector; - // Type of a data member in a class template or of a variable template - consteval auto template_data_type(info); - // Type of a data member in a class template - consteval auto template_data_initializer(info) -> info; - // Declarator part of an alias template - consteval auto alias_template_declarator(info) -> info; + // Overloads of a given function name + consteval auto overloads_of(info) -> vector; // Inline specifier present? consteval auto is_inline(info) -> bool; // cvref qualifiers and others - extensions of semantics in P2996 @@ -200,25 +393,63 @@ namespace std::meta { consteval auto has_static_linkage(info) -> bool; consteval auto is_noexcept(info) -> bool; // predicate for `noexcept` - consteval auto noexcept_of(info) -> info; + consteval auto set_noexcept_clause(info, info) -> info; + consteval auto add_noexcept_clause_conjunction(info, info) -> info; + consteval auto add_noexcept_clause_disjunction(info, info) -> info; // `explicit` present? - extension of P2996 // predicate for `explicit` - consteval auto explicit_specifier_of(info) -> info; + consteval auto set_explicit_clause(info, info) -> info; + consteval auto add_explicit_clause_conjunction(info, info) -> info; + consteval auto add_explicit_clause_disjunction(info, info) -> info; // `constexpr` present? consteval auto is_declared_constexpr(info) -> bool; // `consteval` present? consteval auto is_declared_consteval(info) -> bool; - // Function template return type - consteval auto template_return_type_of(info) -> info; - // Function template parameter normalization prefix - constexpr string_view function_parameter_prefix; - // Function parameters - consteval auto template_function_parameters_of(info) -> vector; - // Trailing `requires` - consteval auto trailing_requires_clause_of(info) -> info; - // Function template body - consteval auto body_of(info) -> info; + // Function template parameters + consteval auto parameters_of(info) -> vector; + consteval auto parameter_list(info) -> vector; + consteval auto forward_parameter_list(info) -> vector; + // + consteval auto set_trailing_requires_clause(info, info) -> info; + consteval auto add_trailing_requires_clause_conjunction(info, info) -> info; + consteval auto add_trailing_requires_clause_disjunction(info, info) -> info; +} +``` + +### `declaration_of` + +```cpp +consteval auto declaration_of(info) -> info; +``` + +Returns the reflection of the declaration of a type or template. Subsequently that reflection can be inserted as part of a token sequence. Typical uses issue a call `declaration_of` and then compute a slightly modified declaration before splicing it. + +### `set_name` + +```cpp +info set_name(info, string_view new_name); +``` + +Given a reflection, returns a reflection that is identical except for the name which is `new_name`. Example: + +```cpp +template +void fun(const T& value) { ... } + +consteval { + auto f = ^^fun; + queue_injection(^^{ + [:\(set_name(declaration_of(f), "my_fun")):] { + return [:f:]<\tokens(template_parameters_of(f))>(\tokens(forward_parameter_list(f))); + } + }); } + +// Equivalent code: +// template +// void my_fun(const T& value) { +// return fun(value); +// } ``` ### `template_alternatives_of` @@ -238,7 +469,7 @@ template <> class C {}; template class C {}; ``` -Each specialization must be accessible for reflection in separation from the others; in particular, there should be a mechanism to iterate `^^C` to reveal `info` handles for the three available variants (the primary declaration, the explicit specialization, and the partial specialization). Each specialization may contain the same syntactic elements as the primary template declaration. In addition to those, explicit specializations and partial specializations also contain the specialization's template arguments (in the code above: `` and ``, respectively), which we also set out to be accessible through reflection. +Each specialization must be accessible for reflection in separation from the others; in particular, there should be a mechanism to iterate `^^C` in the example above to reveal `info` handles for the three available variants (the primary declaration, the explicit specialization, and the partial specialization). Each specialization may contain the same syntactic elements as the primary template declaration. In addition to those, explicit specializations and partial specializations also contain the specialization's template arguments (in the code above: `` and ``, respectively), which we also set out to be accessible through reflection. Given the reflection of a class template or variable template, `template_alternatives_of` returns the reflections of all explicit specializations and all partial specializations thereof (including the primary declaration). Declarations are returned in syntactic order. This is important because a specialization may depend on a previous one, as shown below: @@ -266,39 +497,13 @@ static_assert(r.size() == 3); static_assert(r[0] == ^^C); ``` -### `template_parameters_of`, `template_parameter_prefix` +### `template_parameters_of` ```cpp vector template_parameters_of(info); -constexpr string_view template_parameter_prefix = $unspecified$; ``` -Given the reflection of a template, `template_parameters_of` returns an array of token sequences containing the template's parameters (a parameter pack counts as one parameter). Template parameter names are normalized by naming them the concatenation of `std::meta::template_parameter_prefix` and an integral counter starting at 0 and incremented with each parameter introduction, left to right. For example, `template_parameters_of(^^A)` yields the token sequences `^^{typename _T0}`, `^^{_T0 _T1}`, and `^^{auto... _T2}`. Note that the second parameter has been described as `_T0 _T1` (renaming the dependent name correctly). The template parameter prefix `std::meta::template_parameter_prefix` is an implementation-reserved name such as `"_T"` or `"__t"`. (Our examples use `"_T"`.) - -For example: - -```cpp -template class A; - -static_assert(template_parameters_of(^^A).size() == 3); -// r0 is ^^{typename _T0} -constexpr auto r0 = template_parameters_of(^^A)[0]; -// r1 is ^^{_T0 x} -constexpr auto r1 = template_parameters_of(^^A)[1]; -// r2 is ^^{auto... _T0} -constexpr auto r2 = template_parameters_of(^^A)[2]; -``` - -As noted above, an implementation may return token sequences equivalent to those in the source code, e.g. `^^{class _T0}` instead of `^^{typename _T0}`. - -Defaulted parameters, if any, are included in the token sequences returned. For example: - -```cpp -template class A; - -// r is ^^{typename _T0 = int} -constexpr auto r = template_parameters_of(^^A)[0]; -``` +Given the reflection of a template, `template_parameters_of` returns an array of token sequences containing the template's parameter names (a parameter pack counts as one parameter and is followed by `...`). To avoid name clashes, the implementation chooses unique names. (Our examples use `"_T"` followed by a number.) For function templates, it is guaranteed that `declaration_of` and `template_parameters_of` will use the same names. For example, `template_parameters_of(^^A)` may yield the token sequences `^^{_T0}`, `^^{_T1}`, and `^^{_T2...}`. For explicit specializations of class templates, `template_parameters_of` returns an empty vector: @@ -307,67 +512,79 @@ template class A; template <> class A; // r refers to A -constexpr auto r = template_alternatives_of(^^A)[1]; // definition below +constexpr auto r = template_alternatives_of(^^A)[1]; // pick specialization static_assert(template_parameters_of(r).empty()); ``` -If a non-type template parameter has a non-dependent type, the tokens returned for that parameter include the fully looked-up name. For example: +For partial specializations of class templates, `template_parameters_of` returns the parameters of the partial specialization (not those of the primary template). For example: ```cpp -namespace Lib { - struct S {}; - template class C { ... }; -} -struct S {}; // illustrates a potential ambiguity - -// r is ^^{::Lib::S _T0}, not ^^{S _T0} -constexpr auto r = template_parameters_of(^^Lib::C)[1]; +template class A; +template class A>; +constexpr auto r = template_alternatives_of(^^A)[1]; // pick specialization +static_assert(template_parameters_of(r).size() == 2); ``` -Given a class template `A` that has explicit instantiations and/or partial specializations declared, the call `template_parameters_of(^^A)` is not ambiguous—it refers to the primary declaration of the template. + +Given a class template `A` that has explicit instantiations and/or partial specializations declared, referring the template by name in the call `template_parameters_of(^^A)` is not ambiguous—it refers to the primary declaration of the template. Calling `template_parameters_of` against a non-template entity, as in `template_parameters_of(^^int)` or `template_parameters_of(^^std::vector)`, fails to evaluate to a constant expression. -### `attributes_of` +### `template_parameter_list` ```cpp -vector attributes_of(info); +consteval auto template_parameter_list(info) -> info; ``` -Returns all attributes associated with `info`. The intent is to extend the functionality of `attributes_of` as defined in [P3385](https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2024/p3385r0.html) to template declarations. We defer details and particulars to that proposal. +Returns the result of `template_parameters_of` as a comma-separated list in a token sequence. Variadics, if any, are followed by `...`. -### `template_requires_clause_of` +### `attributes_of`, `add_attribute`, `remove_attribute` ```cpp -info template_requires_clause_of(info); +vector attributes_of(info); +info add_attribute(info, info); +info remove_attribute(info, info); ``` -Given the reflection of a template, returns the template-level `requires` clause as a token sequence. The names of template parameters found in the `requires` clause are normalized. Non-dependent identifiers are returned in fully-qualified form. For example: +`attributes_of` returns all attributes associated with `info`. The intent is to extend the functionality of `attributes_of` as defined in [@P3385R0] to template declarations. + +`add_attribute(r, attr)` returns a reflection that adds the attribute `attr` to the given reflection `r`. If `r` already had the attribute, it is returned. + +`remove_attribute` returns a reflection that removes the attribute `attr` from the given reflection `r`. If `r` did now have the attribute, it is returned. + +We defer details and particulars to [@P3385R0]. + +### `set_requires_clause`, `add_requires_clause_conjunction`, `add_requires_clause_disjunction` ```cpp -struct X { ... }; -template -requires (sizeof(X) > 256 && std::is_default_constructible_v) -class A; +info set_requires_clause(info, info); +info add_requires_clause_conjunction(info, info); +info add_requires_clause_disjunction(info, info); +``` -// Returns `^^{(sizeof(::X) > 1 && ::std::is_default_constructible_v<_T0>)}` -constexpr auto r1 = template_requires_clause_of(^^A); +Given the reflection of a template, `set_requires_clause` returns a reflection of an identical template but with the `requires` clause replaced by the token sequence given in the second parameter. The existing `requires` clause in the template, if any, is ignored. Example: +```cpp template -requires (sizeof(T) > 1 && std::is_default_constructible_v) -using V = std::vector; - -// Returns `^^{(sizeof(_T0) > 1 && ::std::is_default_constructible_v<_T0>)}` -constexpr auto r2 = template_requires_clause_of(^^V); +requires (sizeof(T) > 64) +void fun(const T& value) { ... } -template -requires (std::is_convertible_v) -auto zero = T(0); +consteval { + auto f = ^^fun; + queue_injection(^^{ + [:\(set_requires_clause(set_name(declaration_of(f), "my_fun")), + ^^{ sizeof(\tokens(template_parameters_of(f)[0])) == 128 }):]; + }); +} -// Returns `^^{(::std::is_convertible_v)}` -constexpr auto r3 = template_requires_clause_of(^^zero); +// Equivalent code: +// template +// requires (sizeof(T) == 128) +// void my_fun(const T& value); ``` -Calling `template_requires_clause_of` against a template that has no such clause returns an empty token sequence. Calling `template_requires_clause_of` against a non-template, as in `template_requires_clause_of(^^int)` or `template_requires_clause_of(^^std::vector)`, fails to evaluate to a constant expression. +The functions `add_requires_clause_conjunction` and `add_requires_clause_disjunction` compose the given token sequence with the existing `requires` clause. (If no such clause exists, it is assumed to be the expression `true`.) The `add_requires_clause_conjunction` metafunction performs a conjunction (logical "and") between the clause given in the token sequence and the existing token sequence. The `add_requires_clause_disjunction` metafunction performs a disjunction (logical "or") between the clause given in the token sequence and the existing token sequence. + +Calling `set_requires_clause`, `add_requires_clause_conjunction`, or `add_requires_clause_disjunction` against a non-template, as in `set_requires_clause(^^int, ^^{true})`, fails to evaluate to a constant expression. ### `is_primary_template` @@ -386,167 +603,13 @@ static_assert(!is_primary_template(template_alternatives_of(^^A)[1])); static_assert(!is_primary_template(^^int)); ``` -### `specialization_arguments_of` - -```cpp -vector specialization_arguments_of(info); -``` - -For an explicit specialization or a partial specialization of a class template or variable template, returns that specialization's arguments. For example: - -```cpp -template class A; -template <> class A; -template class A>; -// Get the reflection of the first explicit specialization -constexpr auto r1 = template_alternatives_of(^^A)[1]; -// Get the argument of that specialization, i.e. ^^{int} -constexpr auto r11 = specialization_arguments_of(r1)[0]; -// Get the reflection of the second explicit specialization -constexpr auto r2 = template_alternatives_of(^^A)[2]; -// Get the arguments of that specialization, i.e. ^^{std::tuple} -constexpr auto r21 = specialization_arguments_of(r2)[0]; -``` - -If an explicit specialization uses default arguments, those will be made part of the return of `specialization_arguments_of`, even if they are not syntactically present in the source code: - -```cpp -template class A; -// Explicitly specialize A. -template <> class A<>; -// Get the explicit specialization -constexpr auto r = template_alternatives_of(^^A)[1]; -// r1 will be ^^{int} -constexpr auto r1 = specialization_arguments_of(r)[0]; -``` - -Note that `specialization_arguments_of` may return an empty vector. For example: - -```cpp -template class A; -template <> class A<>; -constexpr auto r = template_alternatives_of(^^A)[1]; -// No specialization arguments present -static_assert(specialization_arguments_of(r).empty()); -``` - -The metafunction `specialization_arguments_of` has a similar role but is qualitatively different from `template_arguments_of` in P2996 because the latter returns resolved entities (types, values, or template names), whereas the former returns token sequences of the arguments. - -Non-dependent types returned are always returned in fully namespace-qualified form. Example: - -```cpp -namespace N { - struct S {}; - template class A; - // Explicitly specialize A. - template <> class A; -} -struct S {}; // to illustrate a possible ambiguity - -// Get the explicit specialization -constexpr auto r = template_alternatives_of(^^N::A)[1]; -// r1 will be ^^{::N::S}, not ^^{S} -constexpr auto r1 = specialization_arguments_of(r)[0]; -``` - -If `specialization_arguments_of` is called with an `info` that is not an explicit template specialization, the call fails to resolve to a constant expression. - - -### `template_bases_of` - -```cpp -vector template_bases_of(info); -``` - -Returns the bases of a class template, in declaration order, as token sequences. Access specifiers are part of the returned tokens in such a way that reassembling the result of `template_bases_of` as a comma-separated list produces a valid base classes specifier. Example: - -```cpp -class B1 { ... }; -template class B2 { ... }; -template class C : public B1, public B2 { ... }; - -// Contains ^^{public ::B1} -constexpr auto r1 = template_bases_of(^^C)[0]; -// Contains ^^{public ::B2<_T0>} -constexpr auto r2 = template_bases_of(^^C)[1]; -``` - -### `template_data_type` - -```cpp -info template_data_type(info); -``` - -Given the reflection of a data member of a class template or of a variable template, returns the type of the member as a token sequence. Returning a type reflection, as is the case for P2996's `type_of`, is not possible in most cases. Example: - -```cpp -template -struct A { - const T& x; -}; -template -auto zero = T(0); - -// Call P2996's nonstatic_data_members_of extended for templates -// r1 contains ^^{_T0 const&} -constexpr auto r1 = template_data_type(nonstatic_data_members_of(^^C)[0]); -// r2 contains ^^{_T0(0)} -constexpr auto r2 = template_data_type(^^zero); -``` - -### `template_data_initializer` - -```cpp -info template_data_initializer(info); -``` - -Returns the initializer part of a data member of a class template or of a variable template declaration, as a token sequence. Example: - -```cpp -template -struct S { - T x = T("hello"); - T y; -} - -template -auto zero = T(0); - -// r1 contains `^^{_T0("hello")}` -constexpr auto r1 = template_data_initializer(nonstatic_data_members_of(^^S)[0]); -// r2 contains `^^{}` -constexpr auto r2 = template_data_initializer(nonstatic_data_members_of(^^S)[1]); -// r3 contains `^^{_T0(0)}` -constexpr auto r3 = template_data_initializer(^^zero); - -``` - -If `template_data_initializer` is called with an `info` that is not an alias template, the call fails to resolve to a constant expression. - -### `alias_template_declarator` - -```cpp -info alias_template_declarator(info); -``` - -Returns the declarator part of an alias template declaration, as a token sequence. Example: - -```cpp -template using V = std::vector; - -// Returns `^^{::std::vector<_T0, ::std::allocator<_T0>>}` -constexpr auto r = alias_template_declarator(^^V); -``` - -If `alias_template_declarator` is called with an `info` that is not an alias template, the call fails to resolve to a constant expression. - ### `overloads_of` ```cpp vector overloads_of(info); ``` -Returns an array of all overloads of a given function, usually passed in as a reflection of an identifier (e.g., `^^func`). All overloads are included, template and nontemplate. To distinguish functions from function templates, `is_function_template` (P2996) can be used. Example: +Returns an array of all overloads of a given function, usually passed in as a reflection of an identifier (e.g., `^^func`). All overloads are included, template and nontemplate. To distinguish functions from function templates, `is_function_template` ([@P2996R7]) can be used. Example: ```cpp void f(); @@ -585,30 +648,56 @@ bool has_static_linkage(info) bool is_noexcept(info); ``` -Extends the homonym metafunctions defined by P2996 to function templates. +Extends the homonym metafunctions defined by [@P2996R7] to function templates. For `is_noexcept` and `is_explicit`, `true` is returned if the `noexcept` and `explicit` clause, respectively, is predicated. -### `noexcept_of` +### `set_noexcept_clause`, `add_noexcept_clause_conjunction`, `add_noexcept_clause_disjunction` ```cpp -info noexcept_of(info); +info set_noexcept_clause(info, info); +info add_noexcept_clause_conjunction(info, info); +info add_noexcept_clause_disjunction(info, info); ``` -Returns the `noexcept` clause of a function or function template, if any, as a token sequence, as follows: +Given the reflection of a template, `set_noexcept_clause` returns a reflection of an identical template but with the `noexcept` clause replaced by the token sequence given in the second parameter. The existing `noexcept` clause in the template, if any, is ignored. Example: -- if no `noexcept` clause is present, returns the empty token sequence; -- if `noexcept` is unconditional, returns the token sequence `^^{noexcept}`; -- if `noexcept` is conditional (e.g., `noexcept(expression)`), returns the conditional noexcept in tokenized form; -- if `noexcept` would not be applicable (e.g., `noexcept(^^int)`), the call fails to evaluate to a constant expression. +```cpp +template +noexcept (sizeof(T) > 64) +void fun(const T& value) { ... } + +consteval { + auto f = ^^fun; + queue_injection(^^{ + [:\(set_noexcept_clause(set_name(declaration_of(f), "my_fun")), + ^^{ sizeof(\tokens(template_parameters_of(f)[0])) == 128 }):]; + }); +} + +// Equivalent code: +// template +// noexcept (sizeof(T) == 128) +// void my_fun(const T& value); +``` + +The functions `add_noexcept_clause_conjunction` and `add_noexcept_clause_disjunction` compose the given token sequence with the existing `noexcept` clause. (If no such clause exists, it is assumed to be the expression `true`.) The `add_noexcept_clause_conjunction` metafunction performs a conjunction (logical "and") between the clause given in the token sequence and the existing token sequence. The `add_noexcept_clause_disjunction` metafunction performs a disjunction (logical "or") between the clause given in the token sequence and the existing token sequence. -### `explicit_specifier_of` +Calling `set_noexcept_clause`, `add_noexcept_clause_conjunction`, or `add_noexcept_clause_disjunction` against a non-template, as in `set_noexcept_clause(^^int, ^^{true})`, fails to evaluate to a constant expression. + +### `set_explicit_clause`, `add_explicit_clause_conjunction`, `add_explicit_clause_disjunction` ```cpp -info explicit_specifier_of(info); +info set_explicit_clause(info, info); +info add_explicit_clause_conjunction(info, info); +info add_explicit_clause_disjunction(info, info); ``` -Returns the `explicit` specifier of the given `info` of a constructor. If the constructor has no explicit specifier, returns the empty sequence. If the constructor has an unconditional `explicit` specifier, returns the token sequence `^^{explicit}`. If the constructor has a predicated `explicit(constant-expression)`, returns the token sequence `^^{explicit(constant-expression)}`. All nondependent identifier in the token sequence are fully namespace-qualified. +Given the reflection of a template, `set_explicit_clause` returns a reflection of an identical template but with the `explicit` clause replaced by the token sequence given in the second parameter. The existing `explicit` specifier in the template, if any, is ignored. + +The functions `add_explicit_clause_conjunction` and `add_explicit_clause_disjunction` compose the given token sequence with the existing `explicit` clause. (If no such clause exists, it is assumed to be the expression `true`.) The `add_explicit_clause_conjunction` metafunction performs a conjunction (logical "and") between the clause given in the token sequence and the existing token sequence. The `add_explicit_clause_disjunction` metafunction performs a disjunction (logical "or") between the clause given in the token sequence and the existing token sequence. + +Calling `set_explicit_clause`, `add_explicit_clause_conjunction`, or `add_explicit_clause_disjunction` against a non-template, as in `set_explicit_clause(^^int, ^^{true})`, fails to evaluate to a constant expression. ### `is_declared_constexpr`, `is_declared_consteval` @@ -619,63 +708,68 @@ bool is_declared_consteval(info); Returns `true` if and only if `info` refers to a declaration that is `constexpr` or `consteval`, respectively. In all other cases, returns `false`. -### `template_return_type_of` +### `parameters_of` ```cpp -info template_return_type_of(info); +vector parameters_of(info); ``` -Returns the return type of the function template represented by `info`. This function is similar with `return_type_of` in P3096. However, one key difference is that `return_type_of` returns the reflection of a type, whereas `template_return_type_of` returns the token sequence of the return type (given that the type is often not known before instantiation or may be `auto&` etc). +Returns an array of token sequences containing the parameter names of the function template represented by `info`. Parameter names are chosen by the implementation. It is guaranteed that the nmes returned by `parameters_of(f)` are consistent with the names returned by `parameters_of(f)` for any reflection `f` of a function template. -### `template_function_parameters_of`, `function_parameter_prefix` +This function bears similarities with `parameters_of` in [@P3096R3]. However, one key difference is that `parameters_of` returns reflection of types, whereas `template_return_type_of` returns the token sequence of the return type (given that the type is often not known before instantiation or may be `auto&` etc). + +### `parameter_list` ```cpp -vector template_function_parameters_of(info); -constexpr string_view function_parameter_prefix; +info parameter_list(info); ``` -Returns an array of parameters (type and name for each) of the function template represented by `info`, as token sequences. Parameter names are normalized by naming them the concatenation of `std::meta::function_parameter_prefix` and an integral counter starting at 0 and incremented with each parameter introduction, left to right. Normalizing parameter names makes it easy for splicing code to generate forwarding argument lists such as the metafunctions `params_of` and `make_fwd_call` in the opening example. - -This function bears similarities with `parameters_of` in P3096. However, one key difference is that `parameters_of` returns reflection of types, whereas `template_return_type_of` returns the token sequence of the return type (given that the type is often not known before instantiation or may be `auto&` etc). +Convenience function that returns the parameters returned by `parameters_of` in the form of a comma-separated list. The result is returned as a token sequence. -### `trailing_requires_clause_of` +### `forward_parameter_list` ```cpp -info trailing_requires_clause_of(info); +info forward_parameter_list(info); ``` -Given the `info` of a function template, returns the trailing `requires` clause of the declaration as a token sequence. Example: - -```cpp -template -requires (sizeof(T) > 8) -void f() requires (sizeof(T) < 64); - -// Will contain ^^{ (sizeof(T) < 64) } -constexpr auto r = trailing_requires_clause_of(^^f); -``` +Convenience function that returns the parameters returned by `parameters_of` in the form of a comma-separated list, each passed to `std::forward` appropriately. The result is returned as a token sequence and can be spliced in a call to foward all parameters to another function. -### `body_of` +### `set_trailing_requires_clause`, `add_trailing_requires_clause_conjunction`, `add_trailing_requires_clause_disjunction` ```cpp -info body_of(info); +info set_trailing_requires_clause(info, info); +info add_trailing_requires_clause_conjunction(info, info); +info add_trailing_requires_clause_disjunction(info, info); ``` -Given the `info` of a function template, returns the function's body as a token sequence, without the top-level braces (which can be added with ease if needed). All parameter names are normalized and all non-dependent identifiers are fully qualified. Example: +Given the reflection of a template, `set_trailing_requires_clause` returns a reflection of an identical template but with the `requires` clause replaced by the token sequence given in the second parameter. The existing `requires` clause in the template, if any, is ignored. Example: ```cpp template -void f(T x) { - return x < 0 ? -x : x; +requires (sizeof(T) > 64) +void fun(const T& value) { ... } + +consteval { + auto f = ^^fun; + queue_injection(^^{ + [:\(set_trailing_requires_clause(set_name(declaration_of(f), "my_fun")), + ^^{ sizeof(\tokens(template_parameters_of(f)[0])) == 128 }):]; + }); } -// Will contain ^^{ return _p0 < 0 ? -_p0 : _p0; } -constexpr auto r = body_of(^^f); +// Equivalent code: +// template +// requires (sizeof(T) == 128) +// void my_fun(const T& value); ``` +The functions `add_trailing_requires_clause_conjunction` and `add_trailing_requires_clause_disjunction` compose the given token sequence with the existing `requires` clause. (If no such clause exists, it is assumed to be the expression `true`.) The `add_trailing_requires_clause_conjunction` metafunction performs a conjunction (logical "and") between the clause given in the token sequence and the existing token sequence. The `add_trailing_requires_clause_disjunction` metafunction performs a disjunction (logical "or") between the clause given in the token sequence and the existing token sequence. + +Calling `set_trailing_requires_clause`, `add_trailing_requires_clause_conjunction`, or `add_trailing_requires_clause_disjunction` against a non-template, as in `set_trailing_requires_clause(^^int, ^^{true})`, fails to evaluate to a constant expression. + # Metafunctions for Iterating Members of Class Templates -The metafunctions described above need to be complemented with metafunctions that enumerate the contents of a class template. Fortunately, P2996 already defines number of metafunctions for doing so: `members_of`, `static_data_members_of`, `nonstatic_data_members_of`, `nonstatic_data_members_of`, `enumerators_of`, `subobjects_of`. To these, P2996 adds access-aware metafunctions `accessible_members_of`, `accessible_bases_of`, `accessible_static_data_members_of`, `accessible_nonstatic_data_members_of`, and `accessible_subobjects_of`. Here, we propose that these functions are extended in semantics to also support `info` representing class templates. The reflections of members of class templates, however, are not the same as the reflection of corresponding members of regular classes; +The metafunctions described above need to be complemented with metafunctions that enumerate the contents of a class template. Fortunately, [@P2996R7] already defines number of metafunctions for doing so: `members_of`, `static_data_members_of`, `nonstatic_data_members_of`, `nonstatic_data_members_of`, `enumerators_of`, `subobjects_of`. To these, P2996 adds access-aware metafunctions `accessible_members_of`, `accessible_bases_of`, `accessible_static_data_members_of`, `accessible_nonstatic_data_members_of`, and `accessible_subobjects_of`. Here, we propose that these functions are extended in semantics to also support `info` representing class templates. The reflections of members of class templates, however, are not the same as the reflection of corresponding members of regular classes; One important addendum to P2996 that is crucial for code generation is that when applied to members, these discovery metafunctions return members in lexical order. Otherwise, attempts to implement `identity` will fail for class templates. Consider: @@ -687,13 +781,11 @@ struct Container { } ``` -The reflections of template components returned by `members_of` and others can be inspected with template-specific primitives, not the primitives defined in P2996. For example, the data members returned by `nonstatic_data_members_of` can be inspected with `template_data_type` and `template_data_initializer`, but not with `offset_of`, `size_of`, `alignment_of`, or `bit_size_of` because at the template definition time the layout has not been created. - # Future Work -This proposal is designed to interoperate with [@P2996R7], [@P3157R1], [@P3294R2], and [@P3385R0]. Changes in these proposals may influence this proposal. Also, certain metafunctions proposed herein (such as `is_inline`) may migrate to these proposals. +Like [@P2996R7], this proposal is designed to grow by accretion. Future revisions will add more primitives that complement the existing ones and improve the comprehensiveness of the reflection facility for templates. -The normalization scheme for template parameters and function parameters is rigid. We plan to refine it in future revisions of this document. +This proposal is designed to interoperate with [@P2996R7], [@P3157R1], [@P3294R2], and [@P3385R0]. Changes in these proposals may influence this proposal. Also, certain metafunctions proposed herein (such as `is_inline`) may migrate to these proposals. --- references: @@ -735,4 +827,4 @@ references: month: 09 day: 18 URL: https://cppcon2024.sched.com/event/1gZhJ/reflection-is-not-contemplation ---- +--- \ No newline at end of file diff --git a/md/wg21 b/md/wg21 index f5423982..750e3d91 160000 --- a/md/wg21 +++ b/md/wg21 @@ -1 +1 @@ -Subproject commit f542398258bded5490e97d1975f5cd2e78aa78eb +Subproject commit 750e3d9169aa258bc9025d54fdc061cba235d45b