C++ Logo

std-proposals

Advanced search

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

From: Sébastien Bini <sebastien.bini_at_[hidden]>
Date: Tue, 23 Aug 2022 13:40:14 +0200
On Mon, Aug 22, 2022 at 10:08 PM Edward Catmur <ecatmur_at_[hidden]>
wrote:

> On Mon, 22 Aug 2022 at 14:36, Sébastien Bini <sebastien.bini_at_[hidden]>
> wrote:
>
>> I'm in favor of the default operator to be aliased. However this is not
>> enough as if the assignment operator of each subclass is not aliased, then
>> you may still end-up making that many copies (or relocations).
>>
>> I believe we do need to provide a way to clearly annotate the assignment
>> operator as aliased (that reloc keyword comes in). Also, it would serve for
>> assignment operators that are defaulted in the implementation file only.
>>
>
> I contend that it's enough to mandate aliasing for relocatable-by-ABI
> types. Note that this doesn't affect ABI in that the callee still does the
> same amount of work; the difference is that the caller is required to
> observe that the source and target cannot alias and that therefore the
> parameter can alias the source instead of having to be a relocated
> temporary.
>

If I understand correctly, you suggest that aliasing be mandated only for
types that have a relocation constructor and have not opted-out of the ABI
break? I like this idea, it further avoids another keyword in the operator
declaration.

But what about trivial types, which follow the rule of zero?

struct S { int _d; S& operator=(S rhs) { _d = rhs._d; return *this; } };

They will get a relocation constructor for free, even if none of their
subobjects explicitly provides a relocation constructor. Had they declared
a prvalue-assignment operator, then it would change its ABI silently. It's
okay for them to silently go from caller-destroy to callee-destroy as their
destructor is a no-op, but aliasing will also force the parameter to share
the same address as the caller's site source object, which is a silent ABI
break.

They could still opt-out with the class attribute, but that requires manual
inspection of the code from users.


> What's still lacking though, but not a blocking issue in my opinion, is
>> that in the assignment operator implementation, users cannot elegantly call
>> a subobject's prvalue assignment operator. What if std::relocate where just
>> a mere cast, similar to std::move?
>>
>> template <class T>
>> T std::relocate(const T& d) { return static_cast<T>(d); }
>>
>
> That's spelled `auto(d)` (in C++23). But it performs decay-copy. The
> signature you've written has to perform a copy, because a reference to
> const cannot (logically) modify its referent.
>

Nice, I didn't know of that upcoming auto(d) feature.


> We need a "dangerous" std::relocate, leaving the source object in
> destroyed state, for use in containers: std::vector, std::optional; memory
> pools, etc.
>

See below.


> Then users can write: (with: `class T : B { D _d; };` )
>>
>> T& T::operator=(T rhs)
>> {
>> B::operator=(std::relocate(rhs));
>> _d = std::relocate(rhs._d);
>> return *this;
>> }
>>
>
> What would that do if D is non_null<unique_ptr<int>>? D cannot be copied,
> and there's no suitable empty state for _d to be left in after a move, so
> it must be left in a destroyed state; but it must not be double-destroyed.
>

Nice find... Indeed, for the relocation to happen the caller must be in the
position of preventing the destructor call on the source object. With an
std::relocate just promoting to a prvalue is not enough to trigger
relocation, as you pointed out. Even for types that are only-relocatable,
such an std::relocate could not guarantee relocation.

That also means that, in the prvalue-assignment operator, users cannot
manually forward to a subobject prvalue-assignment operator if that
subobject is relocation only. (Well, unless we use the union trick or
otherwise).

And yes, we do need an unsafe destructive relocation function then.

Received on 2022-08-23 11:40:27