I'm aware of the existing reflection proposals and the opinion of the standard committee about them, but I'm not totally satisfied with the solutions presented so far.So, I'm interested in hearing if the following idea is feasible and if anyone feels interested in exploring more of it:Given fixed_string: a compile time string (such as the one in N4121)
- Declaration name operators:- declname(identifier) => fixed_string
- take types: yes, declname(type) => fixed_string- namedecl(fixed_string) => identifier
- take user-defined fixed_string as input: yes- take keywords as input: why not?
- Declaration scope operator (obtain all the declared names in the given scope):- declscope(class/enum/namespace) => fixed_string[]
- filtering: could be done with the resulting array but maybe a second argument for declscope(scope, what) or more operators...
- accessibility: same as declared, breaking encapsulation is out of scope, requires diffent mechanism
- attributes, templates/concepts, function/template argument names, declaration specifiers: still need some thinking
Use cases:- pointers: &namedecl(fixed_string), &T::namedecl(fixed_string)- references: auto& r = namedecl(fixed_string) - being 'name' an object- functions: namedecl(fixed_string)(value)- type: decltype(namedecl(fixed_string))- members: T::namedecl(fixed_string), t.namedecl(name)- enums: namedecl(fixed_string) => value, array_size(declanames(fixed_string)) => entries- scope: namedecl(scope)::namedecl(member), namedecl(object).namedecl(member)- compilation error: namespace(name) if "name" does not exist in the current scopeOne last thing:- overloads: overload_set(identifier) => typelist of the signatures (but what if one of them is a template function?)
Hi Ricardo,
On 11/04/16 09:12, Ricardo Andrade wrote:
> I'm aware of the existing reflection proposals and the opinion of the
> standard committee about them, but I'm not totally satisfied with the
> solutions presented so far.
Could you outline what your lack of satisfaction is about?
Cheers, Axel.
You received this message because you are subscribed to a topic in the Google Groups "SG 7 - Reflection" group.
To unsubscribe from this topic, visit https://groups.google.com/a/isocpp.org/d/topic/reflection/uksZoUpQaUk/unsubscribe.
To unsubscribe from this group and all its topics, send an email to reflection+...@isocpp.org.
To post to this group, send email to refle...@isocpp.org.
I'm going to use P0194R0 as a base for most comparisons, as this seems to be the one that got more comments from the committee.Here are some points of concern (at no specific order of importance):
- Abuse of the type system to transport information about each and every declaration (i.e. each member is a different MetaObject, thus a different type). How bad would it be to compile Qt if it was purely based on this?
- Duplication of the language constructs - the "reflection way" of doing the same thing (i.e. decltype(name) -> get_type_t<reflexpr(name)>, &name -> get_pointer_v<reflexpr(name)>).
- Duplication between the traits found in <type_traits> and the traits on the meta namespace (i.e. meta::is_class_v<MO> vs. std::is_class_v<T>).
- No simple "type-safe" replacement for stringfying an identifier using the preprocessor (i.e. get_name_v<reflexpr(name)> require much more compiler resources than a simple #name).
- A reasonably sized library support for static reflection. A future proposal for dynamic reflection would then require another equally sized library.
- Not intended for beginners. Or if used by beginners extensively, may have adverse side-effects (may take much longer to compile or take much more resources to do it).
With fixed_string based reflection I'm trying to address the above:
- fixed_string template instances only vary by the length of the string. Using it extensively won't cause the same pressure on the type system as one type per declaration.
- New language constructs would be added only for reflection facilities. It would rely on all existing constructs for the rest.
- Depend on the existing type traits. New ones might be added as needed but the goal is to allow using these outside of the context of reflection as well (such as overload_set).
- The fixed_string is reasonably cheap to obtain from an identifier and it's virtually as good as one obtained via the preprocessor.
- Ideally not a library solution, just language support (plus traits).
- Slightly more beginner-friendly. Or at least, aims to be lighter.
At the same, I had in mind some interesting aspects of P0194R0:
- Dedicated operator (or keyword) makes it easier to find where reflection is being used in the code.
- Deal equally with typed and non-typed entities (such as namespaces).
- Obtain everything that could be needed from a given scope (class or namespace) at once.
- Use of traits to obtain details about a given declaration.
- In theory, it can be applied recursively from the global scope.
Cheers, Axel.
I had several opportunities to discuss these concerns with the authors of both P0194R0 and P0255R0.They have their point of view, but they are also defending their own line of thought.I tend to agree with their ideas as long as there is not a simpler solution.So, I'm here to be sure about that.
You received this message because you are subscribed to a topic in the Google Groups "SG 7 - Reflection" group.
To unsubscribe from this topic, visit https://groups.google.com/a/isocpp.org/d/topic/reflection/uksZoUpQaUk/unsubscribe.
To unsubscribe from this group and all its topics, send an email to reflection+...@isocpp.org.
To post to this group, send email to refle...@isocpp.org.
Hi,On Mon, Apr 11, 2016 at 9:12 AM, Ricardo Andrade <ricardofabi...@gmail.com> wrote:I'm aware of the existing reflection proposals and the opinion of the standard committee about them, but I'm not totally satisfied with the solutions presented so far.So, I'm interested in hearing if the following idea is feasible and if anyone feels interested in exploring more of it:Given fixed_string: a compile time string (such as the one in N4121)You can do most if not all of these by using `reflexpr` + `get_name` which *will* return a compile time string. If you find the basic interface too verbose for your use-cases then it is only a simple matter of writing (and proposing for standardisation) of several constexpr functions.
- Declaration name operators:- declname(identifier) => fixed_stringget_name_v<reflexpr(identifier)>;- take types: yes, declname(type) => fixed_string- namedecl(fixed_string) => identifierThis is the only thing which cannot be done at the moment, although it would be nice to have.
- take user-defined fixed_string as input: yes- take keywords as input: why not?get_name_v<reflexpr(static)> (reflexpr(static) will return a MetaSpecifier in the future).
- Declaration scope operator (obtain all the declared names in the given scope):- declscope(class/enum/namespace) => fixed_string[]playing with `get_scope` + `get_name`
- filtering: could be done with the resulting array but maybe a second argument for declscope(scope, what) or more operators...compile-time string manipulation similar to what for example the Metaparse library does.
- accessibility: same as declared, breaking encapsulation is out of scope, requires diffent mechanism`is_public<Metaobject>` - this will be more fine grained when specifier reflection is introduced.
- attributes, templates/concepts, function/template argument names, declaration specifiers: still need some thinkingThere will be a MetaTemplate, MetaTemplateParameter, etc.
Use cases:- pointers: &namedecl(fixed_string), &T::namedecl(fixed_string)- references: auto& r = namedecl(fixed_string) - being 'name' an object- functions: namedecl(fixed_string)(value)- type: decltype(namedecl(fixed_string))- members: T::namedecl(fixed_string), t.namedecl(name)- enums: namedecl(fixed_string) => value, array_size(declanames(fixed_string)) => entries- scope: namedecl(scope)::namedecl(member), namedecl(object).namedecl(member)- compilation error: namespace(name) if "name" does not exist in the current scopeOne last thing:- overloads: overload_set(identifier) => typelist of the signatures (but what if one of them is a template function?)There will be a MetaOverloaded function which will return a sequence of MetaFunctions some of which may also be MetaTemplates.If you have a look at N4111 most of the concepts like MetaSpecifier, MetaTemplate, MetaTemplateParameter, MetaOverloadedFunction, MetaFunction, MetaConstructor, MetaOperator, etc. were already proposed, but were (temporarily) removed from the subsequent proposals at the request of some members of the committee. They *will* be reintroduced later.
Matus
Hi Ricardo,let me address your concerns more concisely than in our private conversations:On Mon, Apr 11, 2016 at 9:58 PM, Ricardo Fabiano de Andrade <ricardofabi...@gmail.com> wrote:
[8<]I'm going to use P0194R0 as a base for most comparisons, as this seems to be the one that got more comments from the committee.Here are some points of concern (at no specific order of importance):
- Abuse of the type system to transport information about each and every declaration (i.e. each member is a different MetaObject, thus a different type). How bad would it be to compile Qt if it was purely based on this?
The type system is 'abused' in C++ for many different things, in particular it is nowadays regularly used as a stand-alone *meta-programming language*, where types are used as values. P0194 just follows this pattern and allows to nicely integrate the meta-objects into meta-programs.
Furthermore there is nothing whatsoever preventing a completely lazy implementation so you are paying only for things you ask for and the meta-objects exist purely on the type level (there are no constructors, they cannot be instantiated, etc.)
So the meta-objects are very lightweight.
There were objections from other people to add full reification of things like namespaces, constructors, parameters, etc.
and to doing drastic changes to what can or cannot be a template parameter.
Also we want to reason about meta-data at compile-time for many use cases.So taking this and the well established meta-programming practices into account, representing the meta-objects by using types is IMHO the best choice.
- Duplication of the language constructs - the "reflection way" of doing the same thing (i.e. decltype(name) -> get_type_t<reflexpr(name)>, &name -> get_pointer_v<reflexpr(name)>).
The constructs you listed are *not* the exactly the same things:
decltype(name) -> get_type_t<reflexpr(name)> : not the same at all
&name -> get_pointer_v<reflexpr(name)> : here you are missing an important step, namely that getting the metaobject and then obtaining the address may and will be separated in many cases.
In C++ there are many different ways to do the same things.For example array access:int y;int x[10];x[5] = y;5[x] = y;*(x+5) = y;
Or, is it a problem that we have both a C array and std::array? Etc, etc.
- Duplication between the traits found in <type_traits> and the traits on the meta namespace (i.e. meta::is_class_v<MO> vs. std::is_class_v<T>).
The meta-level traits are there for consistency with the other meta-traits for which there are no equivalents in base-level type traits.
There is no `std::is_namespace_v`, `std::is_constructor_v`, `std::is_specifier_v`, etc. Once the whole reflection facility is completed, there may be several dozens of meta-type-traits. I see no point in breaking their consistency by removing a single or at most a couple of trait(s) for the sake of simplicity.
Also `std::is_class_v` may be *more* verbose and clumsy to use in many cases (again you are missing that the act of reflecting something and inspecting the meta-object's properties can be separated.
- No simple "type-safe" replacement for stringfying an identifier using the preprocessor (i.e. get_name_v<reflexpr(name)> require much more compiler resources than a simple #name).
Preprocessor stringifying is for making *strings* from any PP tokens not just from valid identifiers.
As I said I would love to have the ability to create an identifier from a compile-time string together with some compile-time string manipulation library and something like this was mentioned in N4451 (the `identifier` operator).
But I've been told by the members of the committee to keep the proposal simple and extensible.
At the moment there is nothing in P0194 preventing the implementation of the above in the future. Are you sure that it is possible to implement that with `fixed_string`, where only the size is a compile-time constant?
- A reasonably sized library support for static reflection. A future proposal for dynamic reflection would then require another equally sized library.
Yes, to make things convenient for some simple use cases, there will be additional constexpr functions and templates added to the standard library in the future. We are actively thinking about them, I've spent a big part of the last 10 years writing various reflection facilities from completely static ones to fully dynamic (even dynamically loadable) ones. There are other people who did the same.What we need now, is to agree on a solid, fundamental compiler support for static reflection covering as many use cases as possible. You can build anything else on top of that later.
- Not intended for beginners. Or if used by beginners extensively, may have adverse side-effects (may take much longer to compile or take much more resources to do it).
See the above. We are working on the fundamentals. Yes, there *will* be a convenience interface for simple (or beginner) use-cases. But you cannot build a simplifying facade if you don't have the layer below finished.
With fixed_string based reflection I'm trying to address the above:
- fixed_string template instances only vary by the length of the string. Using it extensively won't cause the same pressure on the type system as one type per declaration.
I (and I assume that others too) want to have the ability to inspect and manipulate the reflected names *at compile-time*.Can you do that with `fixed_string` where only the size is known at compile-time (Am I missing something)?
As proposed in P0194, the name can be represented in the compiler as a plain (zero terminated) C array of chars and it's there only when you request it. Is that a big 'pressure' on the compiler?
- New language constructs would be added only for reflection facilities. It would rely on all existing constructs for the rest.
The same thing is proposed in P0194 and its predecessors.
- Depend on the existing type traits. New ones might be added as needed but the goal is to allow using these outside of the context of reflection as well (such as overload_set).
This was addressed above.
- The fixed_string is reasonably cheap to obtain from an identifier and it's virtually as good as one obtained via the preprocessor.
No it is not virtually as good as the preprocessor. You can concatenate strings, etc. but you *cannot* create identifiers at compile time, unless you have a fully static string, which is what P0194 has.
- Ideally not a library solution, just language support (plus traits).
Why not a library solution? I was under the impression that in C++ things that can be done (and are not too cumbersome to do) by a library, should be done by a library.
- Slightly more beginner-friendly. Or at least, aims to be lighter.
See above.
At the same, I had in mind some interesting aspects of P0194R0:
- Dedicated operator (or keyword) makes it easier to find where reflection is being used in the code.
- Deal equally with typed and non-typed entities (such as namespaces).
- Obtain everything that could be needed from a given scope (class or namespace) at once.
- Use of traits to obtain details about a given declaration.
- In theory, it can be applied recursively from the global scope.
Cheers, Axel.
I had several opportunities to discuss these concerns with the authors of both P0194R0 and P0255R0.They have their point of view, but they are also defending their own line of thought.I tend to agree with their ideas as long as there is not a simpler solution.So, I'm here to be sure about that.
I try hard to make the interface as simple as possible without breaking consistency and without compromising important use cases, but I see no point in simplification just for its own sake.
Best regards,Matus
Hi Matus,On Mon, Apr 11, 2016 at 9:12 AM, Ricardo Andrade <ricardofabi...@gmail.com> wrote:I'm aware of the existing reflection proposals and the opinion of the standard committee about them, but I'm not totally satisfied with the solutions presented so far.So, I'm interested in hearing if the following idea is feasible and if anyone feels interested in exploring more of it:Given fixed_string: a compile time string (such as the one in N4121)You can do most if not all of these by using `reflexpr` + `get_name` which *will* return a compile time string. If you find the basic interface too verbose for your use-cases then it is only a simple matter of writing (and proposing for standardisation) of several constexpr functions.It's not -only- a matter of verbosity, but also how costly it will be to keep adding ad-hoc types for every call to reflexpr.Even though fixed_string is a not as "smart" as a type, it's sufficient to get done the job of preserving information about a declaration.
Another point is that, for example besides the clear intention of meta::get_pointer, one still needs to learn about it along with the reflection mechanism (what consists in what I call "the reflection way" of doing things).In the other hand every C++ user already knows about the ampersand operator. They will need only to learn how to use reflection in terms I've presented.
- Declaration name operators:- declname(identifier) => fixed_stringget_name_v<reflexpr(identifier)>;- take types: yes, declname(type) => fixed_string- namedecl(fixed_string) => identifierThis is the only thing which cannot be done at the moment, although it would be nice to have.Right, the meta-functions described in your proposal make the conversion from a reflected entity to the actual declaration initially unnecessary.At the expense of requiring an additional meta-function for each operation for which the language itself already has already a construct.i.e. get_pointer_v<reflexpr(identifier)> vs. &
- take user-defined fixed_string as input: yes- take keywords as input: why not?get_name_v<reflexpr(static)> (reflexpr(static) will return a MetaSpecifier in the future).My concern is that, by following this trend we will end up with one MetaObject instance per word of C++ code.I'm far from being an expert in compilers, but I don't think they would be able to keep up with that.Redesigning a compiler because of the requirements for using reflection also does not sound like a good idea.
- Declaration scope operator (obtain all the declared names in the given scope):- declscope(class/enum/namespace) => fixed_string[]playing with `get_scope` + `get_name`Considering your proposal already have a reasonably sized library support in it, a lot of user code would still be needed for achieving the same list of fixed_strings using get_scope+get_name.And sometimes a list of name it is all I need.- filtering: could be done with the resulting array but maybe a second argument for declscope(scope, what) or more operators...compile-time string manipulation similar to what for example the Metaparse library does.My comments about filtering were more related to separating the entities that each fixed_string refers to in groups or categories.Mainly:- types (nested, typedefs, using aliases),- objects (member, variables),- functions (member, static, constructors),- templates/concepts, namespaces.The idea is finding ways to obtain only entity names of one of these groups.I have no intention to require compile-time string transformations as part of the reflection.- accessibility: same as declared, breaking encapsulation is out of scope, requires diffent mechanism`is_public<Metaobject>` - this will be more fine grained when specifier reflection is introduced.In my mind specifier reflection should give me the same information which is possible to obtain with meta::is_public in your proposal.Something like: specifiers(name) => fixed_string[] i.e. "public", "const"IMO, a way to violate the access defined by the class author should not be implemented by reflection.In other words, one may want to access a private member without using reflection.The fact that other languages do it by using reflection does not mean it's the best way of doing it.
I'm more towards something like a "cast" operator:unrestricted_access(c.m) // break into the private member "m"
Or even a form of inheritance:class X : friend Y {}; // anything in X is not public in YWhich is cool, because by using "final" the class authors would prevent it to be done.- attributes, templates/concepts, function/template argument names, declaration specifiers: still need some thinkingThere will be a MetaTemplate, MetaTemplateParameter, etc.Along with more pressure on the type system.
Use cases:- pointers: &namedecl(fixed_string), &T::namedecl(fixed_string)- references: auto& r = namedecl(fixed_string) - being 'name' an object- functions: namedecl(fixed_string)(value)- type: decltype(namedecl(fixed_string))- members: T::namedecl(fixed_string), t.namedecl(name)- enums: namedecl(fixed_string) => value, array_size(declanames(fixed_string)) => entries- scope: namedecl(scope)::namedecl(member), namedecl(object).namedecl(member)- compilation error: namespace(name) if "name" does not exist in the current scopeOne last thing:- overloads: overload_set(identifier) => typelist of the signatures (but what if one of them is a template function?)There will be a MetaOverloaded function which will return a sequence of MetaFunctions some of which may also be MetaTemplates.If you have a look at N4111 most of the concepts like MetaSpecifier, MetaTemplate, MetaTemplateParameter, MetaOverloadedFunction, MetaFunction, MetaConstructor, MetaOperator, etc. were already proposed, but were (temporarily) removed from the subsequent proposals at the request of some members of the committee. They *will* be reintroduced later.My final words are that P0194R0 is a very nice proposal, it has a clear roadmap and honestly, it fits all the use cases I had in mind for reflection if everything your proposed initially gets implemented.There's no doubt you know your stuff and did a great work on it. Passing through the committee multiple times is a good proof of it.I'm just not convinced yet it's the best form of reflection we can have in C++.
Hi Ricardo,
First of all, I am happy to see that there is interest in names :-)
That being said, I am not convinced by your approach to have the
name-operator yield the fully qualified name and then use meta functions
to decompose that name.
From the top of my head, I can only think of a single use case that
requires the fully qualified name: If I want to know which namespace a
certain type/object/value was created in. And I am the first to admit
that I wanted that for the wrong reasons at the time.
Also, while potentially being useful, I wonder if some of the meta
functions you propose are even possible? For instance:
* is_function_name_v: I don't think this makes sense. Whether or not a
function is available might depend on ADL.
* is_argument_name_v: This depends on the context in which you call it.
is_argument_name_v<some_name> would have to yield true in some places
and false in others.
Similar with others.
Or those functions would tell something about the object that was used
to obtain the name. Bur in that case, the std::name would have to carry
tons of data in addition to the name.
That is not necessarily bad, but it should then have a different name.
Best,
Roland
You received this message because you are subscribed to a topic in the Google Groups "SG 7 - Reflection" group.
To unsubscribe from this topic, visit https://groups.google.com/a/isocpp.org/d/topic/reflection/uksZoUpQaUk/unsubscribe.
To unsubscribe from this group and all its topics, send an email to reflection+...@isocpp.org.
To post to this group, send email to refle...@isocpp.org.