On Thu, 5 May 2022 at 21:48, Arthur O'Dwyer <arthur.j.odwyer@gmail.com> wrote:
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_); }
        };
        v.emplace_back(S{pw});
    }

Hm, and what if reserve() throws? You need to end the lifetime (i.e., destroy) the source object in that case, so you need a bool to say whether relocation has occurred. (Or, for strong exception safety, you may want to do something else - but you still need to known whether the lifetime of the source object has ended.) So a std::relocate_wrapper<T> will be necessary, since you won't want users writing it themselves.
 
Disagreed, and here's why.
[...]

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.

I see a relocator as having a single purpose, which is to perform the inverse of prvalue materialization: to convert an lvalue to a prvalue. That prvalue will subsequently be materialized into another lvalue, but that's not what relocation is *for*. Absolutely, when relocate of a complete object is implemented as move-plus-destroy, it is performing two tasks, and should either of those throw it's safe for both objects to be destroyed according to how far the move-plus-destroy has proceeded. And obviously trivial relocators can't throw. The problem is user-defined relocation.

In practical terms, what happens if we have a type T with user-defined destructor, and to release some resource ~T accesses members x and y, with x having throwing (move-plus-destroy) relocate? Suppose x's destructor throws after dst.x has been move-constructed from src.x; then there is no object of T (with members x and y both within their lifetime) that we can call ~T on, so the resource will leak. This is unacceptable, so the only safe thing to do if an exception is thrown midway through a user-defined relocate is to terminate.

The slightly odd thing here is that it means it's only absolutely necessary for a type's relocator to be noexcept (or at least default to noexcept(true)) if it has a user-defined destructor. For consistency I feel that all relocators (that is, relocators proper, not move+destroy combinations) should default to noexcept, but I'm open to arguments otherwise.

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:

Ah, great. I'll definitely make the time to read it in depth.

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.

Yes, and I've said that std::optional should hide the manual lifetime-management away so you don't get double-free. And that, accordingly, std::relocate should have a considerably uglier name to dissuade casual usage.

But also I'm becoming more convinced that a reloc operator (applicable to automatic variables, with straightforward behavior) should be viable, will be useful and should alleviate a lot of confusion around move semantics. Relocation could well require language change anyway, so this is an opportunity to make it useful for everyone.