Responses to Trivial Relocation NB Comments

Document #: P3915R0
Date: 2025-11-07 10:48 HST
Project: Programming Language C++
Audience: EWG
Reply-to: Pablo Halpern
<>

Contents

1 Introduction

2 What Trivial Relocation is

Moving an object of known type from one location to another

The trivial relocation operation is specified in terms of the abstract machine, object lifetimes, and object representation, without making any other implementation assumptions.

3 Categories of Trivial Relocation NB Comments

  1. US 10-023: Remove Just about Everything From P2686
  2. CN 1, US 46-085, US 45-081, BDS2-034: Make Trivial Relocation the Same as memcpy
  3. US 73-131, US 72-130, US 166-266, CA-133: Replace std::relocate with std::uninitialized_relocate_*
  4. US 47-084: Fix Implicit Trivial Relocatability
  5. FR-002-025, US 44-082: Conditional Trivial Relocation
  6. CA-136, CA-137: Allow const Qualified Types for Relocation Functions
  7. RO 1-134, US 74-135: P3858: restart_lifetime

4 Category 1: Remove Just about Everything From P2686

NB Comment Addressed: US 10-023: Remove *_if_eligible keywords

5 Category 2: Make Trivial Relocation the Same as memcpy

5.1 NB Comments Addressed

5.2 Assertions made in the NB Comments

If we could memcpy, memmove, and realloc objects of types for which is_trivially_relocatable_v<T> is true, then we could:

  1. Allow existing (mis-)uses of memcpy to become valid and
  2. Enable trivial relocation of byte-buffers containing (type-erased) nested objects.

5.3 The Reality of Treating Objects as Bytes

5.4 Aren’t Unions just as Problematic?

5.5 What Can We Do for Byte Buffers?

5.6 Options for Proceeding

  1. Status quo (no change to the CD)
  2. Require that trivial relocation be the same as memcpy
  3. Require that trivially_relocate be a bitwise operation
  4. Make most unions not TR
  5. Make polymorphic types not TR (or make it implementation-defined)

5.7 Option 1: Status Quo

The CD is not broken; it just has some limitations.

5.8 Option 2: Require that memcpy perform trivial relocation

5.9 Option 3: Require that trivially_relocate be a bitwise operation

5.10 Option 4: Make most unions not TR

5.11 Option 5: Make polymorphic types not TR (or make it implementation-defined)

5.12 Conclusion: Goals to Guide our Decision

5.13 Postscript: A trait for Detecting TR Within a Union

template <class T>
union __union_of { T x; __union_of() {}  ~__union_of() {} };

template <class T>
struct is_trivially_relocatable_in_union :
  is_trivially_relocatable<__union_of<T>> { };

6 Category 3: Replace std::relocate with std::uninitialized_relocate_*

6.1 NB Comments Addressed

6.2 Commonalities between std::relocate and std::uninitialize_relocate_*

6.3 Differences between std::relocate and std::uninitialize_relocate_*

std::relocate (CD and P2786)
std::uninitialize_relocate_* (P3516)
Does not throw (requires is_nothrow_relocatable) Might throw
Works with raw pointers Works with iterators
Automatically detects direction separate forward & reverse functions
Single function template 16 function templates and overloads including parallel and range versions consistent with other uninitialized_* function templates

Future proposals for relocation will involve relocation constructors, which cannot throw, lest both the source and destination be left in an indeterminate, non-destructible state.

6.4 A sample implementation of vector<T>::erase

Below, we have two implementations of vector<T>::erase (with allocator details elided for brevity). The implementation on the left works with either std::relocate from the CD and std::uninitialized_relocate from [P3516R2]. The implementation on the right works only with std::uninitialized_relocate, taking advantage of its built-in cleanup if an invoked move constructor throws an exception. The purpose of this example is to explore whether allowing throwing within std::uninitialized_relocate provides tangible benefits in code complexity and/or efficiency.

Using Nothrow Relocation
Using Throwing std::uninitialized_relocate
iterator erase(const_iterator pos)
{
  auto idx = pos - cbegin();
  T* p = __start + idx;  // mutable ptr
  if constexpr (is_nothrow_relocatable_v<T>) {
    p->~T();
    [uninitialized_]relocate(p + 1, __last, p);
  }
  else {
    for (++p ; p < __last; ++p)
      *(p-1) = std::move(*p);
    p->~T();
  }
  --__last;
  return begin() + idx;
}
iterator erase(const_iterator pos)
{
  auto idx = pos - cbegin();
  iterator mpos = begin() + idx; // mutable iter
  mpos->~T();
  try {
    uninitialized_relocate(mpos + 1, cend(), mpos);
    --__last;
    return mpos;
  }
  catch (...) {
    __last = __start + idx;
    throw;
  }
}

6.5 Does Throwing uninitialized_relocate Result in Cleaner Code?

6.6 Raw Pointers vs. Iterators

6.7 Options that Might Increase Consensus

7 Category 4: Fix Implicit Trivial Relocatability

7.1 NB Comment Addressed

7.2 What’s Broken #1

The design exposition for P2786 states:

However, the following types are not trivially relocatable according to the CD, despite being trivially copyable and not opting out in any way:

struct A { const int i; };  // deleted assignment; not TR according to the CD
struct B { int&      r; };  // deleted assignment: not TR according to the CD

7.3 What’s Broken #2

The design exposition for P2786 states:

However, type D below is not trivially relocatable despite being a simple aggregate of trivially relocatable types.

struct C trivially_relocatable_if_eligible { // trivially relocatable
  C(){}
  C(const C&) = delete;
};
struct D { C m; };  // deleted move constructor; not TR according to the CD

7.4 The Source of the problem

11.2 [class.prop]/2:

2 A class C is default-movable if

  • (2.1) overload resolution for direct-initializing an object of type C from an xvalue of type C selects a constructor that is a direct member of C and is neither user-provided nor deleted. [Emphasis mine]
  • (2.2) overload resolution for assigning to an lvalue of type C from an xvalue of type C selects an assignment operator function that is a direct member of C and is neither user-provided nor deleted. [Emphasis mine]
  • (2.3) C has a destructor that is neither user-provided nor deleted. [Emphasis mine]

The authors of P2786 recognize this issue as a cut-and-paste error introduced when refactoring the wording section.

7.5 Proposed Resolution

The authors of P2786 are in favor the following resolution:

Change 11.2 [class.prop]/2 as follows.

2 A class C is default-movable if

  • (2.1) overload resolution for direct-initializing an object of type C from an xvalue of type C selects a constructor that is a direct member of C and is neither user-provided nor explicitly deleted.
  • (2.2) overload resolution for assigning to an lvalue of type C from an xvalue of type C selects an assignment operator function that is a direct member of C and is neither user-provided nor explicitly deleted.
  • (2.3) C has a destructor that is neither user-provided nor explicitly deleted.

8 Category 5: Conditional Trivial Relocation

8.1 NB Comments Addressed

8.2 Assertions made in the NB Comments

   template <class T>
   struct X trivially_relocatable_if_eligible(some_trait<T>) ...

Is the above a declaration of X or the definition and initialization of trivially_relocatable_if_eligible of type struct X, initialized with some_trait<T>?

8.3 Observations

template <class... T> struct __nested_obj_buffer;

Where a __nested_obj_buffer contains a byte buffer of sufficient size and alignment for one object of any type in T.... This class will be trivially relocatable iff all types in T... can be bitwise relocated (i.e., they are all trivially copyable).

The authors of P2786 are mostly neutral on whether to make *_if_eligible real (non-contextual) keywords or whether they should have a conditional predicate, despite seeing little need for either. It is too late in the process to consider any of the redesign alternatives described in US 44-082, and we believe that the current mitigation strategies are sufficient to avoid the need to remove or dramatically alter the entire trivial relocation feature.

9 Category 6: Allow const Qualified Types for Relocation Functions

9.1 NB Comments Addressed

Both comments would allow const-qualified T for the T* template arguments, claiming consistency with constructors and destructors, which do work on const-qualified types.

9.2 Observations

The authors of P2786 are mostly neutral on the proposed changes, despite not seeing a strong need for them. The two changes should be consistent — if one is adopted, the other should also be adopted.

10 Category 7: P3858: restart_lifetime

NB Comments Addressed

Although one of the authors of P2786 is also an author of P3858, we feel that the proposed feature is too novel and untested for C++26. The facility described in [P3858R0] are not needed for trivial relocation to be a complete and useful feature, nor is there anything in the CD that would prevent adoption of such a facility in the future.

11 References

[CWG3049] Pablo Halpern. 2025-07-10. Implicitly deleted move operation should not disable trivial relocation.
https://wg21.link/cwg3049
[N4034] Pablo Halpern. 2014-05-27. Destructive Move.
https://wg21.link/n4034
[P2786R13] Pablo Halpern, Joshua Berne, Corentin Jabot, Pablo Halpern, Lori Hughes. 2025-02-14. Trivial Relocatability For C++26.
https://wg21.link/p2786r13
[P3516R2] Louis Dionne, Giuseppe D’Angelo. 2025-05-16. Uninitialized algorithms for relocation.
https://wg21.link/p3516r2
[P3780R0] Giuseppe D’Angelo. 2025-07-10. Detecting bitwise trivially relocatable types.
https://wg21.link/p3780r0
[P3858R0] David Sankel, Jon Bauman, Pablo Halpern. 2025-10-06. A Lifetime-Management Primitive for Trivially Relocatable Types.
https://wg21.link/p3858r0