1. What’s going on here?
Some of us submitted [P3236] for St Louis 2024, indicating our disapproval of [P2786R4] and asking for our preferred direction [P1144R10] to be discussed in committee. This resulted in EWG’s "clawing back" P2786 for one meeting, but did not result in any committee discussion of P1144, due to its primary author.
[P2786R13] was then merged into the Working Draft at Hagenberg 2024. Almost all of P3236’s objections remain unaddressed.
-
"Types with user-provided
are reported as trivially relocatable by default."operator = Fixed. -
"The programmer is allowed to create a type that is trivially copyable but not trivially relocatable."
Not fixed. -
"It’s ill-formed to create a trivially relocatable type with an
member, or even a Boostoffset_ptr
member."static_vector Not fixed. -
"No backward-compatible syntax. P2786 proposes a new keyword, and goes out of its way to make the keyword unusable in C++23-and-earlier."
Not fixed. -
"P1144, by providing stronger guarantees of value semantics, permits more optimizations."
Not fixed. -
"P1144 provides a well-designed and stable library API."
Fixed, in that the P1144 library API has been approved for C++26 via [P3516]. -
"P2786’s warrant marking is ‘viral downward.’ As container authors, we think P2786’s normalization of a large number of explicit markings will cause programmer fatigue and lead to bugs."
Not fixed. -
"Deliberately incomplete. P2786’s authors have drafted followup papers including [P2959] and [P2967], all with incomplete wording and ‘heroic’ changes, for example
." Arguably fixed, in P2786 was merged in its present state, and P2959 and P2967 seem to have been abandoned. We now know with certainty what we have gotten, and we can say with certainty that it does not suit us.std :: container_replace_with_assignment
In addition, the Working Draft version of P2786R13 contains several problems that weren’t present in P2786R4 at the time we wrote [P3236].
-
The ARM64e platform uses "vptr signing." Its vptrs are never safe to memcpy. Yet the Working Draft permits
to returnis_trivially_relocatable true
for types with vptrs, types recursively containing members with vptrs, etc. This means that it is never safe to use e.g.
on ARM64e even when we know that the objects being reallocated are allrealloc
.is_trivially_relocatable New problem, not fixed. -
P2786’s approved syntax is shockingly ugly:
. P1144’s attribute syntax is unobtrusive by comparison:class C trivially_relocatable_if_eligible replaceable_if_eligible { }
.class [[ trivially_relocatable ]] C { } New problem, not fixed.
Therefore we submit this revision of P1144, which addresses all of these problems directly, as a diff against the current Working Draft. We gladly list ourselves as coauthors of this proposal, in the same way that five Bloomberg employees listed themselves as coauthors of [P2786R13]. Unlike them, we come from a variety of backgrounds and maintain a variety of libraries.
We beg WG21 to resolve the community’s concerns before it is too late.
2. Scope and design
In this rebased proposal, we propose:
-
The set of trivially relocatable types should be exactly those types which can be relocated as-if by memcpy. We change the definition of "trivially relocatable class" ([basic.types.general]) to include all trivially copyable types, and to exclude polymorphic types which require additional fixup beyond a simple memcpy. We need trivial relocatability to imply memcpy-ability and realloc-ability, or it is useless to us.
-
Remove the possibility that
([obj.lifetime]) might do anything other than a simple memcpy of the underlying bytes. Otherwise we do not touch thestd :: trivially_relocate
andstd :: trivially_relocate
functions; we do not mind them. What is important is that they must be trivial; they must not be allowed to perform non-trivial "fixup" of the relocated objects (e.g. to fix up the vptrs of polymorphic objects).std :: relocate -
Remove the terms of art default-movable and eligible for trivial relocation ([class.prop]).
-
Remove the terms replaceable type ([basic.types.general]), eligible for replacement, and replaceable class ([class.prop]) and the
type-trait ([meta.type.synop]) Nothing in the current working draft (nor [P3516]) depends on these notions. We believe they are likely to cause confusion to end-users, particularly in their similarity to the existing term of art "transparently replaceable" ([basic.life]). We believe they are not useful to library authors.std :: is_replaceable -
Remove the
andtrivially_relocatable_if_eligible
keywords ([lex.name]). They are ugly. They are difficult to use correctly because they are "viral"; see [P3236]. They are unusable outside of C++26 mode. We propose to replace both contextual keywords with a single ignorable attribute, which compiler vendors can choose to support even in older C++ modes if they want.replaceable_if_eligible -
Remove the Annex C entry ([diff.cpp23.dcl.dcl]) and discussion of new grammatical ambiguities ([class.pre]) caused by the P2786 syntax. These grammatical issues no longer exist.
-
Do not touch the
andstd :: is_trivially_relocatable
type-traits.std :: is_nothrow_relocatable -
Add the
attribute as used by std_error, parlaylib, and SG14. Note the lack of "if eligible" qualification: this attribute has "sharp knife" semantics suitable for our purposes and matching our current use-cases. (See [P3236].) Unlike the contextual keywords, this attribute can be supported in older C++ modes too.[[ trivially_relocatable ]] -
We do not need to propose P1144’s library functionality —
,std :: uninitialized_relocate
, and so on —because that functionality has been submitted in a separate paper not authored by Arthur O’Dwyer ([P3516]) which LEWG has approved for C++26. We are happy with this outcome.std :: relocate_at
3. Proposed wording
The wording in this section is relative to the current working draft.
3.1. [cpp.predefined]
Modify the feature-test macros in the table in [cpp.predefined]:
__cpp_impl_three_way_comparison 201907L __cpp_impl_trivially_relocatable YYYYMML __cpp_implicit_move 202207L [...]
__cpp_threadsafe_static_init 200806L __cpp_trivial_relocatability 202502L __cpp_trivial_union 202502L
3.2. [version.syn]
Bump the feature-test macro in [version.syn]/2:
#define __cpp_lib_transparent_operators 201510L // freestanding, also in <memory>, <functional> #define __cpp_lib_trivially_relocatable 202502L YYYYMML // freestanding, also in <memory>, <type_traits> #define __cpp_lib_tuple_element_t 201402L // freestanding, also in <tuple>
3.3. [basic.types.general]
Add a new section in [basic.types.general]:
9․ Arithmetic types ([basic.fundamental]), enumeration types, pointer types, pointer-to-member types ([basic.compound]),, and cv-qualified versions of these types are collectively called scalar types. Scalar types, trivially copyable class types ([class.prop]), arrays of such types, and cv-qualified versions of these types are collectively called trivially copyable types.
std :: nullptr_t ScalarTrivially copyable types, trivially relocatable class types ([class.prop]), arrays of such types, and cv-qualified versions of these types are collectively called trivially relocatable types.Cv-unqualified scalar types, replaceable class types ([class.prop]), and arrays of such types are collectively called replaceable types.Scalar types, standard-layout class types ([class.prop]), arrays of such types, and cv-qualified versions of these types are collectively called standard-layout types. Scalar types, implicit-lifetime class types ([class.prop]), array types, and cv-qualified versions of these types are collectively called implicit-lifetime types.[Note: For a trivially relocatable type, the object relocation operations performed by, for example,
or
std :: swap_ranges , are always tantamount to simple copies of the underlying bytes. —end note]
std :: vector :: reserve 10․ A type is a literal type if it is: [...]
3.4. [class.pre]
Modify [class.pre] as follows:
class-property-specifier:
final
trivially_relocatable_if_eligible
replaceable_if_eligible [...]
5․ Each class-property-specifier shall appear at most once within a single class-property-specifier-seq. Whenever a class-key is followed by a class-head-name, the identifier
,
final and a colon or left brace, the identifier is interpreted as a class-property-specifier., or
trivially_relocatable_if_eligible ,
replaceable_if_eligible [Example:
—end example]struct A ; struct A final {}; // OK, definition of struct A, // not value-initialization of variable final struct X { struct C { constexpr operator int () { return 5 ; } }; struct B trivially_relocatable_if_eligible : C {}; // OK, definition of nested class B, // not declaration of a bit-field member // trivially_relocatable_if_eligible };
3.5. [class.prop]
Modify [class.prop] as follows:
1․ A trivially copyable class is a class:
that has at least one eligible copy constructor, move constructor, copy assignment operator, or move assignment operator ([special], [class.copy.ctor], [class.copy.assign]),
where each eligible copy constructor, move constructor, copy assignment operator, and move assignment operator is trivial, and
that has a trivial, non-deleted destructor ([class.dtor]).
x․ A trivially relocatable class is a class:
or a class that is declared with a
- where no eligible copy constructor, move constructor, copy assignment operator, move assignment operator, or destructor is user-provided,
- which has no virtual member functions or virtual base classes,
- all of whose non-static data members are either of reference type or of trivially relocatable type ([basic.types.general]), and
- all of whose base classes are of trivially relocatable type;
attribute with value
trivially_relocatable true
([dcl.attr.trivreloc]) if that attribute is supported by the implementation ([cpp.cond]).
2․ A classis default-movable if
C
overload resolution for direct-initializing an object of typefrom an xvalue of type
C selects a constructor that is a direct member of
C and is neither user-provided nor deleted,
C overload resolution for assigning to an lvalue of typefrom an xvalue of type
C selects an assignment operator function that is a direct member of
C and is neither user-provided nor deleted, and
C has a destructor that is neither user-provided nor deleted.
C
3․ A class is eligible for trivial relocation unless it
has any virtual base classes,has a base class that is not a trivially relocatable class,has a non-static data member of an object type that is not of a trivially relocatable type, orhas a deleted destructor,except that it is implementation-defined whether an otherwise-eligible union having one or more subobjects of polymorphic class type is eligible for trivial relocation.
4․ A classis a trivially relocatable class if it is eligible for trivial relocation and
C
has theclass-property-specifier,
trivially_relocatable_if_eligible is a union with no user-declared special member functions, oris default-movable.
5․ [Note: A class with const-qualified or reference non-static data members can be trivially relocatable. —end note]
6․ A classis eligible for replacement unless
C
it has a base class that is not a replaceable class,it has a non-static data member that is not of a replaceable type,overload resolution fails or selects a deleted constructor when direct-initializing an object of typefrom an xvalue of type
C ([dcl.init.general]),
C overload resolution fails or selects a deleted assignment operator function when assigning to an lvalue of typefrom an xvalue of type
C ([expr.assign], [over.assign])), or
C it has a deleted destructor.
7․ A classis a replaceable class if it is eligible for replacement and
C
has theclass-property-specifier,
replaceable_if_eligible is a union with no user-declared special member functions, oris default-movable.
8․ [Note: Accessibility of the special member functions is not considered when establishing trivial relocatability or replaceability. —end note]
9․ [Note: Not all trivially copyable classes are trivially relocatable or replaceable. —end note]10․ A class
is a standard-layout class if it: [...]
S
3.6. [cpp.cond]
Add a new entry to the table of supported attributes in [cpp.cond]:
noreturn 200809L trivially_relocatable YYYYMML unlikely 201803L
3.7. [dcl.attr.trivreloc]
Add a new section after [dcl.attr.nouniqueaddr]:
1․ The attribute-tokenspecifies that a class type’s relocation operation has no visible side-effects other than a copy of the underlying bytes, as if by the library function
trivially_relocatable . It may be applied to the definition of a class. It shall appear at most once in each attribute-list. An attribute-argument-clause may be present and, if present, shall have the form
std :: memcpy The constant-expression shall be an integral constant expression of type( constant - expression ) . If no attribute-argument-clause is present, it has the same effect as an attribute-argument-clause of
bool .
( true) 2․ If any definition of a class type has a
attribute with value V, then each definition of the same class type shall have a
trivially_relocatable attribute with value V. No diagnostic is required if definitions in different translation units have mismatched
trivially_relocatable attributes.
trivially_relocatable 3․ If a class type is declared with the
attribute, and the program relies on observable side-effects of its relocation other than a copy of the underlying bytes, the behavior is undefined.
trivially_relocatable 4․ Recommended practice: The value of a has-attribute-expression for the
attribute should be
trivially_relocatable for a given implementation unless this attribute can cause a class type to be trivially relocatable ([class.prop]).
0
3.8. [expr.prim.lambda.closure]
Modify [expr.prim.lambda.closure] as follows:
3. The closure type is not an aggregate type ([dcl.init.aggr]); it is a structural type ([temp.param]) if and only if the lambda has no lambda-capture. An implementation may define the closure type differently from what is described below provided this does not alter the observable behavior of the program other than by changing:
the size and/or alignment of the closure type,
whether the closure type is trivially copyable ([class.prop]),
whether the closure type is trivially relocatable ([class.prop]),
whether the closure type is replaceable ([class.prop]),orwhether the closure type is a standard-layout class ([class.prop]).
An implementation shall not add members of rvalue reference type to the closure type.
3.9. [lex.name]
Modify [lex.name] as follows:
2. The identifiers in Table 4 have a special meaning when appearing in a certain context. When referred to in the grammar, these identifiers are used explicitly rather than using the identifier grammar production. Unless otherwise specified, any ambiguity as to whether a given identifier has a special meaning is resolved to interpret the token as a regular identifier.
Table 4 — Identifiers with special meaning
final import post replaceable_if_eligible override module pre trivially_relocatable_if_eligible
3.10. [library.class.props]
Modify [library.class.props] as follows:
1. Unless explicitly stated otherwise, it is unspecified whether any class described in [support] through [exec] and [depr] is a trivially copyable class, a trivially relocatable class, a standard-layout class, or an implicit-lifetime class ([class.prop]).
2. Unless explicitly stated otherwise, it is unspecified whether any class for which trivial relocation (i.e., the effects of([obj.lifetime])) would be semantically equivalent to move-construction of the destination object followed by destruction of the source object is a trivially relocatable class ([class.prop]).
trivially_relocate
3. Unless explicitly stated otherwise, it is unspecified whether a classis a replaceable class ([class.prop]) if assigning an xvalue
C of type
a to an object
C of type
b is semantically equivalent to destroying
C and then constructing from
b in
a ’s place.
b
3.11. [meta.type.synop]
Modify [meta.type.synop] as follows:
[...] // [meta.unary.prop] , type properties template < class T > struct is_const ; template < class T > struct is_volatile ; template < class T > struct is_trivially_copyable ; template < class T > struct is_trivially_relocatable ; template < class T > struct is_replaceable ; template < class T > struct is_standard_layout ; [...] template < class T > constexpr bool is_implicit_lifetime_v = is_implicit_lifetime < T >:: value ; template < class T > constexpr bool is_replaceable_v = is_replaceable < T >:: value ; template < class T > constexpr bool has_virtual_destructor_v = has_virtual_destructor < T >:: value ;
3.12. [meta.unary.prop]
Modify Table 54 in [meta.unary.prop] as follows:
Template Condition Preconditions
template < class T > struct is_trivially_copyable ; is a trivially copyable type ([basic.types.general])
T shall be a complete type or cv
remove_all_extents_t < T > .
void
template < class T > struct is_trivially_relocatable ; is a trivially relocatable type ([basic.types.general])
T shall be a complete type or cv
remove_all_extents_t < T > .
void
template < class T > struct is_replaceable ; is a replaceable type ([basic.types.general])
T shall be a complete type or cv
remove_all_extents_t < T > .
void
template < class T > struct is_standard_layout ; is a standard-layout type ([basic.types.general])
T shall be a complete type or cv
remove_all_extents_t < T > .
void
3.13. [obj.lifetime]
Modify [obj.lifetime] as follows:
template < class T > T * trivially_relocate ( T * first , T * last , T * result ); 9. Mandates:
is
is_trivially_relocatable_v < T > && ! is_const_v < T > true
.is not an array of unknown bound.
T 10. Preconditions:
[
,
first ) is a valid range.
last [
,
result ) denotes a region of storage that is a subset of the region reachable through
result + ( last - first ) ([basic.compound]) and suitably aligned for the type
result .
T No element in the range [
,
first ) is a potentially-overlapping subobject.
last 11. Postconditions: No effect if
is
result == first true
. Otherwise, the range denoted by [,
result ) contains objects (including subobjects) whose lifetime has begun and whose object representations are the original object representations of the corresponding objects in the source range [
result + ( last - first ) ,
first )
last except for any parts of the object representations used by the implementation to represent type information ([intro.object]). If any of the objects has union type, its active member is the same as that of the corresponding object in the source range. If any of the aforementioned objects has a non-static data member of reference type, that reference refers to the same entity as does the corresponding reference in the source range. The lifetimes of the original objects in the source range have ended.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 ([basic.life]). No constructors or destructors are invoked.
[Note: Overlapping ranges are supported. —end note]
3.14. [diff.cpp23.dcl.dcl]
Modify [diff.cpp23.dcl.dcl] as follows:
1. Affected subclause: [dcl.decl.general]
Change: Introduction ofand
trivially_relocatable_if_eligible as identifiers with special meaning ([lex.name]).
replaceable_if_eligible
Rationale: Support declaration of trivially relocatable and replaceable types ([class.prop]).
Effect on original feature: Valid C++ 2023 code can become ill-formed.
[Example:struct C {}; struct C replaceable_if_eligible {}; // was well-formed (new variable replaceable_if_eligible) // now ill-formed (redefines C) —end example]