|
| 1 | +<?xml version='1.0' encoding='utf-8' standalone='no'?> |
| 2 | +<!DOCTYPE issue SYSTEM "lwg-issue.dtd"> |
| 3 | + |
| 4 | +<issue num="4283" status="New"> |
| 5 | +<title>`std::trivially_relocate` needs stronger preconditions on "nested" objects with dynamic lifetime</title> |
| 6 | +<section> |
| 7 | +<sref ref="[obj.lifetime]"/> |
| 8 | +</section> |
| 9 | +<submitter>Giuseppe D'Angelo</submitter> |
| 10 | +<date>23 Jun 2025</date> |
| 11 | +<priority>99</priority> |
| 12 | + |
| 13 | +<discussion> |
| 14 | +<p> |
| 15 | +In <sref ref="[obj.lifetime]"/> the `std::trivially_relocate` function |
| 16 | +is missing a precondition, that is, that any object alive in the range being |
| 17 | +relocated is itself trivially relocatable. |
| 18 | +<p/> |
| 19 | +We know the objects in the range are trivially relocatable, because |
| 20 | +there is a <i>Mandates</i>: element for this. The current draft has precise |
| 21 | +rules to determine whether a type is trivially relocatable or not; in |
| 22 | +general, subobjects are considered there (cf. <sref ref="[class.prop]"/>, |
| 23 | +"eligible for trivial relocation", which discusses base classes and non-static |
| 24 | +data members). |
| 25 | +<p/> |
| 26 | +However these rules do not take into account objects with dynamic |
| 27 | +lifetime whose storage is being provided by (sub)objects in the range. |
| 28 | +<p/> |
| 29 | +For instance, given a `wrapper` type like: |
| 30 | +</p> |
| 31 | +<blockquote><pre> |
| 32 | +// wraps a T |
| 33 | +template<typename T> |
| 34 | +struct wrapper { |
| 35 | + alignas(T) std::byte data[sizeof(T)]; |
| 36 | +}; |
| 37 | +</pre></blockquote> |
| 38 | +<p> |
| 39 | +then one can build a non-trivially relocatable object into `wrapper` |
| 40 | +objects: |
| 41 | +</p> |
| 42 | +<blockquote><pre> |
| 43 | +struct NTR { ~NTR() {} }; |
| 44 | +static_assert(not std::is_trivially_relocatable_v<NTR>); |
| 45 | + |
| 46 | +using WS = wrapper<NTR>; |
| 47 | +static_assert(std::is_trivially_relocatable_v<WS>); // OK |
| 48 | +</pre></blockquote> |
| 49 | +<p> |
| 50 | +And now one can do this: |
| 51 | +</p> |
| 52 | +<blockquote><pre> |
| 53 | +WS* ws = /* … */; // create a wrapper |
| 54 | +new (&ws->data) NTR(); // create a NTR object into it |
| 55 | + |
| 56 | +std::trivially_relocate(ws, ws+1, dest); // should be UB |
| 57 | +</pre></blockquote> |
| 58 | +<p> |
| 59 | +Attempting to trivially relocate `*ws` should result in undefined |
| 60 | +behavior because `NTR` isn't trivially relocatable. I don't believe that |
| 61 | +this fact is correctly captured by the preconditions of |
| 62 | +`std::trivially_relocate`. |
| 63 | +<p/> |
| 64 | +A similar issue is present for polymorphic types. In <paper num="P2786"/>'s |
| 65 | +design polymorphic types can be trivially relocatable (assuming all the other |
| 66 | +conditions hold). Given a trivially relocatable polymorphic type `P`, |
| 67 | +then this code: |
| 68 | +</p> |
| 69 | +<blockquote><pre> |
| 70 | +struct P { virtual void f(); }; |
| 71 | +static_assert(std::is_trivially_relocatable_v<P>); |
| 72 | + |
| 73 | +using WP = wrapper<P>; |
| 74 | +WP* wp = /* … */; // create a wrapper |
| 75 | +new (&wp->data) P(); // create a P object into it |
| 76 | + |
| 77 | +std::trivially_relocate(wp, wp+1, dest); // implementation defined |
| 78 | +</pre></blockquote> |
| 79 | +<p> |
| 80 | +is well-defined or UB, depending on the implementation. This is because |
| 81 | +on some implementations trivially relocating a polymorphic type requires |
| 82 | +patching its virtual table pointer; cf. the discussion in chapter 15.1 |
| 83 | +of <paper num="P2786R13"/>. However the "type erasure" done by |
| 84 | +<tt>wrapper<P></tt> in the example (ultimately, it is just an array |
| 85 | +of bytes) does not allow implementations to do such patching, and the code |
| 86 | +is going to fail at runtime. Therefore this case also needs to be discussed by |
| 87 | +`std::trivially_relocate`'s specification. |
| 88 | +</p> |
| 89 | +</discussion> |
| 90 | + |
| 91 | +<resolution> |
| 92 | +<p> |
| 93 | +This wording is relative to <paper num="N5008"/>. |
| 94 | +</p> |
| 95 | + |
| 96 | +<ol> |
| 97 | +<li><p>Modify <sref ref="[obj.lifetime]"/> as indicated:</p> |
| 98 | + |
| 99 | +<blockquote class="note"> |
| 100 | +<p> |
| 101 | +[<i>Drafting note</i>: For the general part of the issue (all objects in the range must be of |
| 102 | +trivially relocatable type), we append another point at the end of the existing |
| 103 | +<i>Preconditions:</i> element of `trivially_relocate`.<br/> |
| 104 | +For the specifics of polymorphic types, we amend at the end of the description the existing |
| 105 | +<i>Remarks</i>: element] |
| 106 | +</p> |
| 107 | +</blockquote> |
| 108 | + |
| 109 | +<blockquote> |
| 110 | +<pre> |
| 111 | +template<class T> |
| 112 | + T* trivially_relocate(T* first, T* last, T* result); |
| 113 | +</pre> |
| 114 | +<blockquote> |
| 115 | +<p> |
| 116 | +-9- <i>Mandates</i>: […] |
| 117 | +<p/> |
| 118 | +-10- <i>Preconditions</i>: |
| 119 | +</p> |
| 120 | +<ol style="list-style-type: none"> |
| 121 | +<li><p>(10.1) — `[first, last)` is a valid range.</p></li> |
| 122 | +<li><p>(10.2) — `[result, result + (last - first))` denotes a region of storage that is a subset of the region |
| 123 | +reachable through `result` (<sref ref="[basic.compound]"/>) and suitably aligned for the type `T`.</p></li> |
| 124 | +<li><p>(10.3) — No element in the range `[first, last)` is a potentially-overlapping subobject.</p></li> |
| 125 | +<li><p><ins>(10.?) — All objects whose storage is being provided for (<sref ref="[intro.object]"/>) by |
| 126 | +objects in the `[first, last)` range are of trivially relocatable type.</ins></p></li> |
| 127 | +</ol> |
| 128 | +<p> |
| 129 | +-11- <i>Postconditions</i>: […] |
| 130 | +<p/> |
| 131 | +-12- <i>Returns</i>: `result + (last - first)`. |
| 132 | +<p/> |
| 133 | +-13- <i>Throws</i>: Nothing. |
| 134 | +<p/> |
| 135 | +-14- <i>Complexity</i>: Linear in the length of the source range. |
| 136 | +<p/> |
| 137 | +-15- <i>Remarks</i>: The destination region of storage is considered reused (<sref ref="[basic.life]"/>). |
| 138 | +No constructors or destructors are invoked. <ins>If any polymorphic object (<sref ref="[class.virtual]"/>) |
| 139 | +exists in storage provided for (<sref ref="[intro.object]"/>) by objects in the `[first, last)` range, |
| 140 | +it is implementation-defined whether the behavior is undefined.</ins> |
| 141 | +<p/> |
| 142 | +[<i>Note 2</i>: Overlapping ranges are supported. — <i>end note</i>] |
| 143 | +</p> |
| 144 | +</blockquote> |
| 145 | +</blockquote> |
| 146 | +</li> |
| 147 | + |
| 148 | +</ol> |
| 149 | +</resolution> |
| 150 | + |
| 151 | +</issue> |
0 commit comments