This page is a snapshot from the LWG issues list, see the Library Active Issues List for more information and the meaning of New status.
std::trivially_relocate
needs stronger preconditions on "nested" objects with dynamic lifetimeSection: 20.2.6 [obj.lifetime] Status: New Submitter: Giuseppe D'Angelo Opened: 2025-06-23 Last modified: 2025-06-28
Priority: Not Prioritized
View other active issues in [obj.lifetime].
View all other issues in [obj.lifetime].
View all issues with New status.
Discussion:
In 20.2.6 [obj.lifetime] the std::trivially_relocate
function
is missing a precondition, that is, that any object alive in the range being
relocated is itself trivially relocatable.
wrapper
type like:
// wraps a T template<typename T> struct wrapper { alignas(T) std::byte data[sizeof(T)]; };
then one can build a non-trivially relocatable object into wrapper
objects:
struct NTR { ~NTR() {} }; static_assert(not std::is_trivially_relocatable_v<NTR>); using WS = wrapper<NTR>; static_assert(std::is_trivially_relocatable_v<WS>); // OK
And now one can do this:
WS* ws = /* … */; // create a wrapper new (&ws->data) NTR(); // create a NTR object into it std::trivially_relocate(ws, ws+1, dest); // should be UB
Attempting to trivially relocate *ws
should result in undefined
behavior because NTR
isn't trivially relocatable. I don't believe that
this fact is correctly captured by the preconditions of
std::trivially_relocate
.
P
,
then this code:
struct P { virtual void f(); }; static_assert(std::is_trivially_relocatable_v<P>); using WP = wrapper<P>; WP* wp = /* … */; // create a wrapper new (&wp->data) P(); // create a P object into it std::trivially_relocate(wp, wp+1, dest); // implementation defined
is well-defined or UB, depending on the implementation. This is because
on some implementations trivially relocating a polymorphic type requires
patching its virtual table pointer; cf. the discussion in chapter 15.1
of P2786R13. However the "type erasure" done by
wrapper<P>
in the example (ultimately, it is just an array
of bytes) does not allow implementations to do such patching, and the code
is going to fail at runtime. Therefore this case also needs to be discussed by
std::trivially_relocate
's specification.
Proposed resolution:
This wording is relative to N5008.
Modify 20.2.6 [obj.lifetime] as indicated:
[Drafting note: For the general part of the issue (all objects in the range must be of trivially relocatable type), we append another point at the end of the existing Preconditions: element of
trivially_relocate
.
For the specifics of polymorphic types, we amend at the end of the description the existing Remarks: element]
template<class T> T* trivially_relocate(T* first, T* last, T* result);-9- Mandates: […]
-10- Preconditions:
(10.1) —
[first, last)
is a valid range.(10.2) —
[result, result + (last - first))
denotes a region of storage that is a subset of the region reachable throughresult
(6.8.4 [basic.compound]) and suitably aligned for the typeT
.(10.3) — No element in the range
[first, last)
is a potentially-overlapping subobject.(10.?) — All objects whose storage is being provided for (6.7.2 [intro.object]) by objects in the
[first, last)
range are of trivially relocatable type.-11- Postconditions: […]
-12- Returns:result + (last - first)
. -13- Throws: Nothing. -14- Complexity: Linear in the length of the source range. -15- Remarks: The destination region of storage is considered reused (6.7.4 [basic.life]). No constructors or destructors are invoked. If any polymorphic object (11.7.3 [class.virtual]) exists in storage provided for (6.7.2 [intro.object]) by objects in the[first, last)
range, it is implementation-defined whether the behavior is undefined. [Note 2: Overlapping ranges are supported. — end note]