On Tue, 17 May 2022 at 21:27, Thiago Macieira <thiago@macieira.org> wrote:
 As you said:

> Yes, you're correct. However note that if we add one more data member z, we
> can end up in this situation: x successfully relocates, y throws, but to
> release the resource T needs x and z.

This is improperly-coded and is the developer's fault.

Really? It seems to me that any class that owns a resource and has non-trivial members has this problem in some form. Sure, you could say that this is a layer violation, but sometimes you do want to layer resources - not all resources are new-delete memory.
 
> I would say that if a class has a user-defined destructor (and so
> is suspected of owning resources directly), and if at least one of its
> bases-and-members has throwing relocate, then its relocator cannot be
> defaulted (declared or defined as defaulted) but must be user-defined (or
> deleted). Also, a user-defined relocator (i.e. one not declared as
> defaulted or deleted) should not be noexcept if unspecified; either they
> should be noexcept(false) if unspecified, or a noexcept specification
> should be required.

You'll never get a "suspected of having" in the standard. The choice needs to
be by the signature of the methods involved. If those are synthesised, then
the signature is obtained from the members of that class and its base
class(es), but that does not change the rule.

We already default special member functions according to the presence of other special member functions on the same class; constructors suppress the default constructor, the copy constructor suppresses the move constructor, and I'm fairly sure the destructor suppresses something though I can never remember what. So it should be fine for the presence of a user-defined destructor to have some effect on the relocator.

We have a precedent for noexcept(true)-by-default and that's the destructor. I
think it could be acceptable to make the relocator obey the same rule because
after it's run -- and whether it has thrown or not -- one object's lifetime
has ended.

The justification for making destructors noexcept by default is to be able to invoke destructors during stack unwinding, since that is a process that cannot be allowed to fail. There is no such requirement on relocation, so I really don't see why allowing it to fail could cause any problems. 

Also, while relocation does end the lifetime of an object (as a region of storage), the *value* that was located in that object continues to exist, dematerialized to a prvalue (and possibly subsequently rematerialized to another lvalue), so I don't think relocation is all that similar to destruction.
 
We also have the choice here whether such synthesised relocator is permissible
in the first place if either the move constructor or the destructor are
noexcept(false). That is, the rule would be that it's deleted by default and
if one wants to have a relocator in such a class, they'll have to explicitly
declare one. We can always relax this rule later.f

We want aggregate-style classes (without user-defined special member functions) to Just Work.  Deleting the relocator by default would not only be inefficient, it would be problematic if any member is relocate-only (has a relocator but no copy or move constructor). It would be incongruous for users to have to explicitly default a relocator when all the other special member functions are implicitly defaulted.

That's not a deal-breaker, but it's the next best thing. If adding this member
changes the ABI, then it won't be added for a lot of code that needs to retain
compatibility, including std::unique_ptr.

And if we don't get the ability to pass smart pointers in registers, and to pass around relocate-only types and use them without gymnastics, then there's really no point in this proposal at all. If some platforms are scared of breaking ABI, so be it: they can leave std::unique_ptr without trivial relocation and document the conformance defect; users who are performance-sensitive will switch to a work-alike unique_ptr from Boost, Abseil or whoever that does have trivial relocation, or write their own. We broke ABI for std::string ten years ago; have we really lost our courage since then? Especially since now we have more tools - inline namespaces, modules, sanitizers - to make an ABI break less painful.