C++ Logo

std-proposals

Advanced search

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

From: Sébastien Bini <sebastien.bini_at_[hidden]>
Date: Fri, 19 Aug 2022 11:35:18 +0200
On Thu, Aug 18, 2022 at 6:49 PM Edward Catmur <ecatmur_at_[hidden]>
wrote:

> Actually, I think that aliasing on relocating assignment is not feasible.
> The reason is ABI: we can currently write a (non-special) assignment
> operator with the signature we want to use for relocating assignment, so
> that ABI cannot change. In particular, we can declare the relocating
> assignment operator in a header and then later define it out-of-line in a
> source file, possibly by default: `T& T::operator=(T) = default;`. It would
> be inelegant if the behavior of that assignment operator were required to
> be different dependent on whether it was defaulted in-class or out-of-class.
>

Yes, for the aliasing to work, the = reloc part must appear in the
declaration. I admit this is not ideal, and we should split the aliasing
part from the definition.

As such, we could write:

    reloc T& operator=(T); // = default; or user-defined in implementation
file

"reloc" here would turn on the aliasing magic. It would live in the same
space as "virtual" and "static", and would not be part of the function
signature. It needs not to be rewritten in the function definition. "reloc"
could only be added on the prvalue-assignment operator, and users would not
be allowed to take the address of such a function (as it is a special
function with a special ABI).

And as sugar, we could add that if the prvalue-assignment operator is
defaulted in its declaration then the reloc aliasing is implicit. This
works as today there is no default code generation for the
prvalue-assignment operator.

    // in class declaration:
    T& operator=(T) = default: // reloc is implicitly added

That being said, now lies the problem on how to write a custom
implementation for this assignment operator. For the relocation ctor, we
worked around this by saying that omitted subobject initializers would
automatically call the appropriate reloc ctor of said subobject. We cannot
do that magic for the reloc-assign, as there is no initializer list.
However, I believe that std::relocate can come to the rescue here:

    class T : B
    {
        D _d;
        reloc T& operator=(T rhs)
        { // exception safety is omitted for the example
            B::operator=(std::relocate(static_cast<B*>(&rhs)));
            _d = std::relocate(&rhs._d);
            return *this;
        }
    };

std::relocate is a nice fit here as, thanks to the aliasing, we know that
rhs dtor will not be called. In the same manner, std::relocate could also
be used to initialize subobjects in the reloc ctor.

As a side note, I wonder to what extent std::relocate could not be a
special function that, as reloc, simply turns some address into a prvalue,
without actually performing any relocation. That could allow nice
optimization bits in user-defined reloc ctor or assignment operator.

Sure, but the user is capable of doing this themselves?
>
> 1. T& operator=(T rhs) { static_assert(noexcept(...)); this->~T(); return
> *new (this) T(reloc rhs); }
> 2. T& operator=(T) = default; // ill-formed if ~T() is user-provided
>
> Yes, they don't get automatic selection between the two, but I don't
> really see that as an issue; it'd be quite surprising.
>

I feel mixed with having = default; doing only memberwise assignments:

   - This will force all users that have a user-provided dtor to write
   their own reloc-assignment operator, while it is likely that their reloc
   ctor and dtor are noexcept :/
   - If = default also generates destroy-and-relocate then it enables nice
   recursion. Without this some classes will need to write their own
   reloc-assignment operator while the default generated destroy-and-relocate
   would fit like a glove. Take unique_ptr for instance. This one has a
   legitimate user-provided dtor, and as such is not eligible to
   memberwise-reloc-assign (which is good, as it would otherwise leak its own
   resource). As a consequence std::unique_ptr would need to provide a
   reloc-assign definition, which will ultimately, just free its resource and
   steal that of the source object. Something that the default generated
   destroy-and-relocate would do for free.

My point is that, if = default (a) generates destroy-and-relocate, and (b)
falls back to memberwise-reloc-assignments, then users will almost never
need to write any implementation. Think of case (a) as the base/halting
cases of any recursive function.

Regards,
Sébastien

Received on 2022-08-19 09:35:31