C++ Logo

std-proposals

Advanced search

Re: [std-proposals] Relocation in C++

From: Sébastien Bini <sebastien.bini_at_[hidden]>
Date: Mon, 2 May 2022 17:35:04 +0200
Hello,

> > 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.
>
> Yes, I recognize the issue. This can be resolved by stating that
implementations may refuse to relocate function parameters, and providing a
mechanism (e.g. an attribute) for the author to switch ABI to
callee-destroy.

It's more complicated than that. If relocation also destructs objects, then
you allow for a new category of unmovable objects to support relocation.
For example, std::lock_guard could be relocatable with no change.
Then how do you deal with this?

    void foo(lock_guard<mutex> lg);
    void fwd_to_foo(lock_guard<mutex> lg) { foo(reloc lg); }
    void bar(mutex& m)
    {
        std::lock_guard lg{m};
        fwd_to_foo(reloc lg);
    }

This code will work fine if reloc avoids the destructor call. But it will
not if "implementations refuse to relocate", as lock_guard is not movable.
We cannot have code optionally compile depending on which ABI we pick, can
we?

Best regards,
Sébastien

On Mon, May 2, 2022 at 3:54 PM Edward Catmur <ecatmur_at_[hidden]> wrote:

> On Mon, 2 May 2022 at 14:23, Sébastien Bini <sebastien.bini_at_[hidden]>
> wrote:
>
>> > 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.
>>
>
> Here you're explicitly adding another state to the object for the
> (original and subsequent) authors to have to keep track of. Currently an
> object (e.g. a container) has: - default (empty) state; - value-containing
> state; - moved-from state (usually the same as empty state). Adding a
> relocated state means more work and more potential bugs. If instead
> relocation destroys the object then there are no additional states and
> quite often fewer,; the moved-from state can be made the same as the empty
> state, since there is no need for a singular moved-from state.
>
> 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.
>>
>
> Yes, I recognize the issue. This can be resolved by stating that
> implementations may refuse to relocate function parameters, and providing a
> mechanism (e.g. an attribute) for the author to switch ABI to
> callee-destroy.
>
> 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.
>>
>
> Yes, this is a bit tricky (base classes in particular), though I have some
> ideas about possible syntaxes. However, much of the time `= default` will
> be sufficient, and in the remainder it may be acceptable to treat the
> source object as an xvalue, on the proviso that it will be immediately
> destructed. In other words I'm not convinced that this will be a problem in
> practice; some examples of classes that would need a user-provided
> relocation operation might help.
>

Received on 2022-05-02 15:35:16