On Tue, May 31, 2022 at 4:51 AM Sébastien Bini <sebastien.bini@gmail.com> wrote:

Actually I am not sure there are any real benefits of having `T std::relocate(T*)` if we have the reloc operator (which is a safer version of std::relocate) and std::relocate_at. swap can be written without std::relocate:

You're right that swap can be written in terms of std::relocate_at, not std::relocate.
But you need std::relocate, AFAIK, in order to implement an "emplace_back-by-relocating" operation. See
https://quuxplusone.github.io/blog/2022/05/18/std-relocate/
and in particular its emplace_back example on Godbolt:
https://godbolt.org/z/cqPP4oeE9
I don't think this example is possible unless you have access to something like std::relocate that can produce prvalues.
 
BTW 'new (&rhs) T(reloc temp);' can be replaced by placement-reloc: 'reloc (&rhs) temp;'

Unless there's a really good reason to have two distinct yet synonymous syntaxes, I strongly recommend picking one and sticking with it. (And I recommend the standard, placement-new, syntax, because you certainly can't get rid of it from the language.)


> gsl::non_null<int*> p1, p2, p3 = ...;
> gsl::non_null<int*> q1 = reloc p1;  // OK in your world
> q1 = reloc p2;  // OK??
> std::swap(q1, p3);  // OK??

`reloc obj` (with obj of type T) merely returns a temporary object of type T, built by relocating from obj (just like std::relocate). obj is considered destructed and as such is not left in a moved-from state. We rely on mechanisms such as copy-elision or clearly identified patterns (object initialisation with a reloc statement, like: `auto a = reloc obj;`) to remove that temporary in most cases.

With `q1 = reloc p2;`, as it is phrased in the paper, reloc p2 will return a temporary that is assigned into q1. p2 is considered destructed (relocated into the temporary). Then q1's move assignment operator will be called.

q1 has no "move assignment operator", remember?  We're postulating that gsl::non_null<int*> is one of these types that lacks a moved-from state. So it certainly cannot have a move assignment operator nor a move constructor.

We could go further and claim that assigning from a reloc statement transparently calls destructor + placement reloc (std::destroy_at(&q1); reloc (&q1) p2;). I am not sure that's desirable at all

I agree with your aesthetic sense there: rewriting `a = b` into "destroy a, construct a from b" doesn't seem desirable. But you do need to tackle this problem somehow.

[...]
> Similarly, if you "relocated" a `std::mutex`, bad things would happen.  You can't have a mutex suddenly change memory addresses and expect the rest of the program to just be cool with that.

Yes there are objects that are not even relocatable, like std::mutex. Such types can prohibit relocation by deleting their operator reloc: `operator reloc(T&&) = delete;` (which should already be implicitly deleted since their move constructor is also deleted.)

Hm. Could you write out the rules that you're imagining for
    operator reloc(T&&) = default;
    operator reloc(T&&) = delete;
as well as what happens by default?
(IIUC, you're imagining that `operator reloc` would work the same as `operator<=>` — an explicitly =defaulted definition gets a memberwise implementation, but if you don't say anything then the operator is absent, not defaulted.)
It sounds like you're imagining simultaneously that
    struct S1 {
        M m;
        S1(S1&&);
        operator reloc(S1&&) = default;
    };
will have a defaulted (memberwise) `operator reloc`, and that
    struct S2 {
        M m;
        S2(S2&&) = delete;
    };
will have a deleted `operator reloc` "since their move constructor is also deleted" (i.e., S2's operator reloc won't just be absent; it'll be deleted).

–Arthur