| Document #: | P3915R0 |
| Date: | 2025-11-07 10:48 HST |
| Project: | Programming Language C++ |
| Audience: |
EWG |
| Reply-to: |
Pablo Halpern <phalpern@halpernwightsoftware.com> |
memcpy
memcpy perform trivial
relocationtrivially_relocate be a bitwise
operationstd::relocate with
std::uninitialized_relocate_*
std::relocate and
std::uninitialize_relocate_*std::relocate and
std::uninitialize_relocate_*vector<T>::eraseuninitialized_relocate Result in
Cleaner Code?const
Qualified Types for Relocation Functions
restart_lifetimeMoving 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.
memcpystd::relocate with
std::uninitialized_relocate_*const
Qualified Types for Relocation Functionsrestart_lifetimeNB Comment Addressed: US 10-023: Remove *_if_eligible
keywords
This NB comment is a summary of the arguments made in P1144R13.
All of the arguments have been discussed multiple times, most recently in Sofia in June 2025.
There was consensus against using committee time to explore further the ideas in P1144:
SF
|
F
|
N
|
A
|
SA
|
|
|---|---|---|---|---|---|
| EWG | 8 | 6 | 4 | 14 | 20 |
| LEWG | 1 | 2 | 2 | 7 | 11 |
US 10-023 adds no new information to substantiate the claims that failed to persuade in Sofia and prior.
memcpymemcpy-ablememcpyIf we could memcpy,
memmove, and
realloc objects of types for which
is_trivially_relocatable_v<T>
is true, then we could:
memcpy to become valid andstart_lifetime_as or
trivially_relocate; andtrivially_relocate (see 6.8.4
[basic.life]1)memcpy loses critical type
information with no means of recovery.memcpytrivially_relocate
be a bitwise operationThe CD is not broken; it just has some limitations.
std::optional and
std::variant
holding TR types.memmove-like efficiencymemcpy perform trivial relocationmemcpy ended the lifetime of objects
nested within the source buffer.trivially_relocate be a bitwise
operationconstexpr
evaluation.is_bitwise_trivially_relocatable,
per [P3780R0]optional or
variant other than for trivially
copyable typesoptional and
variant holding TR elements.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>> { };std::relocate with
std::uninitialized_relocate_*std::relocatestd::uninitialized_relocatestd::relocate and
std::uninitialize_relocate_*constexpr
function templatesis_trivially_relocatable,
trivially_relocate,
is_move_constructible, etc.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.
vector<T>::eraseBelow, 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
|
|---|---|
|
|
uninitialized_relocate Result in
Cleaner Code?vector-like or
deque-like container:
uninitialized_relocate can avoid a
separate assignment fall-back loop buttry/catch
to fix up the container upon failure.std::list)uninitialized_relocate to be a
throwing operation provides marginal benefit, at best, and might
encourage inefficient code.new and
begin_lifetime_as.std::destroy
is not a good modelstd::relocate from
the CD, but do not replace it with anything. Allow library vendors to
organically discover the best interface for implementing
std::vector,
std::deque,
etc.relocate with
non-throwing uninitialized_relocate_*.uninitialized_relocate_*
to uninitialized_move_destroy_*
or uninitialized_destructive_move_*.
std::relocate as
isuninitialized_relocate_*,
but keep relocate
uninitialized_relocate_*
could be added to C++26 or C++29std::relocate with
uninitialized_relocate as defined in
[P3516R2], this option is a reasonable
way to make the TR feature usable.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 CDThe design exposition for P2786 states:
const
and/or reference data members.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 CD11.2 [class.prop]/2:
2 A class
Cis default-movable if
- (2.1) overload resolution for direct-initializing an object of type
Cfrom an xvalue of typeCselects a constructor that is a direct member ofCand is neither user-provided nor deleted. [Emphasis mine]
- (2.2) overload resolution for assigning to an lvalue of type
Cfrom an xvalue of typeCselects an assignment operator function that is a direct member ofCand is neither user-provided nor deleted. [Emphasis mine]
- (2.3)
Chas 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.
The authors of P2786 are in favor the following resolution:
Change 11.2 [class.prop]/2 as follows.
2 A class
Cis default-movable if
- (2.1) overload resolution for direct-initializing an object of type
Cfrom an xvalue of typeCselects a constructor that is a direct member ofCand is neither user-provided nor explicitly deleted.
- (2.2) overload resolution for assigning to an lvalue of type
Cfrom an xvalue of typeCselects an assignment operator function that is a direct member ofCand is neither user-provided nor explicitly deleted.
- (2.3)
Chas a destructor that is neither user-provided nor explicitly deleted.
*_if_eligible real
keywordsTo make a class trivially relocatable based on a compile-time property (e.g., whether a nested object type is TR) requires cumbersome metaprogramming.
Adding a language-supported predicate to the TR keywords in the future would be difficult because of a parsing ambiguity:
template <class T>
struct X trivially_relocatable_if_eligible(some_trait<T>) ...
Is the above a declaration of
Xor the definition and initialization oftrivially_relocatable_if_eligibleof typestruct X, initialized withsome_trait<T>?
In almost all circumstances, the implicit eligibility requirements provide conditional TR without needing a new syntax or work-around.
The most common exception is for nested objects of
template-parameter type stored in untyped byte buffers (e.g.,
variant implemented using a byte
buffers rather than a
union)
template <class... T> struct __nested_obj_buffer;
Where a
__nested_obj_buffercontains a byte buffer of sufficient size and alignment for one object of any type inT.... This class will be trivially relocatable iff all types inT...can be bitwise relocated (i.e., they are all trivially copyable).
In almost all cases, a
union would
be a better choice, and would automatically produce the correct
eligibility.
Previous versions of P2786 supported conditional TR and had wording (with CWG input) that resolved the parsing ambiguity without using real keywords.
Either or both changes (making the keywords non-contextual or adding a boolean condition to the keywords) could be applied in C++29 without any more chance of breakage than today.
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.
const
Qualified Types for Relocation Functionsrelocatetrivially_relocateBoth comments would allow
const-qualified
T for the
T* template
arguments, claiming consistency with constructors and destructors, which
do work on
const-qualified
types.
const-qualified
argument would come into play.const-qualification
is a safety feature that can be worked around with
const_cast
in the rare case where it’s problematic.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.
restart_lifetimeNB Comments Addressed
start_lifetime_at [sic]
functionstart_lifetime_at [sic] function
P3858Although 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.