[Boost] [Describe] Review starts Monday 1st March 2021 - 10th March 2021
The Boost formal review of the Describe starts Monday, taking place from March 1st, 2021 to March 10th, 2021 (inclusive). The library is authored by Peter Dimov. Documentation: https://pdimov.github.io/describe/doc/html/describe.html Source: https://github.com/pdimov/describe The library provides a simple means of providing reflection for structures and enums in C++. The documentation contains a number of common motivating examples, including: - A universal print function https://pdimov.github.io/describe/doc/html/describe.html#example_print_funct... - JSON serialisation https://pdimov.github.io/describe/doc/html/describe.html#example_to_json Please provide in your review information you think is valuable to understand your choice to ACCEPT or REJECT including Describe as a Boost library. Please be explicit about your decision (ACCEPT or REJECT). Some other questions you might want to consider answering: - What is your evaluation of the design? - What is your evaluation of the implementation? - What is your evaluation of the documentation? - What is your evaluation of the potential usefulness of the library? - Did you try to use the library? With which compiler(s)? Did you have any problems? - How much effort did you put into your evaluation? A glance? A quick reading? In-depth study? - Are you knowledgeable about the problem domain? More information about the Boost Formal Review Process can be found at: http://www.boost.org/community/reviews.html The review is open to anyone who is prepared to put in the work of evaluating and reviewing the library. Prior experience in contributing to Boost reviews is not a requirement. Thank you for your efforts in the Boost community. They are very much appreciated. Richard Hodges - review manager of the proposed Boost.Describe library Peter is often available in the CppLang Slack #boost channel should you require any clarification not covered by the documentation, as am I. -- Richard Hodges hodges.r@gmail.com office: +442032898513 home: +376841522 mobile: +376380212
Richard Hodges wrote:
The Boost formal review of the Describe starts Monday, taking place from March 1st, 2021 to March 10th, 2021 (inclusive).
The library is authored by Peter Dimov.
Documentation: https://pdimov.github.io/describe/doc/html/describe.html Source: https://github.com/pdimov/describe
The library provides a simple means of providing reflection for structures and enums in C++.
The documentation contains a number of common motivating examples, including:
- A universal print function https://pdimov.github.io/describe/doc/html/describe.html#example_print_fu nction
- JSON serialisation https://pdimov.github.io/describe/doc/html/describe.html#example_to_json
Thanks Richard. Here's one additional example I wrote, an interactive console that allows printing and modifying variables using Describe and Boost.JSON in 119 lines of code: https://gist.github.com/pdimov/1d2507d5aa7809240be1bf7f1cab86a3 Here's a sample session: https://gist.github.com/pdimov/ad90d917ef26072e1d6f2259771a7799
Please provide in your review information you think is valuable to understand your choice to ACCEPT or REJECT including Describe as a Boost library. Please be explicit about your decision (ACCEPT or REJECT).
Conditionally ACCEPT.
- What is your evaluation of the design?
I'm surprised these macros are not already in Boost? In any case, these are nice. The library name works well with the macro interface. I don't like that I need to call describe_members multiple times to get static, non-static, data, and functions. I would appreciate the addition of mod_data and mod_nonstatic to keep the flags consistent. This is my only acceptance condition. Other thoughts: 1.I think describe_members could use a default flags value to represent all members. 2. I'd really like the option (but not the requirement) to provide description comment strings. 3. I'd really like the ability to use this library with overloaded member functions. 4. Support for member function templates might be nice. Support for class templates would be especially nice. I realize a consistent API design for this would be difficult, but seems like a Boost-worthy task. 5. There might be an interesting opportunity for this library to support progressive definition/reflection of namespaces. I'd like to see an attempt. 6. I wonder if there is a nice way to avoid the empty parens when there are no base types. 7. I wish that macros for old-style enums would have OLD in their name.
- What is your evaluation of the implementation?
Just a couple of question marks: * Why is "modifiers" not a C++11 enum class? * Why does this library not use Boost.PreProcessor? I wish that it did.
- What is your evaluation of the documentation?
* Nice docs, generous examples. * Syntax highlighting would be nice. * I'd like to see enum class take precedence over the old-style enums. * I would appreciate some further reading about other library approaches and the standardization efforts past and present.
- What is your evaluation of the potential usefulness of the library?
It's useful. Everyone has their own worse versions of these macros.
- Did you try to use the library? With which compiler(s)?
Yes, I poked around with the JSON RPC example using gcc 10.2.0.
have any problems?
Nothing I haven't already mentioned.
- How much effort did you put into your evaluation? A glance? A quick reading? In-depth study?
2 hours.
- Are you knowledgeable about the problem domain?
I'm familiar with these techniques and the constraints involved. I'm not familiar with the standardization efforts in this space, nor any of the existing Boost efforts aside from the ADAPT_STRUCT stuff. Thanks, Barrett Adair
Barrett Adair wrote:
I don't like that I need to call describe_members multiple times to get static, non-static, data, and functions. I would appreciate the addition of mod_data and mod_nonstatic to keep the flags consistent. This is my only acceptance condition.
Thank you Barrett for taking the time to review. The reason the static/nonstatic and data/function members are returned separately is that the type of `pointer` changes. For nonstatic data, it's M T::*. For static data, it's M*. For nonstatic function, it's R (T::*)(A...). For static function, it's R (*)(A...). Since one typically does things with `pointer` in the loop over members, it's rarely the case that you want all of these at once, because the syntactic form of the code manipulating them changes. So I've found it in practice more convenient to separate them by default.
Barrett Adair wrote:
4. Support for member function templates might be nice. Support for class templates would be especially nice. I realize a consistent API design for this would be difficult, but seems like a Boost-worthy task.
When you say "support for class templates", what do you mean?
5. There might be an interesting opportunity for this library to support progressive definition/reflection of namespaces. I'd like to see an attempt.
Further elaboration would be appreciated here as well. :-)
* Why is "modifiers" not a C++11 enum class?
That's interesting. The primitives take unsigned, not an enum, as their `modifiers` parameter. The reason they take unsigned is to make it easier to implement them as compiler built-ins, as compiler primitives depending on user-defined types is inconvenient.
* Why does this library not use Boost.PreProcessor? I wish that it did.
This would certainly have saved me a lot of trouble, but I didn't consider the dependency acceptable. To establish itself the library needs to be lightweight enough so that people would use it without second thought to describe their types. I'll remove even the Mp11 dependency, after I'm sure the implementation is stable. (And provide mp_for_each so that simple uses won't need anything else as well.)
On 27.02.21 23:53, Richard Hodges via Boost wrote:
The Boost formal review of the Describe starts Monday, taking place from March 1st, 2021 to March 10th, 2021 (inclusive).
My first impression of Describe is that it looks very useful. However, there are two things that bother me. 1. There appears to be no way to add annotations to members. I often have class data members that should be excluded from serialization. Without annotation support, I would have to maintain my own external list of such members in order to use automated serialization. 2. The examples are powerful, but they're also kind of complicated and ugly. I can't help thinking that it might be possible to simplify the client code by moving more code into the library. For example, describe_enumerators_as_array seems like a useful addition to the library. -- Rainer Deyke (rainerd@eldwood.com)
Rainer Deyke wrote:
My first impression of Describe is that it looks very useful. However, there are two things that bother me.
1. There appears to be no way to add annotations to members. I often have class data members that should be excluded from serialization.
We all do. :-) I want to keep alive the dream of having this in the core language one day, which implies that ideally we'd spell that struct X { int m1 [[transient]]; // do not serialize int m2 [[version(4)]]; // only in v4 or later }; and then have a way to get these attributes in the member descriptor. I'll try to figure out a way to provide this in the library in a way that would be compatible with language attributes.
Without annotation support, I would have to maintain my own external list of such members in order to use automated serialization.
2. The examples are powerful, but they're also kind of complicated and ugly. I can't help thinking that it might be possible to simplify the client code by moving more code into the library. For example, describe_enumerators_as_array seems like a useful addition to the library.
This specific one does, yes. But in general, each use typically is slightly different. Enum to string conversion, for example, can fail in both directions, and there's no one right way to handle these failures. It's just easier to write your own enum_from/to_string which work exactly as you want them to work. And for things like JSON, hashing, serialization, the goal isn't for the Describe library to provide the functionality; the goal is for it to serve as the standard way for these other libraries to be able to query the member descriptors and automatically provide behavior out of the box. Of course if some piece of functionality is reinvented verbatim, over and over, it will (eventually) get added to the base library. Just not today.
Hi,
Thank you, Peter, for sharing this library.
While reading through the documentation of describe and considering how to use it in a C++14 code base a few questions came to my mind.
1) I saw that describe does not support describing reference member variables. Is there a fundamental limitation that prohibits this? We have quite a few `boost::asio::io_context&` members or similar members. (Yes, we haven't found time to update them to executor members.) We wouldn't be able to describe those.
2) Overloaded functions are not supported. Again, what are the reasons for this limitation? Is it fundamentally impossible?
3) The first and obvious use case is clearly enums. They get a pretty big share of the examples as well. However, Just describing the enums to have a name value descriptor
template<class D> struct enum_descriptor
{
// can't use auto here because of the need to supply the definitions below
static constexpr decltype(D::value()) value = D::value();
static constexpr decltype(D::name()) name = D::name();
};
is only part of what would be generally useful.
Is it possible to support attaching extra data to the enum or their descriptor. As an example consider an error code enum. Without describe we would have something like
enum class my_errors {
success,
some_thing_went_wrong,
invalid_path,
wrong_argument,
unknown_error
};
char const* my_errors_s[5] = {
"success",
"some thing went wrong",
"invalid path",
"wrong argument",
"unknown error"
};
std::errc my_errors_map[] = {
0,
std::errc::io_error,
std::errc::file_or_directory_not_found,
std::errc::invalid_argument,
std::errc::io_error
};
With describe I can easily get rid of the `my_errors_s` array. But even this is only the case if the string can be programmatically computed from the names. Even better would be if I could just attach arbitrary strings and the mapping info as well. So I would be looking for something like
enum class my_errors {
success,
some_thing_went_wrong,
invalid_path,
wrong_argument,
unknown_error
};
BOOST_DESCRIBE_ENUM_CLASS_WITH_DATA(
my_errors,
std::errc,
(success, 0),
(some_thing_went_wrong, std::errc::io_error),
(invalid_path, std::errc::file_or_directory_not_found),
(wrong_argument, std::errc::invalid_argument),
(unknown_error, std::errc::io_error),
};
which would create descriptors like
template<class D> struct enum_descriptor
{
// can't use auto here because of the need to supply the definitions below
static constexpr decltype(D::value()) value = D::value();
static constexpr decltype(D::name()) name = D::name();
static constexpr decltype(D::data()) data = D::data();
};
Another similar example would be associating message types with ids, e.g.
enum class message {
Hello,
Bye,
Ping,
Pong,
...
};
BOOST_DESCRIBE_ENUM_CLASS_WITH_TYPE(
message,
(Hello, HelloMessage),
(Bye, ByeMessage),
(Ping, EchoMessage),
(Pong, EchoMessage),
...
};
Is something like this possible and withing scope? With enums I find myself often in the situation that there is some kind of extra to be attached with it.
I can understand if something like this is not considered within the scope of describe. However, from a user perspective it would make less attractive if I have to define the enum, descibe it, and then repeat it to some other piece of code again to attach the extra data (or put the extra data into (a) separate array(s).
4) I like that there are quite a few examples that show how to get and use the information from describe. However, when looking at those, I think that a few convenience helpers to easily iterate at compile time or at runtime would be nice (even if its in the end just a simple wrapper calling mp_for_each and describe_*. For example the runtime printing example which essentially creates an array of value,name pairs needs quite a lot boilerplate
template<class E> struct enum_descriptor
{
E value;
char const * name;
};
template
Maximilian Riemensberger wrote:
Hi,
Thank you, Peter, for sharing this library.
You are welcome. :-) Thanks for the feedback.
While reading through the documentation of describe and considering how to use it in a C++14 code base a few questions came to my mind.
1) I saw that describe does not support describing reference member variables. Is there a fundamental limitation that prohibits this? We have quite a few `boost::asio::io_context&` members or similar members. (Yes, we haven't found time to update them to executor members.) We wouldn't be able to describe those.
Reference (and bitfield) members are a problem for reflection libraries (and even some core proposals) because it's not possible to form a pointer to member for them (and without injection, there's no good way to describe them in a useful way.)
2) Overloaded functions are not supported. Again, what are the reasons for this limitation? Is it fundamentally impossible?
The problem is basically that &X::f doesn't work when f is overloaded. There are probably ways around that, such as f.ex. using something like (int (X::*)(float) const, f) in place of just `f` (ugh), but I haven't yet settled on a solution I'd be comfortable with. (Actually I know what solution I'd be comfortable with - adding __describe_members to clang, gcc and msvc - but that's a difficult endeavor.) ...
Is it possible to support attaching extra data to the enum or their descriptor. As an example consider an error code enum. Without describe we would have something like
enum class my_errors { success, some_thing_went_wrong, invalid_path, wrong_argument, unknown_error };
char const* my_errors_s[5] = { "success", "some thing went wrong", "invalid path", "wrong argument", "unknown error" };
std::errc my_errors_map[] = { 0, std::errc::io_error, std::errc::file_or_directory_not_found, std::errc::invalid_argument, std::errc::io_error };
That's an interesting example. I'll have to think about it.
On Tue, Mar 2, 2021 at 10:48 PM Peter Dimov via Boost
Maximilian Riemensberger wrote:
2) Overloaded functions are not supported. Again, what are the reasons for this limitation? Is it fundamentally impossible?
The problem is basically that &X::f doesn't work when f is overloaded. There are probably ways around that, such as f.ex. using something like
(int (X::*)(float) const, f)
in place of just `f` (ugh), but I haven't yet settled on a solution I'd be comfortable with.
(Actually I know what solution I'd be comfortable with - adding __describe_members to clang, gcc and msvc - but that's a difficult endeavor.)
By coincidence I was reading Folly's Poly [1] yesterday, where Eric uses folly::sig to support overloading. Not sure it's relevant though :) Which brings up the question of support in Describe of non-member functions, also mentioned on that page. Can they be incorporated into the described members? Just curious. On another topic, could you have an example using std::variant? If a described struct contains a variant which is a mix of other described structs and std:: values (string, vector, optional, etc...), how one does go about dispatching to a custom serialization method? And if that variant is not of the std:: variety, but in fact a discriminated boost::any wrapper, as generated by Avro's C++ code generator, how does one hook into Describe's machinery? Or is it the other way around, and Describe just hands over that weird value, and it's the code it calls that's supposed to be aware of it? (the latter means it's the N operations that must be aware of it, instead of the 1 Describe/Description) Yet on something else, Boost.PFR seems to give an operator== for one's structs out of the box. That's a binary operation, while most of your examples are unary, no? How would "generated" op== work in Boost.Describe? Assuming the described structs don't have themselves an op==, and one would want to generate those op== using their "descriptions"? Thanks, --DD [1] https://github.com/facebook/folly/blob/master/folly/docs/Poly.md
Dominique Devienne wrote:
By coincidence I was reading Folly's Poly [1] yesterday, where Eric uses folly::sig to support overloading. Not sure it's relevant though :)
Which brings up the question of support in Describe of non-member functions, also mentioned on that page. Can they be incorporated into the described members? Just curious.
I wouldn't want to do that; the nonmembers are part of the enclosing namespace, and would properly be reflected as such one day when we have real language reflection. It's true that Describe, being a library, is more flexible, but I'd like to keep it as close as possible to the eventual real thing.
On another topic, could you have an example using std::variant?
There's nothing particularly unique about std::variant; it's like any other standard container class such as std::vector. If the serialization library you're using supports std::variant, it will integrate with Describe. Here for instance is an example of generating a hash function for described classes using ContainerHash: https://gist.github.com/pdimov/caef1e0c42f9ceaf37eef529d78e2081 Since Boost.ContainerHash supports std::variant out of the box, everything just works.
Dominique Devienne wrote:
Yet on something else, Boost.PFR seems to give an operator== for one's structs out of the box. That's a binary operation, while most of your examples are unary, no? How would "generated" op== work in Boost.Describe? Assuming the described structs don't have themselves an op==, and one would want to generate those op== using their "descriptions"?
Here's an example of a generated operator==: https://gist.github.com/pdimov/35bd34dab981667777ff6433818e8768
Hi,
Here's my small review of describe :
# TLDR
conditionnaly ACCEPT
# Are you knowledgeable about the problem domain?
Somewhat. I guess every C++ programmer played with various libraries
that try to provide reflection. In my case, i designed one that has
been in use in a custom ORM.
# How much effort did you put into your evaluation? A glance? A quick
reading? In-depth study?
About 10 hours playing with the library. I did *not* look in-depth at
the library internals.
# With which compiler(s)? Did you have any problems?
Gcc and linux, version 7, 8, and 10. I encountered one issue that will
be fixed (https://github.com/pdimov/describe/issues/3 ).
With gcc-7, compiling with gnu++14 instead of c++14 is mandatory, as
stated by the documentation. However, failing to do so results in non-
understandable error messages. Maybe some improvements could be done
here.
# What is your evaluation of the design?
The library looks well designed for the describe_enumerators.
There are imho some design flaws, however, with the describe
struct/class api.
## Macros
BOOST_DESCRIBE_STRUCT must be put outside the class definition.
BOOST_DESCRIBE_CLASS, on the other hand, must be put inside the class
definition. This is typically the "i will always get this wrong" case.
Since the library heavily relies on templating and macros, the error
messages are arcane to the final user. I would suggest, that, for
consistency, BOOST_DESCRIBE_STRUCT should be put inside the class
definition as well.
## Modifiers
modifiers is defined as enum modifiers
{
mod_public = 1,
mod_protected = 2,
mod_private = 4,
mod_virtual = 8,
mod_static = 16,
mod_function = 32,
mod_inherited = 64,
mod_hidden = 128,
};
which suggests that any combination is valid. That is not the case (and
not only for the obvious virtual/static). Given the following code:
struct C {
int a;
std::string b;
double c;
void f(float a, int b) {
}
void g() {
}
static void s() {}
private:
int p_;
void h_(std::string const& s) {}
BOOST_DESCRIBE_CLASS(C, (), (a, b, c, f, g, s), (), (p_, h_))
};
I would expect the following:
static_assert(mp_size
pt., 5 mar 2021 o 09:00 Julien Blanc via Boost
Hi,
Here's my small review of describe :
# TLDR
conditionnaly ACCEPT
# Are you knowledgeable about the problem domain?
Somewhat. I guess every C++ programmer played with various libraries that try to provide reflection. In my case, i designed one that has been in use in a custom ORM.
# How much effort did you put into your evaluation? A glance? A quick reading? In-depth study?
About 10 hours playing with the library. I did *not* look in-depth at the library internals.
# With which compiler(s)? Did you have any problems?
Gcc and linux, version 7, 8, and 10. I encountered one issue that will be fixed (https://github.com/pdimov/describe/issues/3 ).
With gcc-7, compiling with gnu++14 instead of c++14 is mandatory, as stated by the documentation. However, failing to do so results in non- understandable error messages. Maybe some improvements could be done here.
# What is your evaluation of the design?
The library looks well designed for the describe_enumerators.
There are imho some design flaws, however, with the describe struct/class api.
Thank you for this review. It expresses a couple of concerns I had but was not able to put them into words.
## Macros
BOOST_DESCRIBE_STRUCT must be put outside the class definition. BOOST_DESCRIBE_CLASS, on the other hand, must be put inside the class definition. This is typically the "i will always get this wrong" case. Since the library heavily relies on templating and macros, the error messages are arcane to the final user. I would suggest, that, for consistency, BOOST_DESCRIBE_STRUCT should be put inside the class definition as well.
My impression is that the library addresses a number of use cases that are fairly unrelated. Of course, they all fall under the category "obtain some meta-information about the entity''; but other than that I cannot easily tell the part that is the same about the four use cases. As an example of this, I cannot answer the question if this library is for obtaining meta information about already existing definitions, or is it also for creating new definitions? If it is the former, then BOOST_DEFINE_ENUM clearly does not belong here. If it is the latter, then I am clearly missing an analogous definition mechanism for defining structs.
## Modifiers
modifiers is defined as enum modifiers { mod_public = 1, mod_protected = 2, mod_private = 4, mod_virtual = 8, mod_static = 16, mod_function = 32, mod_inherited = 64, mod_hidden = 128, };
which suggests that any combination is valid. That is not the case (and not only for the obvious virtual/static). Given the following code:
struct C { int a; std::string b; double c; void f(float a, int b) { } void g() { } static void s() {} private: int p_; void h_(std::string const& s) {} BOOST_DESCRIBE_CLASS(C, (), (a, b, c, f, g, s), (), (p_, h_)) };
I would expect the following: static_assert(mp_size
>::value == 6); static_assert(mp_size >::value == 2); That's not the case. Instead, the results are resp 3 and 1. By default, only data members are returned, not member functions.
So, let's try another one. I want all my member functions: static_assert(mp_size
>::value == 4) --> fails. By default, it only returns non-static functions.
So, let's try to include static function as well: static_assert(mp_size
>::value == 4) That also fails. The result is one, now i only get the static functions.
It looks that there's a mix of apples and orange in this enum. Some values acts as they will include more data in the results, some on the contrary will filter, and some will change completely the type of the members enumerated. This is not obvious when reading the documentation, and there's just nothing in the name of the enumerated values that will hints the user about what it really does.
This design issue is my main motivation for the conditional acceptance of the library: i think it should not be accepted until this part is fixed.
I agree with the observation. My explanation for it (I do not know the real motivation, just saying as a reviewer) is that it is driven by use cases. Excluding enums, I have counted three: - data member (recursive) access for PODs - access to all data members (and base-classes) for class types, for the purpose of marshalling, etc.. - member function call by name For each of these use cases, the library provides a solution that is quite natural for that specific use case. But each of these use cases expects a somewhat different interface. But those three interfaces do not combine to a one coherent whole. I am missing a programming model here. I realize that what I say is vague. I cannot provide anything more specific right now. I would be more comfortable if it offered different functions: boost::describe::members() // for data members boost::describe::members_recursive() // decomposes base classes into its members (possible?) boost::describe::member_functions() // for functions
## Missing features
The library does not provide much. While i understand this is a design choice to allow transition to proper reflection when it lands into the core language, there are a few things that i believe it could provide, which belongs to the "core" of a reflection library.
I'm missing a BOOST_DEFINE_FIXED_ENUM_[CLASS_]FLAG, which would do the following:
BOOST_DEFINE_FIXED_ENUM_CLASS(E, Base, v1, v2, …, vN)
enum class E : Base { v1 = 0x1u, v2 = 0x2u, ... vN = 1u << N; };
I'm also missing a way to retrieve the base name of the class. Something like describe_type<T>::name which would return a char const* "T" (≠ typeid(T).name()).
Again, I would say that the library does not specify very clearly what its scope is. In particular, what it is *not*. If it is only for providing reflection for existing definitions, then I would say it does too much: BOOST_DEFINE_ENUM is out of scope. But if its scope is to also provide the definitions (especially for enums), then it is more like a "better enum" sub-library, and now, as you point out, it provides too little. BTW, your example should also include overloaded bitwise operators, as they do not work out of the box for enum classes. Regards, &rzej;
# What is your evaluation of the implementation?
I do not feel qualified enough to evaluate the implementation. It uses a lot of preprocessor and template magic, which are far beyond what i can evaluate, especially in the time i can spend on this review.
# What is your evaluation of the documentation?
Pretty good. It's clear, looks pretty, and the examples are great. I miss a complete reference page, though: something like https://think-async.com/Asio/asio-1.18.0/doc/asio/reference.html , which lists all types in the library.
# What is your evaluation of the potential usefulness of the library?
Very useful. In fact, i'm already planning using it everywhere as soon as i can get rid of some really old (gcc 4.8) toolchains. Both the enum reflection and the member reflection can be of great use in some code base i work on. I hope this library will land into boost soon.
Thanks to Peter for this work, which is very valuable.
Regards,
Julien
_______________________________________________ Unsubscribe & other changes: http://lists.boost.org/mailman/listinfo.cgi/boost
Andrzej Krzemienski wrote:
My impression is that the library addresses a number of use cases that are fairly unrelated. Of course, they all fall under the category "obtain some meta- information about the entity''; but other than that I cannot easily tell the part that is the same about the four use cases.
As an example of this, I cannot answer the question if this library is for obtaining meta information about already existing definitions, or is it also for creating new definitions? If it is the former, then BOOST_DEFINE_ENUM clearly does not belong here. If it is the latter, then I am clearly missing an analogous definition mechanism for defining structs.
It is a library for obtaining meta information about existing definitions, yes. As I have mentioned, the original motivation for it was to implement in a library what could at a later date become a built-in compiler mechanism for simple reflection. That is, the DESCRIBE macros would be unnecessary as the metadata would be generated by the compiler automatically, and the describe_* primitives would be compiler built-ins. BOOST_DEFINE_ENUM is a concession to practical usability. In the hypothetical world where the compiler automatically describes the enum, it wouldn't need to exist. But at the moment, we have to do with what we have, which is the library without compiler support, and repeating the enumerators for large enums is simply too cumbersome. In short, BOOST_DEFINE_ENUM is provided because it pretty much has to be. In its current state, the library is as close as its fundamental core as it will ever be. It will almost certainly acquire further usability enhancements as time goes on.
Andrzej Krzemienski wrote:
pt., 5 mar 2021 o 09:00 Julien Blanc via Boost
napisał(a): ... It looks that there's a mix of apples and orange in this enum. Some values acts as they will include more data in the results, some on the contrary will filter, and some will change completely the type of the members enumerated. This is not obvious when reading the documentation, and there's just nothing in the name of the enumerated values that will hints the user about what it really does.
This design issue is my main motivation for the conditional acceptance of the library: i think it should not be accepted until this part is fixed.
I agree with the observation. My explanation for it (I do not know the real motivation, just saying as a reviewer) is that it is driven by use cases.
That is correct. I already gave a partial answer in https://lists.boost.org/Archives/boost/2021/03/250949.php, but that may have been too short. The majority of use cases deal with nonstatic data members, so the library makes this the default (you don't need to supply any modifier in order to get the nonstatic data members.) In contrast, there's no good default for accessibility. Some use cases need just the public members, some need public+protected, some need all, some need public+protected and separately the private ones, and so on. The cases where one needs the static members are rarer. And since the type of the pointer returned in the descriptor differs between static and nonstatic, one typically doesn't need both returned at the same time. In the rarer still cases where one does want both static and nonstatic members at the same time, there's always the option of mp_append-ing the two descriptor lists. So the design approach here is to keep the more common cases short, while leaving advanced uses more complex. There are many ways one can redesign the modifiers, and I already went through most of them. This is what I have settled on. I do accept the feedback that this needs better documentation, and perhaps comments in modifiers.hpp for the people who look there first (don't we all).
Julien Blanc wrote:
BOOST_DESCRIBE_STRUCT must be put outside the class definition. BOOST_DESCRIBE_CLASS, on the other hand, must be put inside the class definition. This is typically the "i will always get this wrong" case. Since the library heavily relies on templating and macros, the error messages are arcane to the final user. I would suggest, that, for consistency, BOOST_DESCRIBE_STRUCT should be put inside the class definition as well.
BOOST_DESCRIBE_CLASS goes inside the class definition for two reasons: one,
to be able to access private members; two, to support class templates. You can
do
#include
pt., 5 mar 2021 o 17:39 Peter Dimov via Boost
Julien Blanc wrote:
BOOST_DESCRIBE_STRUCT must be put outside the class definition. BOOST_DESCRIBE_CLASS, on the other hand, must be put inside the class definition. This is typically the "i will always get this wrong" case. Since the library heavily relies on templating and macros, the error messages are arcane to the final user. I would suggest, that, for consistency, BOOST_DESCRIBE_STRUCT should be put inside the class definition as well.
BOOST_DESCRIBE_CLASS goes inside the class definition for two reasons: one, to be able to access private members; two, to support class templates. You can do
#include
template<class T> struct X { T m;
BOOST_DESCRIBE_CLASS(X, (), (m), (), ()) };
int main() { using namespace boost::describe; using D = describe_members
; } (except it seems it doesn't work on MSVC 2017, which I currently use for unrelated reasons, but it does work on g++ and clang++ :-))
In contrast, BOOST_DESCRIBE_STRUCT being put outside the struct allows you to describe structs whose definitions you don't control, such as structs defined in third party libraries and system headers.
So there's a reason for both decisions.
It makes sense to support both cases. Maybe it is just the choice of names. In C++ we are taught that struct is actually a class. Maybe a rename BOOST_DESCRIBE_STRUCT -> BOOST_DESCRIBE_AGGREGATE (or similar) would make the difference easier to spot. Regards, &rzej;
On 6/03/2021 6:23 am, Andrzej Krzemienski wrote:
BOOST_DESCRIBE_CLASS goes inside the class definition for two reasons: one, to be able to access private members; two, to support class templates. You [...] In contrast, BOOST_DESCRIBE_STRUCT being put outside the struct allows you to describe structs whose definitions you don't control, such as structs defined in third party libraries and system headers.
So there's a reason for both decisions.
It makes sense to support both cases. Maybe it is just the choice of names. In C++ we are taught that struct is actually a class. Maybe a rename BOOST_DESCRIBE_STRUCT -> BOOST_DESCRIBE_AGGREGATE (or similar) would make the difference easier to spot.
Or perhaps BOOST_DESCRIBE_TYPE_INTERNAL, which is defined inside the struct/class and can access private members and type parameters, and BOOST_DESCRIBE_TYPE_EXTERNAL, which is defined outside the struct/class and can only refer to public members. You may wish to describe a class that you don't control, for example.
On 27.02.21 23:53, Richard Hodges via Boost wrote:
Please provide in your review information you think is valuable to understand your choice to ACCEPT or REJECT including Describe as a Boost library. Please be explicit about your decision (ACCEPT or REJECT).
I vote to ACCEPT Boost.Describe. Although the library has some weaknesses (which have already come up in the review process), I do not consider them serious enough to delay the acceptance of Boost.Describe. Boost.Describe in its present form already passes the "would I use it" test.
Some other questions you might want to consider answering:
- What is your evaluation of the design?
It's a good minimalistic building blocks for higher level abstractions. The lack of a way to add annotations to described members is problematic. Normally, types in C++ can be externally annotated by defining a traits class template, but this is not really an option for the Di types returned by Boost.Describe, since they have no public names.
- What is your evaluation of the implementation?
The library adds functions to user-defined namespaces, which introduces the possibility of name collisions. For example, BOOST_DESCRIBE_ENUM adds a function called _enum_descriptor_fn. A better name for this function would be _boost_describe_enum_descriptor_fn, which significantly reduces the chance of a name collision.
- What is your evaluation of the documentation?
Good.
- What is your evaluation of the potential usefulness of the library?
Very useful.
- Did you try to use the library? With which compiler(s)? Did you have any problems?
No.
- How much effort did you put into your evaluation? A glance? A quick reading? In-depth study?
A few hours.
- Are you knowledgeable about the problem domain?
Yes. -- Rainer Deyke (rainerd@eldwood.com)
participants (9)
-
Andrzej Krzemienski
-
Barrett Adair
-
Dominique Devienne
-
Gavin Lambert
-
Julien Blanc
-
Maximilian Riemensberger
-
Peter Dimov
-
Rainer Deyke
-
Richard Hodges