
Hi Everyone, Some feedback from playing with Boost.Describe for class types. 1. As others pointed out, it is confusing that BOOST_DESCRIBE_STRUCT() and BOOST_DESCRIBE_CLASS() have such close names, but their interface and requirements are so different. Maybe rename the latter to BOOST_DESCRIBE_THIS_CLASS()? 2. Base class subobjects are in some applications treated equivalently to member subobject; this is why I found it surprising that I cannot get the names of base class subobjects. But I guess there are implementation difficulties. Bases can be qualified names. 3. As with enums, _public_member_descriptor_fn in global namespace is UB or invalid program as per http://eel.is/c++draft/lex.name#3.2. 4. As others pointed out, the part of the interface that uses enum modifiers is not intuitive. I think that after a while it starts to make sense: you decide on your use case (like doing RPC), form your enum bitmask, and then pass it everywhere. It became more-less clear to me only after I had a look at the implementation of member_filter: https://github.com/pdimov/describe/blob/develop/include/boost/describe/membe... Maybe it makes sense to expose this in the docs. 5. Modifier mod_inherited is great for flattening the inheritance structure: struct X { int a; }; struct Y : X { int b; }; struct Z : Y { int c; }; BOOST_DESCRIBE_STRUCT(X, (), (a)); BOOST_DESCRIBE_STRUCT(Y, (X), (b)); BOOST_DESCRIBE_STRUCT(Z, (Y), (c)); int main() { namespace desc = boost::describe; using namespace std::literals::string_literals; constexpr auto mod = static_cast<desc::modifiers>(desc::mod_public | desc::mod_inherited); using L = desc::describe_members<Z, mod>; assert((boost::mp11::mp_at_c<L, 0>::name == "a"s)); assert((boost::mp11::mp_at_c<L, 1>::name == "b"s)); assert((boost::mp11::mp_at_c<L, 2>::name == "c"s)); } But it is not obvious at first. I recommend that the docs provide an example like this. It even works when base classes have members with same names: struct X { int a; }; struct Y { int a; }; struct Z : X, Y { int b; }; BOOST_DESCRIBE_STRUCT(X, (), (a)); BOOST_DESCRIBE_STRUCT(Y, (), (a)); BOOST_DESCRIBE_STRUCT(Z, (X, Y), (b)); int main() { constexpr auto mod = static_cast<describe::modifiers>(describe::mod_public | describe::mod_inherited); using L = describe::describe_members<Z, mod>; Z z = {{{3}}, {{6}}, {9}}; assert((z.*mp_at_c<L, 0>::pointer == 3)); assert((z.*mp_at_c<L, 1>::pointer == 6)); assert((z.*mp_at_c<L, 2>::pointer == 9)); } 5. What is the use case for modifier mod_hidden? I cannot think of any situation where I would need it. 6. Regarding the overloaded functions, I am no expert in preprocessor metaprogramming, but maybe if there was a way to provide function signatures along the name, you would have enough information to form the pointers. Something like: struct X { void f(int){} void f(void*){} }; BOOST_DESCRIBE_STRUCT(X, (), ( SIGNATURE(void f(int)), SIGNATURE(void f(void*)) ); 7. Modifier mod_virtual works unintuitively. It does not affect the filtering in describe_bases<>. It is only used for decorating the list elements. Maybe the docs should be more explicit about what it is used for. Again, is there a use case for this? Regards, &rzej;

Andrzej Krzemienski wrote: ...
It even works when base classes have members with same names:
struct X { int a; };
struct Y { int a; };
struct Z : X, Y { int b; }; ... 5. What is the use case for modifier mod_hidden? I cannot think of any situation where I would need it.
If you change the name of `Z::b` above to `a`, it will hide the two `a` members in X and Y, so they won't be included in mod_inherited unless mod_hidden is also passed. Whether you'd want them to be depends on why do you want all the members; when doing RPC you don't want the hidden functions, for instance. But if for some (layout?) reasons you want all the data members, you do want the hidden members.
7. Modifier mod_virtual works unintuitively. It does not affect the filtering in describe_bases<>. It is only used for decorating the list elements. Maybe the docs should be more explicit about what it is used for. Again, is there a use case for this?
mod_virtual is necessary for the implementation of mod_inherited for base classes (virtual bases of the same type must only be returned once.) Since the library has to compute it anyway, I might as well return it to the user. My original design (which was about a compiler implementation of these reflection facilities) had more such output-only "modifiers", such as mod_final and mod_overridden. These, along with mod_virtual, were supposed to be returned in member function descriptors - the compiler knows all this and we can't get it in any other way, so why not return it. In the library implementation, only mod_virtual for bases remained. :-)

Am 14.03.21 um 18:33 schrieb Peter Dimov via Boost:
mod_virtual is necessary for the implementation of mod_inherited for base classes (virtual bases of the same type must only be returned once.)
Since the library has to compute it anyway, I might as well return it to the user.
My original design (which was about a compiler implementation of these reflection facilities) had more such output-only "modifiers", such as mod_final and mod_overridden. These, along with mod_virtual, were supposed to be returned in member function descriptors - the compiler knows all this and we can't get it in any other way, so why not return it. In the library implementation, only mod_virtual for bases remained. :-)
This reminds me of a question I had earlier but forgot: Given C++17 constexpr if I think it would be useful to return ALL members and then constexpr if on the type. E.g. currently it is not possible to do: boost::mp11::mp_for_each<describe_members<T, mod_any_access>>([&](auto D){ if constexpr(is_function(D)) handle_function if constexpr(is_variable(D)) handle_variable } I think the situation where this would be most useful is: boost::mp11::mp_for_each<describe_members<T, mod_any_access | mod_function>>([&](auto D){ some_computation // use the result of the computation to call functions which may be static if constexpr(is_static(D)) handle_static_function else handle_member_function } I.e. the problem with the toggles is, that they are toggles only. It is impossible to return both static and not-static members, which especially for functions is useful In general it is impossible to return ALL members, e.g. `boost::mp11::mp_for_each<describe_members<T>>` could be useful, especially for a compiler implementation Also a reminder that those toggles should stand out. E.g. I was wondering whether mod_hidden meant, that only hidden members are returned or in addition. :) Just thinking out loud: `describe_members<T, mod_any_access, mod_function, mod_static>` and `describe_members<T, mod_any_access, mod_data, mod_nonstatic>` could work too and make that clear if those are kept as toggles.

Le 2021-03-15 08:47, Alexander Grund via Boost a écrit :
Am 14.03.21 um 18:33 schrieb Peter Dimov via Boost:
I.e. the problem with the toggles is, that they are toggles only. It is impossible to return both static and not-static members, which especially for functions is useful In general it is impossible to return ALL members, e.g. `boost::mp11::mp_for_each<describe_members<T>>` could be useful, especially for a compiler implementation
I've been thinking about that, and i believe reversing the modifiers could give a cleaner interface for doing it. ie, something like: describe_members<T, mod_filter_none> --> returns everything describe_members<T, mod_filter_no_data_members> --> returns only the function, whatever their visibility is, and whether they are inherited or not. describe_members<T, mod_filter_no_inherited> --> returns only the members (data or functions) that are not inherited describe_members<T, mod_filter_no_static> --> returns only the members (data or functions) that are not static describe_members<T, mod_filter_no_inherited | mod_filter_no_functions | mod_filter_no_static> --> returns only the data members that are not inherited and not static (could be provided as an alias, mod_filter_self_data_members_only) From what i have seen, it may event be built on top of the current describe machinery (there is, i believe, pretty much everything in the details namespace to implement it without touching the macros and the current model). Regards, Julien

Julien Blanc wrote:
From what I have seen, it may event be built on top of the current describe machinery (there is, i believe, pretty much everything in the details namespace to implement it without touching the macros and the current model).
You can implement any alternative modifiers on top of the current primitives without touching anything in the detail namespace.
participants (4)
-
Alexander Grund
-
Andrzej Krzemienski
-
Julien Blanc
-
Peter Dimov