C++ Logo

std-proposals

Advanced search

Re: P1839 and the object representation of subobjects

From: Jason McKesson <jmckesson_at_[hidden]>
Date: Wed, 22 Jul 2020 02:58:41 -0400
On Wed, Jul 22, 2020 at 2:21 AM Thiago Macieira via Std-Proposals
<std-proposals_at_[hidden]> wrote:
>
> On Tuesday, 21 July 2020 16:38:40 PDT Jason McKesson via Std-Proposals wrote:
> > 1: `byte_ptr + off`. Pointer arithmetic in C++ is based on arrays
> > (which for a single object is treated as a 1-element array).
> > `byte_ptr` does not point to an array of `char`, so pointer arithmetic
> > is invalid.
> > 2: The pointer resulting from `reinterpret_cast<C1*>` does not
> > *actually* point to the subobject of type `C1` within the other
> > object, even if the address `qoptr + off` is the address of that
> > subobject.
> >
> > P1839R2 solves problem number 1, as stated by its wording:
> [cut]
>
> > As for #2, `std::launder` from C++17 solves this problem:
> > > [ptr.launder]/3
> > > Requires: `p` represents the address `A` of a byte in memory. An
> > > object X that is within its lifetime (6.8) and whose type is similar (7.5)
> > > to T is located at the address `A`. All bytes of storage that would be
> > > reachable through the result are reachable through `p` (see below).
> > >
> > > Returns: A value of type T* that points to `X`.
> >
> > There is an object of type `C1` at the address `byte_ptr + off`;
> > therefore, `launder` retrieves a pointer to it.
>
> Ok, I'm happy with that. The third piece of this puzzle is getting that
> offset, which we need to either rely on offsetof (which is now conditionally
> allowed on non-standard-layout types) or on a PMO.
>
> Would it make sense to add this entire operation as a library function?
>
> struct S
> {
> int i, j;
> };
>
> S s;
> back_to_object<&S::i>(&s.i) == &s
> back_to_object<&S::j>(&s.j) == &s
>
> struct O
> {
> virtual ~O();
> struct Empty {};
> [[no_unque_address]] Empty e;
> union {
> int k;
> };
> };
>
> O o;
> back_to_object<&S::e>(&o.e) == &o;
> back_to_object<&S::k>(&o.k) == &o;
>

Nesting becomes a bit cumbersome to handle:

```
struct Inner
{
  int i;
};

struct Outer
{
  Inner in;
};

Outer o;
back_to_object<&Outer::in>(back_to_object<&Inner::i>(&o.in.i)) == &o;
```

Granted, it could be possible to have `back_to_object` take a pack of
member pointers, performing each conversion in sequence from last to
first: `back_to_object<&Outer::in, &Inner::i>`. This would be valid as
long as the type of the next subobject in the sequence is the same as
the type of the class being pointed to by the previous.

But overall, it'd make a lot more sense to just make the tweaks I
suggested to P1839, which makes `reinterpret_cast`ing to an object
representation essentially give you a pointer to the start of that
object's representation within the array of the largest containing
contiguous object. After all, if you can go down into nested types, it
makes sense that you could go "up" too.

Given the above changes, it's more reasonable to just allow an NSDM
member pointer to be converted to an offset into its nearest
subobject. So the above becomes:

```
constexpr auto offset = offset_of<&Outer::in, &Inner::i>;
```

Where `offset_of` is a variable template that takes a pack of member
pointers, as described above.

Received on 2020-07-22 02:02:09