Date: Tue, 31 May 2022 09:30:57 -0400
On Tue, May 31, 2022 at 4:51 AM Sébastien Bini <sebastien.bini_at_[hidden]>
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
>
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
>
Received on 2022-05-31 13:31:10