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.