C++ Logo

std-discussion

Advanced search

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

From: Julien Villemure-Fréchette <julien.villemure_at_[hidden]>
Date: Tue, 08 Aug 2023 16:05:17 -0400
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
>>

Received on 2023-08-08 20:05:33