Date: Sun, 5 Jun 2022 21:45:21 +0100
On Thu, 2 Jun 2022 at 09:39, Sébastien Bini <sebastien.bini_at_[hidden]>
wrote:
> *About T(T) vs operator reloc(T&&):*
>
> T(T src) is special in two ways:
>
> - It alleviates the destructor call on src;
> - Behind the scenes, T(T) only gets the address of the source object
> (as if by T(T&)), and not an actual copy as its signature would suggest.
>
> Right?
>
Yes.
> In addition, in T(T src), src becomes an lvalue in that context, and as
> such writing manually the default implementation becomes impossible:
> struc T : B
> {
> D _data;
> T(T src) : B{src}, _data{src._data} {} // /!\ Calls copy constructors /!\
> };
>
> Which means that we will have to rely on the same tricks as operator
> reloc(T&&) (i.e. non-written initializers will be initialized by the reloc
> ctor or synthesized relocation).
>
> My point is that I don't see much benefit of T(T) over operator
> reloc(T&&).
>
> - Semantically speaking, I get your point that T(T) would be more
> correct, but it seems so counter-intuitive to use as it does not obey the
> same rules as any other function that takes a prvalue as parameter.
> - On the other hand operator reloc(T&&) doesn't lie on the fact that
> it takes an address, and its signature doesn't imply that its source
> parameter will be destructed. Instead, the very name of the operator
> suggests the opposite.
>
> We can still have `reloc obj` to merely change the value category of `obj`
> instead of returning the new instance, regardless of the signature we
> choose for relocation (T(T), operator reloc(T&&) or even operator reloc(T)).
>
Well, yes; certainly arguing about the exact spelling is bikeshedding. One
aspect I think needs further exploration is a similar facility to allow
running code before the relocation of subobjects begins, and I think
constructor delegation could be the facility to solve that; the other
aspect is symmetry with operator=(T).
*About reloc operator changing the value category:*
>
> Consider the following code:
> void prsink(T);
> void xsink(T&&);
>
> T x, y;
> prsink(reloc x); // here destructor call on x is avoided
> xsink(reloc y); // destructor of y is called
>
> Before, the reloc operator was in charge of making the decision of not
> calling some destructor. This is no longer the case, as it now really
> depends on how the prvalue is consumed. Am I right?
>
Yes, I think so. Although even previously you still had the issue for
discarded-value expression (you want to call the destructor, not the
relocator).
void sink(T);
> void sink(T&&);
>
> void foo(T val)
> {
> sink(reloc val);
> }
>
> What happens now if T has opted-out of the ABI break with the
> non-standardized attribute? Before the reloc operator was in charge of
> building the new instance and could fallback to the move constructor. Now
> this is no longer the case as it depends on how the prvalue is consumed.
> Isn't it problematic as now the language needs to distinguish between
> prvalues whose destructor calls can be alleviated and the others? Which
> overload of sink is picked in that case?
>
The ABI break opt-out doesn't affect overloading, so sink(T) will be called
in either case. Supposing T has the attribute applied, the move or copy
constructor of T will be called to construct a temporary from val and this
temporary is the argument to sink(T); this is much as would happen today,
and is exactly the same if T does not have a candidate relocation
operator/relocating constructor.
wrote:
> *About T(T) vs operator reloc(T&&):*
>
> T(T src) is special in two ways:
>
> - It alleviates the destructor call on src;
> - Behind the scenes, T(T) only gets the address of the source object
> (as if by T(T&)), and not an actual copy as its signature would suggest.
>
> Right?
>
Yes.
> In addition, in T(T src), src becomes an lvalue in that context, and as
> such writing manually the default implementation becomes impossible:
> struc T : B
> {
> D _data;
> T(T src) : B{src}, _data{src._data} {} // /!\ Calls copy constructors /!\
> };
>
> Which means that we will have to rely on the same tricks as operator
> reloc(T&&) (i.e. non-written initializers will be initialized by the reloc
> ctor or synthesized relocation).
>
> My point is that I don't see much benefit of T(T) over operator
> reloc(T&&).
>
> - Semantically speaking, I get your point that T(T) would be more
> correct, but it seems so counter-intuitive to use as it does not obey the
> same rules as any other function that takes a prvalue as parameter.
> - On the other hand operator reloc(T&&) doesn't lie on the fact that
> it takes an address, and its signature doesn't imply that its source
> parameter will be destructed. Instead, the very name of the operator
> suggests the opposite.
>
> We can still have `reloc obj` to merely change the value category of `obj`
> instead of returning the new instance, regardless of the signature we
> choose for relocation (T(T), operator reloc(T&&) or even operator reloc(T)).
>
Well, yes; certainly arguing about the exact spelling is bikeshedding. One
aspect I think needs further exploration is a similar facility to allow
running code before the relocation of subobjects begins, and I think
constructor delegation could be the facility to solve that; the other
aspect is symmetry with operator=(T).
*About reloc operator changing the value category:*
>
> Consider the following code:
> void prsink(T);
> void xsink(T&&);
>
> T x, y;
> prsink(reloc x); // here destructor call on x is avoided
> xsink(reloc y); // destructor of y is called
>
> Before, the reloc operator was in charge of making the decision of not
> calling some destructor. This is no longer the case, as it now really
> depends on how the prvalue is consumed. Am I right?
>
Yes, I think so. Although even previously you still had the issue for
discarded-value expression (you want to call the destructor, not the
relocator).
void sink(T);
> void sink(T&&);
>
> void foo(T val)
> {
> sink(reloc val);
> }
>
> What happens now if T has opted-out of the ABI break with the
> non-standardized attribute? Before the reloc operator was in charge of
> building the new instance and could fallback to the move constructor. Now
> this is no longer the case as it depends on how the prvalue is consumed.
> Isn't it problematic as now the language needs to distinguish between
> prvalues whose destructor calls can be alleviated and the others? Which
> overload of sink is picked in that case?
>
The ABI break opt-out doesn't affect overloading, so sink(T) will be called
in either case. Supposing T has the attribute applied, the move or copy
constructor of T will be called to construct a temporary from val and this
temporary is the argument to sink(T); this is much as would happen today,
and is exactly the same if T does not have a candidate relocation
operator/relocating constructor.
Received on 2022-06-05 20:45:34