C++ Logo

std-discussion

Advanced search

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

From: Mykola Garkusha <garkusha.mykola_at_[hidden]>
Date: Tue, 8 Aug 2023 21:16:02 +0100
http://eel.is/c++draft/basic#:~:text=If%20two%20objects,end%20note%5D That
is what reinterpret_cast says about pointer interconvertible

http://eel.is/c++draft/ptr.launder#2 std::launder clearly allows to get a
"pointer to T" given *the address* where T's storage starts



On Tue, Aug 8, 2023 at 9:05 PM Julien Villemure-Fréchette via
Std-Discussion <std-discussion_at_[hidden]> wrote:

> This seems strange. std::launder explicitly says it cannot convert a
> pointer to 'o1' to a pointer to 'o2' unless 'o1' and 'o2' are pointer
> interconvertible, otherwise it is UB.
>
> from my understanding, I thought that std::launder was only an escape
> hatch for dealing with stale references to non transparently replaceable
> objects.
>
>
> On August 6, 2023 1:20:00 p.m. EDT, Mykola Garkusha via Std-Discussion <
> std-discussion_at_[hidden]> wrote:
>
>> Thanks, this is super clear actually. Everything settled after deep
>> diving into pointer conversions for non-pointer-interconvertible objects.
>>
>> So then with std::launder, this shall work as it should return pointer
>> value "pointer to T"?
>>
>> T2* ptr2 = std::laudner(reinterpret_cast<T2 *>(&storage_))
>> ptr2->value...
>>
>>
>> On Sun, Aug 6, 2023 at 5:39 PM Matthew House via Std-Discussion <
>> std-discussion_at_[hidden]> wrote:
>>
>>> 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).
>>> --
>>> Std-Discussion mailing list
>>> Std-Discussion_at_[hidden]
>>> https://lists.isocpp.org/mailman/listinfo.cgi/std-discussion
>>>
>> --
> Std-Discussion mailing list
> Std-Discussion_at_[hidden]
> https://lists.isocpp.org/mailman/listinfo.cgi/std-discussion
>

Received on 2023-08-08 20:16:16