Date: Mon, 2 May 2022 15:23:38 +0200
Hello Edward,
Thank you for your reply as well.
> As I understand it, in this proposal relocation of an automatic variable
> does not terminate its lifetime. Will references and pointers to that
> object continue to be valid? This does not appear to be addressed in the
> paper.
Indeed, pointers and references on a relocated value become dangling. The
proposal does not address this issue.
In my opinion this is a larger problem in C++ and goes much beyond the
proposed reloc operator.
> What advantage does this proposal provide over the use of e.g. clang-tidy
> user-after-move detection?
You can see my answer to Giuseppe D'Angelo. In short:
- allows for better optimizations (see std::list move ctor), and even
allows trivial relocation ;
- improved class design possibilities for class invariants that do not work
well with move constructors ;
- allows to move const volatile objects
> The proposal appears to state that the destructor is still called,
implying
> that relocation must leave the object in a destructible empty state,
> presumably usually identical to the moved-from state. This implies
> branching in the destructor to check for this state, whereas if lifetime
> was ended (thus no destructor call on relocated objects) this could be
> avoided.
Very often the relocated-state and moved-state would be identical yes. They
are different in a design point of view though, one that must remain a
valid object, the other not.
The destructor must already do those checks for the moved-from state. I'll
reply more on the destructor below.
> A destructor may call arbitrary methods on the class under destruction,
> which will have no way to tell that the instance they are invoked on was
> previously relocated. How will you ensure safety in this scenario? It
seems
> that there will be a considerable burden on the class author to ensure
that
> all methods called from the destructor are safe to be called on a
relocated
> instance, since the language will not ensure this, and a considerable
> maintenance burden going forward.
I don't see how that's different from the destructor call on a moved
instance.
> Far better to terminate the lifetime immediately and omit calling the
> destructor entirely.
Yes I agree. In fact the first version of the paper worked this way: the
relocation destructor acted as a constructor for the new instance and as a
destructor for the relocated instance. As such, the destructor of the
relocated instance was not called, as the instance was already considered
destructed.
However, as others have pointed it out, this leads to an ABI break.
Consider:
void sink(T z);
void fwd_to_sink(T y)
{
sink(reloc y); // oops
}
void foo()
{
T x;
fwd_to_sink(reloc x);
}
In fwd_to_sink, reloc cannot omit the call to the destructor of 'y', as it
is called by 'foo'. For this to work:
- fwd_to_sink would need to somehow return some information on the
destruction state of its parameters.
- or change the call convention so that the parameters are destructed in
the called function body instead of the callee site.
So I definitely agree, but I don't think this paper can be accepted with an
ABI break...
> My suggestion for syntax would be *prvalue qualification* on class member
> functions, with relocation implemented as prvalue qualified conversion to
> the same (class) type:
>
> struct A {
> operator A() prvalue; // or "reloc", or "~", or "&(false)", etc. -
> note that the qualification position is relatively free syntax
> };
I thought of something similar:
struct T {
operator reloc(); // return a new instance of T, like a constructor
would do
// or as a static variant with signature:
operator reloc(T&& self);
};
But I found it very inconvenient to write the function body:
struct T : public B {
operator reloc() {
// how to construct the B part of T into the new T using B's reloc?
// how to initialise the data-members of T using the reloc
}
}
This can hardly reuse the constructor syntax (with base class and
data-member initialisers). I couldn't find anything convincing in that
path, but I can still give it more thought.
Seb
On Mon, May 2, 2022 at 1:14 PM Edward Catmur <ecatmur_at_[hidden]> wrote:
>
>
> On Mon, 2 May 2022 at 11:24, Sébastien Bini via Std-Proposals <
> std-proposals_at_[hidden]> wrote:
>
>> Stating that the moved-from object is invalid and should not be touched
>> again is (in my opinion) a bad class design. You may have a look at
>> https://herbsutter.com/2020/02/17/move-simply/, which further motivated
>> me to write this paper. Quoting Herb Sutter: "In C++, an object is valid
>> (meets its invariants) for its entire lifetime, which is from the end of
>> its construction to the start of its destruction (see [basic.life]/4).
>> Moving from an object does not end its lifetime, only destruction does, so
>> moving from an object does not make it invalid or not obey its invariants."
>> Relocation allows to make an object invalid before it is destructed, albeit
>> it is never touched again except by its destructor.
>>
>
> A destructor may call arbitrary methods on the class under destruction,
> which will have no way to tell that the instance they are invoked on was
> previously relocated. How will you ensure safety in this scenario? It seems
> that there will be a considerable burden on the class author to ensure that
> all methods called from the destructor are safe to be called on a relocated
> instance, since the language will not ensure this, and a considerable
> maintenance burden going forward.
>
> Far better to terminate the lifetime immediately and omit calling the
> destructor entirely.
>
>
>> The reloc operator introduces a language-level guarantee (no use after
>> relocation) that will always be better than an external tool that
>> developers may simply not run.
>>
>
> If this is language-level, the language can immediately terminate the
> lifetime. In the if/else case, lifetime can be terminated with a destructor
> call at the end of the branch where relocation does not occur.
>
> I am still open to discuss the syntax. I would be in favor of a solution
>> that doesn't introduce a new constructor and a new type of reference, but I
>> still haven't found it :/
>>
>
> Firstly, don't introduce a new value category; we already have more than
> enough. If the instance lifetime is terminated immediately, there is no
> need for another value category since there will no longer be an object in
> its storage.
>
> My suggestion for syntax would be *prvalue qualification* on class member
> functions, with relocation implemented as prvalue qualified conversion to
> the same (class) type:
>
> struct A {
> operator A() prvalue; // or "reloc", or "~", or "&(false)", etc. -
> note that the qualification position is relatively free syntax
> };
>
Thank you for your reply as well.
> As I understand it, in this proposal relocation of an automatic variable
> does not terminate its lifetime. Will references and pointers to that
> object continue to be valid? This does not appear to be addressed in the
> paper.
Indeed, pointers and references on a relocated value become dangling. The
proposal does not address this issue.
In my opinion this is a larger problem in C++ and goes much beyond the
proposed reloc operator.
> What advantage does this proposal provide over the use of e.g. clang-tidy
> user-after-move detection?
You can see my answer to Giuseppe D'Angelo. In short:
- allows for better optimizations (see std::list move ctor), and even
allows trivial relocation ;
- improved class design possibilities for class invariants that do not work
well with move constructors ;
- allows to move const volatile objects
> The proposal appears to state that the destructor is still called,
implying
> that relocation must leave the object in a destructible empty state,
> presumably usually identical to the moved-from state. This implies
> branching in the destructor to check for this state, whereas if lifetime
> was ended (thus no destructor call on relocated objects) this could be
> avoided.
Very often the relocated-state and moved-state would be identical yes. They
are different in a design point of view though, one that must remain a
valid object, the other not.
The destructor must already do those checks for the moved-from state. I'll
reply more on the destructor below.
> A destructor may call arbitrary methods on the class under destruction,
> which will have no way to tell that the instance they are invoked on was
> previously relocated. How will you ensure safety in this scenario? It
seems
> that there will be a considerable burden on the class author to ensure
that
> all methods called from the destructor are safe to be called on a
relocated
> instance, since the language will not ensure this, and a considerable
> maintenance burden going forward.
I don't see how that's different from the destructor call on a moved
instance.
> Far better to terminate the lifetime immediately and omit calling the
> destructor entirely.
Yes I agree. In fact the first version of the paper worked this way: the
relocation destructor acted as a constructor for the new instance and as a
destructor for the relocated instance. As such, the destructor of the
relocated instance was not called, as the instance was already considered
destructed.
However, as others have pointed it out, this leads to an ABI break.
Consider:
void sink(T z);
void fwd_to_sink(T y)
{
sink(reloc y); // oops
}
void foo()
{
T x;
fwd_to_sink(reloc x);
}
In fwd_to_sink, reloc cannot omit the call to the destructor of 'y', as it
is called by 'foo'. For this to work:
- fwd_to_sink would need to somehow return some information on the
destruction state of its parameters.
- or change the call convention so that the parameters are destructed in
the called function body instead of the callee site.
So I definitely agree, but I don't think this paper can be accepted with an
ABI break...
> My suggestion for syntax would be *prvalue qualification* on class member
> functions, with relocation implemented as prvalue qualified conversion to
> the same (class) type:
>
> struct A {
> operator A() prvalue; // or "reloc", or "~", or "&(false)", etc. -
> note that the qualification position is relatively free syntax
> };
I thought of something similar:
struct T {
operator reloc(); // return a new instance of T, like a constructor
would do
// or as a static variant with signature:
operator reloc(T&& self);
};
But I found it very inconvenient to write the function body:
struct T : public B {
operator reloc() {
// how to construct the B part of T into the new T using B's reloc?
// how to initialise the data-members of T using the reloc
}
}
This can hardly reuse the constructor syntax (with base class and
data-member initialisers). I couldn't find anything convincing in that
path, but I can still give it more thought.
Seb
On Mon, May 2, 2022 at 1:14 PM Edward Catmur <ecatmur_at_[hidden]> wrote:
>
>
> On Mon, 2 May 2022 at 11:24, Sébastien Bini via Std-Proposals <
> std-proposals_at_[hidden]> wrote:
>
>> Stating that the moved-from object is invalid and should not be touched
>> again is (in my opinion) a bad class design. You may have a look at
>> https://herbsutter.com/2020/02/17/move-simply/, which further motivated
>> me to write this paper. Quoting Herb Sutter: "In C++, an object is valid
>> (meets its invariants) for its entire lifetime, which is from the end of
>> its construction to the start of its destruction (see [basic.life]/4).
>> Moving from an object does not end its lifetime, only destruction does, so
>> moving from an object does not make it invalid or not obey its invariants."
>> Relocation allows to make an object invalid before it is destructed, albeit
>> it is never touched again except by its destructor.
>>
>
> A destructor may call arbitrary methods on the class under destruction,
> which will have no way to tell that the instance they are invoked on was
> previously relocated. How will you ensure safety in this scenario? It seems
> that there will be a considerable burden on the class author to ensure that
> all methods called from the destructor are safe to be called on a relocated
> instance, since the language will not ensure this, and a considerable
> maintenance burden going forward.
>
> Far better to terminate the lifetime immediately and omit calling the
> destructor entirely.
>
>
>> The reloc operator introduces a language-level guarantee (no use after
>> relocation) that will always be better than an external tool that
>> developers may simply not run.
>>
>
> If this is language-level, the language can immediately terminate the
> lifetime. In the if/else case, lifetime can be terminated with a destructor
> call at the end of the branch where relocation does not occur.
>
> I am still open to discuss the syntax. I would be in favor of a solution
>> that doesn't introduce a new constructor and a new type of reference, but I
>> still haven't found it :/
>>
>
> Firstly, don't introduce a new value category; we already have more than
> enough. If the instance lifetime is terminated immediately, there is no
> need for another value category since there will no longer be an object in
> its storage.
>
> My suggestion for syntax would be *prvalue qualification* on class member
> functions, with relocation implemented as prvalue qualified conversion to
> the same (class) type:
>
> struct A {
> operator A() prvalue; // or "reloc", or "~", or "&(false)", etc. -
> note that the qualification position is relatively free syntax
> };
>
Received on 2022-05-02 13:23:49