C++ Logo

std-discussion

Advanced search

Re: Is it valid use reinterpret_cast to form pointer to object?

From: Matthew House <mattlloydhouse_at_[hidden]>
Date: Sun, 6 Aug 2023 12:39:05 -0400
On Sun, Aug 6, 2023 at 10:03 AM Mykola Garkusha
<garkusha.mykola_at_[hidden]> wrote:
> Just want to clarify this clearly to be 100% sure.
>
> > Not in general; [basic.lval] para. 11 is specifically about reading
> > and writing scalar values through a glvalue, and nothing else. Since
> > ptr2 is a T* pointer pointing to an std::byte object, you can't do
> > most things with ptr2 that you could do with an arbitrary T* pointer,
>
> ptr2 should point to T2 now
>
> https://eel.is/c++draft/expr#reinterpret.cast-7
> https://eel.is/c++draft/expr#reinterpret.cast-3

That's not how it works in this case, since the two objects 'storage_'
and '*ptr_' are not pointer-interconvertible. By
[expr.reinterpret.cast] para. 7, 'T* ptr2 =
reinterpret_cast<T*>(&storage_)' is equivalent to 'T* ptr2 =
static_cast<T*>(static_cast<void*>(&storage_))'. Here, '&storage_'
points to the entire 'storage_' array (not to its first element
'storage_[0]', as I mistakenly said earlier), so by [expr.static.cast]
para. 4 and [conv.ptr] para. 2, 'static_cast<void*>(&storage_)' also
points to the 'storage_' array.

For 'T* ptr2 = static_cast<T*>(...)', we have to look at
[expr.static.cast] para. 14: *if* there exists an object of type
(similar to) T pointer-interconvertible with 'storage_', then 'ptr2'
will point to that T object; *otherwise*, 'ptr2' will be a T* pointer
pointing to the 'storage_' array. But if we look at the definition of
pointer-interconvertible objects in [basic.compound] para. 4, none of
those bullets apply for 'storage_' and '*ptr_': they are different
objects, neither is a union object, and neither is the first
non-static member of the other. Therefore, 'ptr2' is a T* pointer
pointing to the std::byte[sizeof(T)] array 'storage_', not a T*
pointer pointing to the T object '*ptr_'.

> Not sure how strict aliasing (https://eel.is/c++draft/expr#basic.lval-11) can be violated in the example below, as the access (for scalar variables) would be done through dynamic type, as T2 lifetime has started and storage_ provides storage for it.
> T2* ptr2 = reinterpret_cast<T2 *>(&storage_)
> ptr2->value/*scalar*/;
>
> > ([expr.ref] para. 8). Most (but not all) usual
> > operations require that the dynamic type of the pointee actually
> > matches the type of the pointer.
>
> I don't see how the above violates the sample below.
> reinterpret_cast<T2 *>(&storage_)->....

Look at [expr.ref] para. 8. The class member access 'ptr2->value' is
illegal, regardless of what is done with that glvalue afterwards. The
"strict aliasing" rule in [basic.lval] para. 11 only applies *after*
that class member access is done, and even then it helps only if
'value' is a char, unsigned char, or std::byte.

> The type of expression above is T* and that is what the *actual* result of reintepret_cast is as T2's object address should be equal to the address of storage_ and storage_ provides storage for T2's object which is in turn tested within storage_. Although I agree that std::launder would make it more explicit and I can be wrong on this. https://eel.is/c++draft/basic#intro.object-9

The std::byte[sizeof(T)] array does provide storage for the T object,
but this is irrelevant here, since the array object and the T object
are not pointer-interconvertible. The only importance of providing
storage is that the T object is nested within the array by
([intro.object] para. 4), so constructing the T object doesn't end the
lifetime of the array ([basic.life] para. 1).

Received on 2023-08-06 16:39:17