On Thu, May 5, 2022 at 10:33 AM Sébastien Bini via Std-Proposals <std-proposals@lists.isocpp.org> wrote:
[Avi Kivity wrote:] 
> That is, the destructor is called immediately, since the reloc operator ended the scope of the name. I think that's a good thing.

I cannot be called [...] as it could then lead to objects being destructed twice

Right, I think there's a mismatch between the way Avi was using/understanding the terminology and the way you (and I) use it.
- Immediately after a call to std::relocate_at (or the core-language `operator reloc` or whatever), the source object has become destroyed and its lifetime is over.
- But the destructor is not called.
This terminology is confusing in the context of today's C++, because today's C++ does not (admit|permit) any difference between the ideas of "the object's lifetime ends" and "the object's destructor is called." We are able to use the same English phrase — "the object is destroyed" — to mean both notions, interchangeably, without any ambiguity, because they are literally synonymous in C++ today.
In C++-with-relocation (whether P1144 or otherwise), it is possible for an object's lifetime to end in either of two different ways: either its destructor is called, or it is relocated-from. In the former case, its destructor is called; in the latter case, its destructor is never called. The phrase "the object is destroyed" should now be avoided, because it is ambiguous: it could be taken to mean either "the object's destructor is called," or "the object's lifetime ends," and these notions are now no longer synonymous.

And how do you relocate an object inside a container? Do you need an std::vector<T>::push_back(std::optional<T>) API? I'd find that disturbing.

You can in fact use the regular `emplace_back`, via a pattern I call the "super constructing super elider":
However, it's still pretty disturbing. :)

Here's a test case using P1144r6 syntax with the magic library function `T std::relocate(T*)`:

    void relocate_into_a_vector(std::vector<Widget>& v, Widget *pw) {
        struct S {
            Widget *pw_;
            operator Widget() const { return std::relocate(pw_); }

(Note that if you try this on Godbolt you'll get a move-and-destroy, because my P1144 fork's `T std::relocate(T*)` is insufficiently magic.)

> I agree that the relocator should be unconditionally noexcept, such that it
> should not be possible to throw out from it (but std::terminate, not UB).
> This is because the source object will have been partially relocated and
> thus will be incapable of releasing resources.


Disagreed, and here's why.
C++ permits destructors to throw. If part of a destructor throws, then C++ takes care of cleaning up the rest of the object as part of stack unwinding: in this godbolt, even though ~Car's attempt to call ~Wheels threw an exception, the runtime still takes care of calling ~Doors, so that no RAII resources are leaked. 
The upshot is that if a destructor throws, the caller (of that destructor) is still guaranteed that all the object's RAII resources were freed; even though the destructor "failed," the object is still 100% guaranteed to have become destroyed and its lifetime is 100% over.

Also, more familiarly, C++ permits constructors to throw. If part of a constructor throws, then C++ takes care of cleaning up the already-constructed parts of the object. The upshot is that if a constructor throws, the caller (of that constructor) is guaranteed that all the new object's RAII resources were either freed or never-allocated; the constructor failed and so the object is 100% guaranteed to have become destroyed and its lifetime never started.

Analogously, your `operator reloc` should permit relocators to throw.  The relocator has two jobs: to construct the destination object and to destroy the source object. Both of these jobs agree about what should happen if an exception is thrown: C++ should take care of cleaning up the already-constructed and/or not-yet-destroyed parts of the destination and/or source object. If relocation fails, then constructing-the-new-object failed (so there is no new object) and also destroying-the-old-object "failed" (so there is no old object either).  A failed relocation operation means that neither object exists anymore.

Of course the Standard Library is free to not-work with program-defined types that are not nothrow_relocatable, in the same way that today's Standard Library doesn't-work with program-defined types that are not nothrow_destructible. There's no shame in that. But the correct core-language behavior seems indisputable to me.

I guess this is good motivation for me to finally publish a P1144r6. P1144r5 gave the wrong exception behavior to `std::uninitialized_relocate` (i.e. P1144r5 disagreed with my indisputable logic above, because I had not yet figured it out). David Stone convinced me to change it, more than a year ago. But I never got around to submitting P1144r6 to a mailing. It's just been sitting around in draft format since March 2021:

We do need a new syntax to call this special relocation constructor. The main reason is for std::relocate_at implementation (which is needed for container implementations). std::relocate_at(const T* src, T* dst) either (a) makes memcpy if trivially relocatable, (b) constructs dst with some kind of placement-new with the relocation ctor/dtor, or (c) placement-new move ctor + dtor: new (dst) T{const_cast<T&&>(*src)}; src->~T();

In the first two revisions of my proposal, (b) was implemented by a call to the relocation ctor directly: new (dst) T{const_cast<T~>(*src)}; That worked well because of the relocation reference.
The syntax I suggested in my last email (T::T reloc(T&&)) allowed to write the placement new: new (dst) T reloc{const_cast<T&&>(*src)};

If your right-hand side has dynamic lifetime already, you don't need core-language support for `T reloc`; you can just use a tagged constructor or anything really. Dynamic-lifetime objects admit library-only solutions, because it's always the library author's job to keep track of dynamic lifetimes — the core language needn't get involved in that case.
    ::new ((void*)dst) T(std::relocating, std::move(*src));
As I understand your `operator reloc` proposal, though, you're trying to find a core-language way to tell the compiler that some static or automatic variable is now "dead" and shouldn't be double-destroyed at the end of its natural lifetime:
    std::string src1 = "hello world";
    std::string dst1 = src1 reloc; // or something??
    std::string src2 = "hello world";
    std::string *dstp = ~~~~;
    ::new ((void*)dstp) std::string(src2 reloc);
    } // Here dst1.~string() is implicitly called.
    // src1 and src2 were already destroyed via `reloc` so here nothing happens to them.

P1144r6 proposes a magic library utility function `std::relocate(T*)`:
With this magic function, you could express the above as
    std::string src1 = "hello world";
    std::string dst1 = std::relocate(src1);
    std::string src2 = "hello world";
    std::string *dstp = ~~~~;
    ::new ((void*)dstp) std::string(std::relocate(src2));
    } // Here dst1.~string() is implicitly called, as are src1.~string() and src2.~string().
    // But src1 and src2 were already destroyed, so that's a double-free bug and Bad Stuff happens.
    // P1144 doesn't try to solve this part; it just claims you shouldn't be doing that.

my $.02,