Date: Fri, 10 Dec 2021 00:07:19 +0100
Thinking about all this makes me even more convinced that we really could
use something like reflection TS2 that would instead of a
class-template-based API implement a mostly function-template-based
constexpr API and write a really large body of use-cases and examples on
top of that.
My immediate worries are:
1) We are trying to get rid of TMP as a metaprogramming paradigm while not
having an adequate replacement. Consteval is fine in some scenarios but
type (and template) manipulation is a major part and use case of
metaprogramming. I don't see how any current pure-consteval-based
functional API supports that.
2) We have not thought through all the consequences of a purely consteval
reflection API and how that would integrate into bigger MP frameworks and
we are making assumptions without any proof since we don't have a
*properly* working consteval reflection API that we can play with.
3) We are trying to replace templates with something either less expressive
and thus are wittingly or unwittingly sacrificing valid use-cases (which
would be extremely disappointing), or with something that eventually will
be as powerful as templates, but also potentially as complex and slow to
compile only with different syntax, instead of fixing the perceived
problems with templates.
4) We maybe are forgetting what the point of the reified metaobjects was:
to be able to pass them around in metaprogramming algorithm compositions as
parameters and return values, to be able to store them in "variables",
because we cannot do that with many of the base-level entities that are not
reified in C++ (namespaces, constructors, functions, base class specifiers,
etc., etc.)
Having messed with TMP for ~15 years and having implemented reflection in
clang 3 times now, I *do* understand what the issues of type-based
reflection are.
I just don't see how meta::info fixes that without creating big issues on
its own and I would hate to see reflection blocked because of this maybe
for another decade or more until we sort them out.
On Thu, Dec 9, 2021 at 11:07 PM David Rector <davrec_at_[hidden]> wrote:
>
>
> On Dec 9, 2021, at 1:20 PM, Matus Chochlik via SG7 <sg7_at_[hidden]>
> wrote:
>
> On Thu, Dec 9, 2021 at 10:43 AM Jean-Baptiste Vallon Hoarau via SG7 <
> sg7_at_[hidden]> wrote:
>
>> Besides the thing that Peter mentioned in another response, something
>>> like this didn't work either when I played with the meta::info
>>> implementation:
>>> ```
>>> auto unreflect_type(meta::info mo) {
>>> return std::type_identity<typename [: mo :]>{};
>>> }
>>> ```
>>> i.e the return type cannot depend on a non-template meta::info value.
>>> You basically have to create a separate consteval function that creates and
>>> returns the metaobject. This limits how the metaobjects are passed around
>>> in a composition of metaprogramming algorithms. You probably can
>>> work-around this by making all the metaprogramming algorithms templates,
>>> but is sort-of defeats the thing where all metaobjects are of a single type
>>> (meta::info) and In the end it's almost the same as what the "mirror"
>>> header does.
>>>
>>
>> Yes, but this is by design. meta::info objects as function parameters
>> belongs to (just like other functions parameters) the evaluation-time
>> world, not instantiation-time. Indeed metaprogramming with the meta::info
>> proposal implies that, if the output of your metaprogramming algorithm
>> depends on the type of your reflection, then it must return something which
>> must then be injected in the context where that value is an
>> instantiation-time construct.
>>
>
> So IIUC the difference that we're talking about is the following:
> ```
> // type-based metaobjects ---------------------------
>
> template <typename T>
> string_view classify(type_identity<T>) { return "a type"; }
> template <typename T>
> string_view classify(type_identity<T*>) { return "a pointer type"; }
> template <typename T>
> string_view classify(type_identity<unique_ptr<T>>) { return "a smart
> pointer"; }
>
> auto transform(auto mo) {
> return do_(something(complicated(with(mo))));
> }
>
> auto get_type_id(auto mo) {
> return
> type_identity<do_t<something_t<here_t<unreflect(transform(mo))>>>>{};
> }
>
> string_view my_algo(auto mo) {
> return classify(get_type_id(mo));
> }
>
> // meta::info metaobjects ------------------------
>
> template <typename T>
> consteval string_view classify(type_identity<T>) { return "a type"; }
> // ...
>
> consteval meta::info transform(meta::info mo) {
> return do_(something(complicated(with(mo))));
> }
>
> consteval string_view my_algo(meta::info mo) {
> return classify(type_identity<do_t<something_t<here_t<[: mo :]>>>>{});
> }
> ```
> A function like the `get_type_id` above cannot be implemented in pure
> consteval. This is IMO a pretty big limitation if the thing that is done in
> `get_type_id` is really complex and it is used in many places.
> (Actually I'm not even sure if the `my_algo` function could be implemented
> without being a template, because it depends on the consteval argument.
> I'll have to try the meta::info implementation).
>
>
>> This does not defeat the purpose of operating over uniformly typed
>> values. For example, imagine a meta-function which generates the code
>> needed to serialize any value. With the meta::info proposal, the whole
>> meta-function never needs to be instantiated, simply evaluated, and then
>> the returned fragment is injected (a task essentially equivalent to
>> instantiation) into the caller's context.
>>
> I've heard this reasoning before and I'm still not sure what advantages
> does this bring over template function instantiation, since as you say
> (evaluation + injection = almost like instantiation).
>
>
> Injection does not intrinsically require instantiation; injections are
> instantiated and evaluated only when an enclosing template is instantiated
> like anything else. One can use injection statements in ordinary control
> flow statements, since all injectable entities will have the same type
> (whether strings or meta::infos or a fragment type specific to that
> injection context). Whereas, with instantiated reflections some facility
> like TMP or `template for` is required to iterate.
>
> That said, while I very much support getting away from template
> instantiation-based reflection in the long run, I think the fact that
> meta::info vs. template instantiations is an *interface/language* issue,
> rather than an *implementation* question, suggests a more general issue the
> language should address: need templates & concepts really be syntactically
> distinct from types, anywhere in the language?
>
> Consider my old clang-based reflection implementation, which reflects the
> clang interface, only as template instantiations a la Matus’s approach, but
> object-oriented. That is to say: a class, represented as a `CXXRecordDecl`
> in clang, would be reflected to the user as a `CXXRecordDecl<uintptr_t>`.
>
> For reference here is the (very large) library header providing the
> interface:
> https://raw.githubusercontent.com/drec357/clang-meta/master/llvm/tools/clang/tools/reflection-src-generator/generated_files/client_reflection_impl.hpp
> .
>
> By defining the right templated function overloads, one can almost achieve
> the same syntax as one would use in clang; e.g.
>
> ```
> template for (auto D :
> reflexpr(MyClassTemplate)->getTemplatedDecl()->decls()) {
> if constexpr (D->isImplicit())
> continue;
> if constexpr (auto_ FD = dyn_cast<FieldDecl>(D)) {
> auto QT = FD->getType();
> if (QT->isDependentType())
> meta::debug("Field named ", FD->getQualifiedNameAsString(), " has a
> dependent type");
> } else {
> D->dump(); //displays at compile-time
> meta::error( D->getBeginLoc()
> , "Unhandled Decl kind; see dump"
> ,
> user::FixItHint::CreateRemoval(user::SourceRange(D->getBeginLoc(),
> D->getEndLoc()));
> );
> }
> }
> ```
>
> What are the *syntax* differences between this and the ordinary clang
> interface? How could they be minimized?
>
> 1. `template for` instead of `for`
> 2. the ubiquitous need for auto types
>
> 1 can be handled simply; I don’t see why we couldn't get rid of `template
> for` syntax, instead using `for`, and simply ask that the compiler perform
> compile-time expansion when the type of the range variable is tuple-like.
>
> As for 2, suppose one could use templates and concepts just like types, a
> la:
> ```
> namespace meta::clang {
>
> template<uintptr_t metaobjptr>
> class DeclContext {...};
>
> template<uintptr_t metaobjptr>
> class CXXRecordDecl {...};
>
> // CXXRecordDecl is a templates so is treated like `auto`.
> template<typename T>
> CXXRecordDecl *getClassReflection() { return reflexpr(T); }
>
> // DeclContext and CXXRecordDecl are templates so are treated like
> `auto`;
> // A function template will be implicitly generated from this code.
> DeclContext *getParentOf(CXXRecordDecl *D) { return D->getDeclContext(); }
> }
> ```
> (I am not up to date on the latest thinking re: concepts etc, and surely
> this has been discussed at some point and so I would appreciate any links
> to papers that bear on this.)
>
> If the language allowed for both of these changes, I believe there would
> be no need to specify how the compiler reflects information. I.e. a
> compiler could use a meta::info type, or an implicit meta::info<intptr_t>
> template *which is always referenced as meta::info* — i.e. essentially the
> same way Matus reflects information. (Indeed perhaps the template-ness of
> an entity could be private information, to an extent.)
>
> Dave
>
>
>
>
>
>
>
>
>
>
use something like reflection TS2 that would instead of a
class-template-based API implement a mostly function-template-based
constexpr API and write a really large body of use-cases and examples on
top of that.
My immediate worries are:
1) We are trying to get rid of TMP as a metaprogramming paradigm while not
having an adequate replacement. Consteval is fine in some scenarios but
type (and template) manipulation is a major part and use case of
metaprogramming. I don't see how any current pure-consteval-based
functional API supports that.
2) We have not thought through all the consequences of a purely consteval
reflection API and how that would integrate into bigger MP frameworks and
we are making assumptions without any proof since we don't have a
*properly* working consteval reflection API that we can play with.
3) We are trying to replace templates with something either less expressive
and thus are wittingly or unwittingly sacrificing valid use-cases (which
would be extremely disappointing), or with something that eventually will
be as powerful as templates, but also potentially as complex and slow to
compile only with different syntax, instead of fixing the perceived
problems with templates.
4) We maybe are forgetting what the point of the reified metaobjects was:
to be able to pass them around in metaprogramming algorithm compositions as
parameters and return values, to be able to store them in "variables",
because we cannot do that with many of the base-level entities that are not
reified in C++ (namespaces, constructors, functions, base class specifiers,
etc., etc.)
Having messed with TMP for ~15 years and having implemented reflection in
clang 3 times now, I *do* understand what the issues of type-based
reflection are.
I just don't see how meta::info fixes that without creating big issues on
its own and I would hate to see reflection blocked because of this maybe
for another decade or more until we sort them out.
On Thu, Dec 9, 2021 at 11:07 PM David Rector <davrec_at_[hidden]> wrote:
>
>
> On Dec 9, 2021, at 1:20 PM, Matus Chochlik via SG7 <sg7_at_[hidden]>
> wrote:
>
> On Thu, Dec 9, 2021 at 10:43 AM Jean-Baptiste Vallon Hoarau via SG7 <
> sg7_at_[hidden]> wrote:
>
>> Besides the thing that Peter mentioned in another response, something
>>> like this didn't work either when I played with the meta::info
>>> implementation:
>>> ```
>>> auto unreflect_type(meta::info mo) {
>>> return std::type_identity<typename [: mo :]>{};
>>> }
>>> ```
>>> i.e the return type cannot depend on a non-template meta::info value.
>>> You basically have to create a separate consteval function that creates and
>>> returns the metaobject. This limits how the metaobjects are passed around
>>> in a composition of metaprogramming algorithms. You probably can
>>> work-around this by making all the metaprogramming algorithms templates,
>>> but is sort-of defeats the thing where all metaobjects are of a single type
>>> (meta::info) and In the end it's almost the same as what the "mirror"
>>> header does.
>>>
>>
>> Yes, but this is by design. meta::info objects as function parameters
>> belongs to (just like other functions parameters) the evaluation-time
>> world, not instantiation-time. Indeed metaprogramming with the meta::info
>> proposal implies that, if the output of your metaprogramming algorithm
>> depends on the type of your reflection, then it must return something which
>> must then be injected in the context where that value is an
>> instantiation-time construct.
>>
>
> So IIUC the difference that we're talking about is the following:
> ```
> // type-based metaobjects ---------------------------
>
> template <typename T>
> string_view classify(type_identity<T>) { return "a type"; }
> template <typename T>
> string_view classify(type_identity<T*>) { return "a pointer type"; }
> template <typename T>
> string_view classify(type_identity<unique_ptr<T>>) { return "a smart
> pointer"; }
>
> auto transform(auto mo) {
> return do_(something(complicated(with(mo))));
> }
>
> auto get_type_id(auto mo) {
> return
> type_identity<do_t<something_t<here_t<unreflect(transform(mo))>>>>{};
> }
>
> string_view my_algo(auto mo) {
> return classify(get_type_id(mo));
> }
>
> // meta::info metaobjects ------------------------
>
> template <typename T>
> consteval string_view classify(type_identity<T>) { return "a type"; }
> // ...
>
> consteval meta::info transform(meta::info mo) {
> return do_(something(complicated(with(mo))));
> }
>
> consteval string_view my_algo(meta::info mo) {
> return classify(type_identity<do_t<something_t<here_t<[: mo :]>>>>{});
> }
> ```
> A function like the `get_type_id` above cannot be implemented in pure
> consteval. This is IMO a pretty big limitation if the thing that is done in
> `get_type_id` is really complex and it is used in many places.
> (Actually I'm not even sure if the `my_algo` function could be implemented
> without being a template, because it depends on the consteval argument.
> I'll have to try the meta::info implementation).
>
>
>> This does not defeat the purpose of operating over uniformly typed
>> values. For example, imagine a meta-function which generates the code
>> needed to serialize any value. With the meta::info proposal, the whole
>> meta-function never needs to be instantiated, simply evaluated, and then
>> the returned fragment is injected (a task essentially equivalent to
>> instantiation) into the caller's context.
>>
> I've heard this reasoning before and I'm still not sure what advantages
> does this bring over template function instantiation, since as you say
> (evaluation + injection = almost like instantiation).
>
>
> Injection does not intrinsically require instantiation; injections are
> instantiated and evaluated only when an enclosing template is instantiated
> like anything else. One can use injection statements in ordinary control
> flow statements, since all injectable entities will have the same type
> (whether strings or meta::infos or a fragment type specific to that
> injection context). Whereas, with instantiated reflections some facility
> like TMP or `template for` is required to iterate.
>
> That said, while I very much support getting away from template
> instantiation-based reflection in the long run, I think the fact that
> meta::info vs. template instantiations is an *interface/language* issue,
> rather than an *implementation* question, suggests a more general issue the
> language should address: need templates & concepts really be syntactically
> distinct from types, anywhere in the language?
>
> Consider my old clang-based reflection implementation, which reflects the
> clang interface, only as template instantiations a la Matus’s approach, but
> object-oriented. That is to say: a class, represented as a `CXXRecordDecl`
> in clang, would be reflected to the user as a `CXXRecordDecl<uintptr_t>`.
>
> For reference here is the (very large) library header providing the
> interface:
> https://raw.githubusercontent.com/drec357/clang-meta/master/llvm/tools/clang/tools/reflection-src-generator/generated_files/client_reflection_impl.hpp
> .
>
> By defining the right templated function overloads, one can almost achieve
> the same syntax as one would use in clang; e.g.
>
> ```
> template for (auto D :
> reflexpr(MyClassTemplate)->getTemplatedDecl()->decls()) {
> if constexpr (D->isImplicit())
> continue;
> if constexpr (auto_ FD = dyn_cast<FieldDecl>(D)) {
> auto QT = FD->getType();
> if (QT->isDependentType())
> meta::debug("Field named ", FD->getQualifiedNameAsString(), " has a
> dependent type");
> } else {
> D->dump(); //displays at compile-time
> meta::error( D->getBeginLoc()
> , "Unhandled Decl kind; see dump"
> ,
> user::FixItHint::CreateRemoval(user::SourceRange(D->getBeginLoc(),
> D->getEndLoc()));
> );
> }
> }
> ```
>
> What are the *syntax* differences between this and the ordinary clang
> interface? How could they be minimized?
>
> 1. `template for` instead of `for`
> 2. the ubiquitous need for auto types
>
> 1 can be handled simply; I don’t see why we couldn't get rid of `template
> for` syntax, instead using `for`, and simply ask that the compiler perform
> compile-time expansion when the type of the range variable is tuple-like.
>
> As for 2, suppose one could use templates and concepts just like types, a
> la:
> ```
> namespace meta::clang {
>
> template<uintptr_t metaobjptr>
> class DeclContext {...};
>
> template<uintptr_t metaobjptr>
> class CXXRecordDecl {...};
>
> // CXXRecordDecl is a templates so is treated like `auto`.
> template<typename T>
> CXXRecordDecl *getClassReflection() { return reflexpr(T); }
>
> // DeclContext and CXXRecordDecl are templates so are treated like
> `auto`;
> // A function template will be implicitly generated from this code.
> DeclContext *getParentOf(CXXRecordDecl *D) { return D->getDeclContext(); }
> }
> ```
> (I am not up to date on the latest thinking re: concepts etc, and surely
> this has been discussed at some point and so I would appreciate any links
> to papers that bear on this.)
>
> If the language allowed for both of these changes, I believe there would
> be no need to specify how the compiler reflects information. I.e. a
> compiler could use a meta::info type, or an implicit meta::info<intptr_t>
> template *which is always referenced as meta::info* — i.e. essentially the
> same way Matus reflects information. (Indeed perhaps the template-ness of
> an entity could be private information, to an extent.)
>
> Dave
>
>
>
>
>
>
>
>
>
>
Received on 2021-12-09 17:07:33