C++ Logo

std-proposals

Advanced search

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

From: Sébastien Bini <sebastien.bini_at_[hidden]>
Date: Fri, 6 May 2022 10:55:48 +0200
Hi all,

> Right, I think there's a mismatch between the way Avi was
using/understanding the terminology and the way you (and I) use it.

Yes, I've seen this confusion a few times (I don't know if that's the case
here). We do need to carefully pick our words :p

> {
> std::string src1 = "hello world";
> std::string dst1 = std::relocate(src1);
> std::string src2 = "hello world";
> std::string *dstp = ~~~~;
> ::new ((void*)dstp) std::string(std::relocate(src2));
> ~~~~
> } // Here dst1.~string() is implicitly called, as are src1.~string() and
src2.~string().
> // But src1 and src2 were already destroyed, so that's a double-free bug
and Bad Stuff happens.
> // P1144 doesn't try to solve this part; it just claims you shouldn't be
doing that.

The reloc operator allows for all that except it would be perfectly safe
(no double destructor call):

    std::string src1 = "hello world";
    std::string dst1 = reloc src1;
    std::string src2 = "hello world";
    std::string *dstp = /* some uninitialized memory address */;
    reloc ((void*)dstp) std::string(src2); /* there is a "placement reloc"
variant that is proposed as well */

*> Yes, it's unfortunate, but the alternatives (not being able to reloc
function parameters at all, or breaking relocate-only types) are worse.*

In my opinion, if we are to support relocatable-only types, then we need to
provide some way to:
1. relocate local objects (be it function parameter or not)
2. relocate an existing object inside a container
3. relocate an object out of a container (that one should be easy with
std::relocate_at)

*> You can't relocate function parameters, but it's viable to relocate
*constructor* parameters, since constructors don't have types. So optional
can have an additional constructor taking its prvalue argument by
callee-destroy; this won't require new kinds of function pointer since you
can't form a function pointer to a constructor.*

I agree that constructor prvalue parameters could be relocated, since
constructors are kind of special. But still there must be some way to
indicate that this constructor must have that special ABI.

*> You'll need a relocate_wrapper type that can be (possibly implicitly)
constructed from a callee-destroy prvalue, and that releases that value (by
relocation to a prvalue) on request. So you call either
push_back(relocate_wrapper(reloc x)), or relocate_back(reloc x).*

This is similar to what is in the proposal: std::reloc_wrapper (page 24,
https://github.com/SebastienBini/cpp-relocation-proposal/blob/main/relocation.pdf).
You can relocate an object inside it, check if it has an object, and
relocate it out. The problem is then that you need to perform two
relocations (one inside the wrapper, one from the wrapper to the container).

In my proposal, I suggested push_back overloads that took a tag type as
first parameter and a prvalue as second:
std::vector<T>::push_back(std::relocate_t, T);

The std::reloc_wrapper of my proposal was introduced mainly for std::pair
and std::tuple, where a ctor API with std::relocate_t tag type would be
confusing, and that those types (pair and tuples) already know how to
unwrap std::reference_wrapper.

I also would like to point out that the second revision of the proposal
already allows for all three points I mentioned above (to support
relocatable-only types), with no new ABI, and no special wrapper for most
uses. And this remains possible because reloc does not destruct objects
(which is a downside I admit). I am just reminding you this because at some
point we need to decide between the two options: (a) non-destructive
relocation, but easier to handle, or (b) destructive relocation but at the
cost of more complexity.

*> But std::relocate_at is a Standard library function. It doesn't need to
be implementable; you just write language that says what its effects are,
the actual implementation will just call a compiler builtin. Even in the
trivial case you don't want to call memcpy, since you want it usable in
constexpr.*

That's a fair point. Also, if no one sees a point of having non-default
base-or-member initialisers in operator reloc member function, and that its
source object is already destructed once the function body is reached, what
would be wrong with this operator reloc?

struct T : B
{
    M data;

    /*
     * operator reloc returns a new instance.
     * actual relocation (base-class and data-member relocations) happen
before the function body is called (as if by =default).
     * The function body only serves to make some adjustments, like fixing
self-references.
     * 'this' pointer in reloc function body points to the newly
constructed instance.
     * The source object is considered destructed and is no longer usable,
that's why it does not appear as a parameter.
     * It is not possible to provide custom base-or-member-initializer.
     */
    operator reloc()
    {
        data.self = *this; // fix self-references here
    }
};

Received on 2022-05-06 08:56:00