C++ Logo

std-proposals

Advanced search

Re: Making bit_cast Useful

From: connor horman <chorman64_at_[hidden]>
Date: Thu, 24 Sep 2020 10:33:09 -0400
On Thu, Sep 24, 2020 at 10:13 Arthur O'Dwyer <arthur.j.odwyer_at_[hidden]>
wrote:

> On Thu, Sep 24, 2020 at 9:39 AM connor horman via Std-Proposals <
> std-proposals_at_[hidden]> wrote:
>
>> On Thu, 24 Sep 2020 at 08:51, Andrew Giese via Std-Proposals <
>> std-proposals_at_[hidden]> wrote:
>>
>>> * If r is an expression of type T, the expression std::bit_cast<T>(r),
>>>> if well-formed, shall be equivalent to copy constructing the result from r,
>>>> except that the expression treats copy constructors which are defined as
>>>> deleted as though they were defined as default instead, and does not call
>>>> move constructors even if r is an rvalue.
>>>
>>>
>>> std::bit_cast only works for types that are TriviallyCopyable, and will
>>> emit a compiler error otherwise. This "Reflexive" case seems perhaps
>>> unnecessary then.
>>>
>>> It was added for completeness. It can also be used when the copy
>> constructor is defined as deleted, because TriviallyCopyable allows any of
>> the copy constructor, move constructor, copy assignment, and move
>> assignment to be deleted IIRC, as long as at least one is not. Though,
>> you'd be right, I should mention that it's only the case if the expression
>> is well-formed. I've changed it to that effect.
>>
>
> FWIW, "does not call move constructors" is also redundant, since T is
> trivially copyable. For a trivially copyable type, there's no difference
> between "calling" the trivial move constructor, "calling" the trivial copy
> constructor, or just copying the bits.
>
Technically, for the same reason I mentioned regarding the copy
constructor, calling the move constructor may have a different result (it
may be ill-formed to construct T from an rvalue). While it may be
reasonable to apply the same workaround, as you mentioned, there is no
difference between calling a trivial move constructor vs. a trivial copy
constructor, and mentioning it always calls a trivial copy constructor is
more consistent with the function type.

>
> reference_wrapper:
>>>> * If r is an expression of type std::reference_wrapper<T>and U is
>>>> (possibly differently cv-qualified) T, then the expression
>>>> std::bit_cast<U*>(r), shall be well-formed and an equivalent expression to
>>>> const_cast<U*>(std::addressof(static_cast<T&>(r)))
>>>> * If r is an expression of type T*, which points to an object, and U is
>>>> (possibly differently cv-qualified) T, then the expression
>>>> std::bit_cast<std::reference_wrapper<U>>(r) shall be well-formed, and an
>>>> equivalent expression to std::ref(*const_cast<U*>(r)). If r does not point
>>>> to an object, the behaviour is undefined. If T is an incomplete type other
>>>> than (possibly cv-qualified) void or an array of unknown bound, the
>>>> expression treats T as though it was completed. If T is an array of unknown
>>>> bound, the expression treats T as though it were an array with length 1.
>>>>
>>>
> This series of requirements seems novel. Is your intent to mandate that
> std::reference_wrapper<T> should be implemented as a standard-layout class
> type with one member of type T* stored at offset zero, and no other
> members, and no padding? Is this already mandated somewhere in C++20, or
> is this a novelty?
>
To my knowledge, it is a novel requirement, however (as you do mention) all
implementations I've seen do this that way.

>
> AFAIK all vendors' implementations do implement reference_wrapper this
> way, but if you're adding requirements on the implementation, the right
> place for those is probably in the section on reference_wrapper, not in the
> section on bit_cast.
>
It may be reasonable to put that into reference_wrapper, in which case the
former rule is redundant. I'd still need the second rule to allow the
backwards cast. I'd assume it wouldn't be unreasonable to codify this
existing practice, citing p0907 which required the 2's-complement
representation for signed integers, which was the representation used in
practice.

>
>
>> Unresolved Questions:
>>>> std::optional<std::reference_wrapper<T>> is currently not treated
>>>> separately. It may be nice to apply a similar pair of rules as for regular
>>>> std::reference_wrapper, with the additional clause that an empty optional
>>>> bit_casts to a null pointer and the reverse. However, unlike the
>>>> reference_wrapper requirements which are effective in libstdc++ and libc++
>>>> (because its implemented with a single raw pointer member),
>>>> std::optional<std::reference_wrapper<T>> is not implemented as such (though
>>>> libc++ interestingly implements the storage for std::optional<T&> in case
>>>> the standard ever decides to permit it, and this is done simply using a raw
>>>> pointer). A possibility is not enforcing well-formedness, but keeping the
>>>> behaviour if it is well-formed.
>>>>
>>>
> Right, you can't mandate that sizeof(optional<reference_wrapper<T>>) ==
> sizeof(T*) unless you're willing to take an ABI break, which no vendor is.
> For more on the possibilities of "tombstone representations" in
> std::optional, see my C++Now 2018 presentation "The Best Type Traits C++
> Doesn't Have." <https://www.youtube.com/watch?v=MWBfmmg8-Yo>
>
Indeed, I want to avoid breaking ABI as much as possible. All of the rules
here are intended to be ones which are already applied in practice.
Enforcing a practice that is both novel in the standard and in the field is
not something I'd want to do.

> <https://www.youtube.com/watch?v=MWBfmmg8-Yo>
>
<https://www.youtube.com/watch?v=MWBfmmg8-Yo>
>
>
> The reference_wrapper rules don't allow void*->reference_wrapper<void>. Is
>>>> there a reason to allow this? Can reference_wrapper<void> exist?
>>>>
>>>
> Formally, no; in [refwrap]/1, reference_wrapper<T> implicitly requires
> that it be possible to have "an object or function of type T," which is not
> the case when T=void.
> http://eel.is/c++draft/refwrap#general-1
> Also, there is no special wording to eliminate
> `reference_wrapper<T>::operator T&() const` when T=void, which again
> implies that instantiating reference_wrapper<void> would make your program
> ill-formed.
>
Ok, that makes sense, so I don't need to hack around void* =>
reference_wrapper<void> (or deal with reference_wrapper<char> =>
reference_wrapper<void>)

>
> –Arthur
>
>
>

Received on 2020-09-24 09:33:22