Hi all,

> The defaulted operator reloc can't work here anyway, since Transaction needs to be informed that DB has relocated. 
>
> So Transaction must already have a ctor Transaction(Transaction&&, DB&) and the relocator of T must invoke that ctor:
>
> T::operator reloc(T&& src) : _trans(std::move(src._trans), _db) {}

Indeed, my bad. I do have a gut feeling that synthesized relocation may lead to problems, mainly because it may change the destruction order of subobjects. However I cannot find a convincing example where the default move constructor + destructor on the whole object does the right thing, and where the default operator reloc() mixing true relocations and synthesized relocations on subobjects does not :/ I guess we'll keep synthesized relocation for the moment.

> template<class T> requires is_noexcept_relocatable_v<T>
> void swap(T& lhs, T& rhs) {
>    T temp = relocate(&lhs);
>    relocate_at(&rhs, &lhs);
>    new (&rhs) T(reloc temp);
> }

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:

template<class T> requires is_noexcept_relocatable_v<T>
void swap(T& lhs, T& rhs)
{
    std::aligned_storage_t<sizeof(T), alignof(T)> tmp;
    std::relocate_at(&lhs, reinterpret_cast<T*>(&tmp));
    std::relocate_at(&rhs, &lhs);
    std::relocate_at(reinterpret_cast<T*>(&tmp), &rhs);
}


BTW 'new (&rhs) T(reloc temp);' can be replaced by placement-reloc: 'reloc (&rhs) temp;'

> 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.

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:
- We don't enforce this at the language-level for other assignment operators.
- Why limit to the operator reloc? Assigning from any pr-value of the same type could be optimized into destructor + placement reloc, but that may break existing code?
- How to handle exceptions? This optimization could be turned off if the destructor or operator reloc are noexcept(false).
- Users can implement their assignment operator with std::swap which takes advantage of relocation (as it is often the case anyway).

> 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.)

Best regards,
Sébastien

On Tue, May 31, 2022 at 12:15 AM Edward Catmur <ecatmur@googlemail.com> wrote:
On Mon, 30 May 2022 at 12:07, Arthur O'Dwyer <arthur.j.odwyer@gmail.com> wrote:
On Mon, May 30, 2022 at 1:56 PM Edward Catmur <ecatmur@googlemail.com> wrote:
[...]
template<class T> requires std::is_noexcept_relocatable_v<T>
void swap(T& lhs, T& rhs) {
    T temp = relocate_at(&lhs);
    new (&lhs) T(relocate_at(&rhs));
    new (&rhs) T(relocate_at(&temp));
}

(relocate_at is the magic library function that (destroys and) relocates its argument into a returned prvalue.)

Terminology nit: `relocate_at` takes a pointer parameter `dest`, just like `construct_at` and `destroy_at`.
`relocate` with no suffix is the magic library function that relocates into a returned prvalue.

Ah, yeah, thanks. And that last relocate is incorrect; it should use the relocation operator keyword, since temp should not be destroyed at the end of scope:

template<class T> requires is_noexcept_relocatable_v<T>
void swap(T& lhs, T& rhs) {
    T temp = relocate(&lhs);
    relocate_at(&rhs, &lhs);
    new (&rhs) T(reloc temp);
}