Date: Wed, 21 Sep 2022 12:28:39 +0200
Hi all,
I almost want to propose a magic library function that takes a prvalue, a
>>> list of immediate bases and pointers to data members, and returns those
>>> subobjects as a destructurable class type. Something like:
>>>
>>> auto [b, x, arr] = std::decompose<B, &D::x_, &D::arr_>(reloc obj);
>>>
>>> It would check that the returned bases and data members are direct,
>>> distinct, non-overlapping and relocatable, and destroy any subobjects not
>>> mentioned. Then you would be able to return that `std::decompose` result
>>> directly, or perform further processing it.
>>>
>>
>> This looks good, but how are you able to mix type (B) and non-type
>> (&D::x) parameters in the function declaration?
>>
>
> Oh, cheating. Actually, it looks like there's some renewed momentum on
> P1985 Universal Template Parameters, so that would help here. Otherwise the
> non-type parameters could be passed by function argument, although that's
> less elegant.
>
> One annoying thing about this is that there's no proof that the caller has
> the right to access base B of D. We could mandate that by fiat, but it'd be
> nicer if there were a way to denote a specific base-of-derived, e.g. with
> the same syntax as a pointer to data member. That would be a lot more work,
> though, so just mandating that the base be accessible and unambiguous in
> the context of the caller might have to do.
>
I know what you mean and I don't know if that might do. I've seen and
written code that passes the pointer to some data member as a template
parameter to some static free function in the same TU. This is a good way
to factorise code that performs the same set of operations on different
data members. For instance:
template <auto Member> static void Do(D& self);
void D::do_a() { Do<&D::_a>(*this); }
void D::do_b() { Do<&D::_b>(*this); }
My point is that `Do` cannot access data members of T under normal
circumstances, but since the address is supplied as a template parameter,
it works. Now back to std::decompose. If done in `Do` then it could not
access the base class of `D`, and there is no similar way to grant it
access.
That may have to do as I cannot think of something better. Besides, if at
some point the language supports pointers to base members, we can still put
them into std::decompose.
> If `operator T()` were prvalue qualified that would help prevent bugs,
using deducing this on the object parameter:
>
> T relocating_wrapper<T>::operator T(this relocating_wrapper self) {
return self.opt_.pop(); }
>
> Users would have to go to considerable lengths to misuse it and the
resulting code would have a manifest use-after-move bug. Yes, there's still
a double relocation but that should be elidable.
That code is already okay w/t C++23 right?
> Well, this is a good argument for `reloc` to DTRT on references: that is,
`reloc r` should behave on reference typed variables r as
static_cast<decltype(r)>(r) while ending the lifetime of the reference and
removing it from scope.
Indeed that looks better.
> Fair enough, though I think it'd be worth mentioning in a "further
directions" section.
You make me doubt. How large would that chunk of the proposal be? At
minimum, I guess all template parameter pack functions that capture its
parameter pack with a forwarding reference will need to be reconsidered...
to be eventually replaced by the decltype(auto)... syntax, plus changing
std::forward to reloc. That would also open the door to the non-variadic
syntax: vector<T>::push_back(decltype(auto) x). And what about
decltype(Concept)... syntax?
Also, another technicality now that I am in the writing of the proposal, is
it okay to consider reloc as a new unary operator, or does it need to be a
keyword? I have heard here and there that new keywords are difficult to be
approved, that's why I am asking. Personally I don't see why reloc would
need to be a keyword.
>
I almost want to propose a magic library function that takes a prvalue, a
>>> list of immediate bases and pointers to data members, and returns those
>>> subobjects as a destructurable class type. Something like:
>>>
>>> auto [b, x, arr] = std::decompose<B, &D::x_, &D::arr_>(reloc obj);
>>>
>>> It would check that the returned bases and data members are direct,
>>> distinct, non-overlapping and relocatable, and destroy any subobjects not
>>> mentioned. Then you would be able to return that `std::decompose` result
>>> directly, or perform further processing it.
>>>
>>
>> This looks good, but how are you able to mix type (B) and non-type
>> (&D::x) parameters in the function declaration?
>>
>
> Oh, cheating. Actually, it looks like there's some renewed momentum on
> P1985 Universal Template Parameters, so that would help here. Otherwise the
> non-type parameters could be passed by function argument, although that's
> less elegant.
>
> One annoying thing about this is that there's no proof that the caller has
> the right to access base B of D. We could mandate that by fiat, but it'd be
> nicer if there were a way to denote a specific base-of-derived, e.g. with
> the same syntax as a pointer to data member. That would be a lot more work,
> though, so just mandating that the base be accessible and unambiguous in
> the context of the caller might have to do.
>
I know what you mean and I don't know if that might do. I've seen and
written code that passes the pointer to some data member as a template
parameter to some static free function in the same TU. This is a good way
to factorise code that performs the same set of operations on different
data members. For instance:
template <auto Member> static void Do(D& self);
void D::do_a() { Do<&D::_a>(*this); }
void D::do_b() { Do<&D::_b>(*this); }
My point is that `Do` cannot access data members of T under normal
circumstances, but since the address is supplied as a template parameter,
it works. Now back to std::decompose. If done in `Do` then it could not
access the base class of `D`, and there is no similar way to grant it
access.
That may have to do as I cannot think of something better. Besides, if at
some point the language supports pointers to base members, we can still put
them into std::decompose.
> If `operator T()` were prvalue qualified that would help prevent bugs,
using deducing this on the object parameter:
>
> T relocating_wrapper<T>::operator T(this relocating_wrapper self) {
return self.opt_.pop(); }
>
> Users would have to go to considerable lengths to misuse it and the
resulting code would have a manifest use-after-move bug. Yes, there's still
a double relocation but that should be elidable.
That code is already okay w/t C++23 right?
> Well, this is a good argument for `reloc` to DTRT on references: that is,
`reloc r` should behave on reference typed variables r as
static_cast<decltype(r)>(r) while ending the lifetime of the reference and
removing it from scope.
Indeed that looks better.
> Fair enough, though I think it'd be worth mentioning in a "further
directions" section.
You make me doubt. How large would that chunk of the proposal be? At
minimum, I guess all template parameter pack functions that capture its
parameter pack with a forwarding reference will need to be reconsidered...
to be eventually replaced by the decltype(auto)... syntax, plus changing
std::forward to reloc. That would also open the door to the non-variadic
syntax: vector<T>::push_back(decltype(auto) x). And what about
decltype(Concept)... syntax?
Also, another technicality now that I am in the writing of the proposal, is
it okay to consider reloc as a new unary operator, or does it need to be a
keyword? I have heard here and there that new keywords are difficult to be
approved, that's why I am asking. Personally I don't see why reloc would
need to be a keyword.
>
Received on 2022-09-21 10:28:53