On Jan 31, 2022, at 1:23 PM, Daveed Vandevoorde via SG7 <sg7@lists.isocpp.org> wrote:



On Jan 31, 2022, at 1:10 PM, Peter Dimov <pdimov@gmail.com> wrote:

Daveed Vandevoorde wrote:
In Boost.Describe, I support a "modifier" mod_inherited, which
includes the inherited members in the "members_of" list. I don't
currently support it for the base classes, but I could. So when you
have

struct B {};
struct D1: B {};
struct D2: B {};
struct C: D1, D2 {};

and you say describe_bases<C, mod_any_access | mod_inherited>, you
would get a list of base class descriptors [desc(D1), desc(B), desc(D2),
desc(B)].

Okay, so like the alternative API I described where you also get indirect bases.
Do the descriptors maintain information about the derivation path?

Not at the moment.

In a prototype of mine (before the official Describe) I had base descriptors
have something like

struct Desc
{
  static constexpr auto pointer = [](C* p) -> B*
  {
      return (B*)(D1*)p;
  };
};

with the derivation path implicitly encoded in the lambda. But in Describe
I don't provide `pointer` for base descriptors at all, having, err, postponed it.

A similar issue actually exists for inherited members. If you have

struct B { int m; };
struct D1: B {};
struct D2: B {};
struct C: D1, D2 {};

and you get the all members list for C, including inherited, that would
be two descriptors for B::m. At the moment in Describe both of these have

struct Desc
{
  static constexpr auto pointer = &B::m;
};

but that's wrong; they should actually have two different member pointers
of type `int D::*`, one pointing to the first `m`, other to the second.

I don't think this is achievable without compiler support, though.
Both &C::D1::m and &C::D2::m give &B::m.

This reminds me of a clang issue https://lists.llvm.org/pipermail/cfe-dev/2021-November/069428.html which might shed some light.

The gist: the clang static analyzer had a check that would warn you if two declarations in certain kinds of expressions were identical.  E.g. `a || a`.  The problem is that it would also warn on this:  `std::is_same<int, char>::value == std::is_same<float, double>::value`.  
Why?  Because they are both `std::false_type` — same as &C::D1::m and &C::D2::m reflect the same AST declaration, &B::m

The solution (https://reviews.llvm.org/D114622) was to compare the *expressions* referencing the declarations, rather than the declarations themselves: this would differentiate the different derived to base paths.

So, this probably indicates that a means of reflecting an expression referencing a declaration (a DeclRefExpr in clang) might be needed independently of a reflection of the declaration itself (perhaps double parentheses:  &((C::D1::m)) ); and corresponding queries might be needed to fetch a) the reflected scope qualifier and b) the reflected declaration of such a reflection.



Right.  Maybe if you had something like:

template<auto PM, typename D, typename … Path> struct X {
 static constexpr auto pointer = PM;
 static auto select(D *ptr) { … }
}

where the “select” operation would apply casts down Path and then "pointer” could select from that?

Seems convoluted, admittedly.



IIRC, in the context of P1240 someone once suggested that we should be able
to write something like:

D *p;
auto *pb = p->[:bases_of(^D)[0]:];

(assuming D has a base class). That’s similar to your suggestion of a more
general pointer-to-member value concept, but it has the advantage of
generalizing to bit fields as well.

That would indeed be a way to do it.

Okay, I’ll se if we can pursue that possibility then.

Daveed

--
SG7 mailing list
SG7@lists.isocpp.org
https://lists.isocpp.org/mailman/listinfo.cgi/sg7