Date: Mon, 28 Dec 2020 13:44:17 -0500
Re: Barry Revzin's comment, your right, I should probably have iterated a
use case.
This was in relation to some code that entailed a checked downcast on a
std::unique_ptr, something that is available for shared_ptr but absent in
the case of unique_ptr. This would specifically involve
dynamic_pointer_cast, but I decided to add the other two (notably omitting
reinterpret_pointer_cast, because that is probably more of a footgun then
regular reinterpret_cast, if one can believe that is possible).
On Sun, Dec 27, 2020 at 23:53 Arthur O'Dwyer via Std-Proposals <
std-proposals_at_[hidden]> wrote:
> On Sun, Dec 27, 2020 at 10:16 PM connor horman via Std-Proposals <
> std-proposals_at_[hidden]> wrote:
>
>> To std::default_deleter<T> [...] modify the constructor of
>> default_deleter to participate in overload resolution if any of the
>> following is true:
>> - U is a (possibly virtual) base class of T (does this need to allow
>> inaccessible base classes, or is that not permitted by an implicit
>> conversion),
>> - T is an accessible (possibly virtual) base class of U, in a context
>> unrelated to both T and U,
>> - U is (possibly-differently cv-qualified) T, or
>> - Both T and U are polymorphic types and U has a virtual destructor.
>>
>
> This part sounds like Lénárd Szolnoki's proposal for further constraining
> constructors of default_delete. I've got an implementation of it here:
>
> https://github.com/Quuxplusone/llvm-project/commit/ec526c22aa43954d35356f63b55867c16aa08e88
> and on Godbolt:
> https://p1144.godbolt.org/z/hEYz6c
> (Lénárd, have you written a paper for this proposal yet? Shouldn't you, or
> someone, do that?)
>
This wouldn't constrain it, it would actually widen it, by allowing
explicit construction in any of the cases one of the respective conversions
is defined (I don't believe you can have a unique_ptr<void> but you can
correct me on that), with one exception, that the case for dynamic_cast
across hierarchies is valid only when the destination type has a virtual
destructor.
>
>
>> template<typename U,typename T,typename D>
>> std::unique_ptr<U,REBIND_DELETER<D,U>>
>> dynamic_pointer_cast(std::unique_ptr<T,D>&& ptr)
>> - Performs a type qualification conversion of a unique_ptr, as though by
>> const_cast
>> - Only participates in overload resolution if REBIND_DELETER<D,U> is well
>> formed and constructible from D,
>> typename std::pointer_traits<typename D::pointer>::template rebind<U> is
>> implicitly convertible to typename REBIND_DELETER<D,U>::pointer, the
>> conversion from T* to U* may be performed by a valid dynamic_cast
>>
>
> I haven't tried to decipher your REBIND_DELETER stuff, but I suspect that
> you're going to have trouble with the fact that dynamic_cast is not
> reversible: it's quite possible to have an A *a1, dynamic_cast it to a B*,
> dynamic_cast the B* back to an A*, and end up with a completely different
> object! So if your strategy is to return a unique_ptr<B, SomeDeleter> with
> a deleter that dynamic_casts B* back to A* and deletes that with the
> original deleter... well, that's not going to work the way you wanted.
> https://godbolt.org/z/Y376Kr
>
REBIND_DELETER is designed to work similar to rebind for allocator_traits
or for pointer_traits, the same as the original Deleter, but "rebound" to
U.
However, it may be reasonable to constrain the conversions (particularily
dynamic_pointer_cast) to std::default_deleter (which, if the conversion
suceeds, under the rules I provided, would always have defined behaviour).
It may be possible to work with this design, though with some constraints.
Certainly in a generic case, you could not rely the reverse dynamic_cast
working properly, though for a standard delete t; (ie. with
default_deleter) it would work fine anyways (provided A has a virtual
destructor). I believe it would also work with allocators, using
dynamic_cast<void*>(p), then deallocating that (would a pseudo-destructor
call invoke the virtual destructor in the Most-derived object?). In either
case, it should 100% work for default_deleter.
I'm wondering if a similar issue could be present with the shared_ptr
overloads that already exist in the standard.
> <https://godbolt.org/z/Y376Kr>
>
> In real code, I'd want to see a function named clearly after its purpose,
> e.g. `ptr_downcast` if you want to use it for downcasts within a hierarchy:
> https://godbolt.org/z/abG33b
> If I saw someone trying to do this in the presence of a custom deleter,
> and/or via a generically named function like `static_pointer_cast`, I'd bet
> that they didn't know what they were doing.
>
Perhaps, though, as mentioned, these are existing functions in the
standard, so the name have precedent, I'm just adding overloads for the
unique_ptr case.
>
> –Arthur
> --
> Std-Proposals mailing list
> Std-Proposals_at_[hidden]
> https://lists.isocpp.org/mailman/listinfo.cgi/std-proposals
>
use case.
This was in relation to some code that entailed a checked downcast on a
std::unique_ptr, something that is available for shared_ptr but absent in
the case of unique_ptr. This would specifically involve
dynamic_pointer_cast, but I decided to add the other two (notably omitting
reinterpret_pointer_cast, because that is probably more of a footgun then
regular reinterpret_cast, if one can believe that is possible).
On Sun, Dec 27, 2020 at 23:53 Arthur O'Dwyer via Std-Proposals <
std-proposals_at_[hidden]> wrote:
> On Sun, Dec 27, 2020 at 10:16 PM connor horman via Std-Proposals <
> std-proposals_at_[hidden]> wrote:
>
>> To std::default_deleter<T> [...] modify the constructor of
>> default_deleter to participate in overload resolution if any of the
>> following is true:
>> - U is a (possibly virtual) base class of T (does this need to allow
>> inaccessible base classes, or is that not permitted by an implicit
>> conversion),
>> - T is an accessible (possibly virtual) base class of U, in a context
>> unrelated to both T and U,
>> - U is (possibly-differently cv-qualified) T, or
>> - Both T and U are polymorphic types and U has a virtual destructor.
>>
>
> This part sounds like Lénárd Szolnoki's proposal for further constraining
> constructors of default_delete. I've got an implementation of it here:
>
> https://github.com/Quuxplusone/llvm-project/commit/ec526c22aa43954d35356f63b55867c16aa08e88
> and on Godbolt:
> https://p1144.godbolt.org/z/hEYz6c
> (Lénárd, have you written a paper for this proposal yet? Shouldn't you, or
> someone, do that?)
>
This wouldn't constrain it, it would actually widen it, by allowing
explicit construction in any of the cases one of the respective conversions
is defined (I don't believe you can have a unique_ptr<void> but you can
correct me on that), with one exception, that the case for dynamic_cast
across hierarchies is valid only when the destination type has a virtual
destructor.
>
>
>> template<typename U,typename T,typename D>
>> std::unique_ptr<U,REBIND_DELETER<D,U>>
>> dynamic_pointer_cast(std::unique_ptr<T,D>&& ptr)
>> - Performs a type qualification conversion of a unique_ptr, as though by
>> const_cast
>> - Only participates in overload resolution if REBIND_DELETER<D,U> is well
>> formed and constructible from D,
>> typename std::pointer_traits<typename D::pointer>::template rebind<U> is
>> implicitly convertible to typename REBIND_DELETER<D,U>::pointer, the
>> conversion from T* to U* may be performed by a valid dynamic_cast
>>
>
> I haven't tried to decipher your REBIND_DELETER stuff, but I suspect that
> you're going to have trouble with the fact that dynamic_cast is not
> reversible: it's quite possible to have an A *a1, dynamic_cast it to a B*,
> dynamic_cast the B* back to an A*, and end up with a completely different
> object! So if your strategy is to return a unique_ptr<B, SomeDeleter> with
> a deleter that dynamic_casts B* back to A* and deletes that with the
> original deleter... well, that's not going to work the way you wanted.
> https://godbolt.org/z/Y376Kr
>
REBIND_DELETER is designed to work similar to rebind for allocator_traits
or for pointer_traits, the same as the original Deleter, but "rebound" to
U.
However, it may be reasonable to constrain the conversions (particularily
dynamic_pointer_cast) to std::default_deleter (which, if the conversion
suceeds, under the rules I provided, would always have defined behaviour).
It may be possible to work with this design, though with some constraints.
Certainly in a generic case, you could not rely the reverse dynamic_cast
working properly, though for a standard delete t; (ie. with
default_deleter) it would work fine anyways (provided A has a virtual
destructor). I believe it would also work with allocators, using
dynamic_cast<void*>(p), then deallocating that (would a pseudo-destructor
call invoke the virtual destructor in the Most-derived object?). In either
case, it should 100% work for default_deleter.
I'm wondering if a similar issue could be present with the shared_ptr
overloads that already exist in the standard.
> <https://godbolt.org/z/Y376Kr>
>
> In real code, I'd want to see a function named clearly after its purpose,
> e.g. `ptr_downcast` if you want to use it for downcasts within a hierarchy:
> https://godbolt.org/z/abG33b
> If I saw someone trying to do this in the presence of a custom deleter,
> and/or via a generically named function like `static_pointer_cast`, I'd bet
> that they didn't know what they were doing.
>
Perhaps, though, as mentioned, these are existing functions in the
standard, so the name have precedent, I'm just adding overloads for the
unique_ptr case.
>
> –Arthur
> --
> Std-Proposals mailing list
> Std-Proposals_at_[hidden]
> https://lists.isocpp.org/mailman/listinfo.cgi/std-proposals
>
Received on 2020-12-28 12:44:30