What library types are trivially_relocatable
in practice?
trivially_relocatable
in practice?In my libc++ fork implementing library support for
P1144 “Object relocation in terms of move plus destroy,”
I have a unit test
that tests std::is_trivially_relocatable_v<X>
for every library-provided type X
that I can think of. Here is that same list of library-provided types, in convenient tabular format,
for each of the Big Three implementations (libc++, libstdc++, and MSVC). The results for libc++
are accurate. The results for libstdc++ and MSVC are my best guesses based on whatever homework
I’ve done up to this point. I will endeavor to fix bugs in this table as they are reported.
The rightmost column, “Lowest,” shows the lowest common denominator: what we could mandate in the Standard without causing any ABI breakage for vendors.
Key:
✓✓
means it’s always trivially copyable (which implies trivially relocatable).✓
means it’s always trivially relocatable.C
means it’s a template that is conditionally trivially relocatable.No
means it’s never trivially relocatable.D
means it’s never trivially relocatable in debug mode, but always trivially relocatable in release.CD
means it’s never trivially relocatable in debug mode, but conditionally trivially relocatable in release.?
means I have only a tentative guess.
Library type | libc++ | libstdc++ | MSVC | Lowest |
---|---|---|---|---|
reference_wrapper<T> |
✓✓ | ✓✓ | ✓✓ | ✓✓ |
initializer_list<T> |
✓✓ | ✓✓ | ✓✓ | ✓✓ |
allocator<T> |
✓✓ | ✓ | ✓✓ | ✓ |
pmr::polymorphic_allocator<T> |
✓✓ | ✓✓ | ✓✓* | ✓✓ |
default_delete<T> |
✓✓ | ✓✓ | ✓✓ | ✓✓ |
hash<T> |
✓✓ | ✓✓ | ✓✓ | ✓✓ |
less<T> |
✓✓ | ✓✓ | ✓✓ | ✓✓ |
less<void> |
✓✓ | ✓✓ | ✓✓ | ✓✓ |
unique_ptr<T> |
✓ | ✓ | ✓ | ✓ |
unique_ptr<T, D> |
C | C | C | C |
shared_ptr<T> |
✓ | ✓ | ✓ | ✓ |
weak_ptr<T> |
✓ | ✓ | ✓ | ✓ |
pair<T, U> |
C | C | C | C |
tuple<Ts...> |
C | C | C | C |
variant<Ts...> |
C | C | C | C |
optional<T> |
C | C | C | C |
any |
No | No | No | No |
locale |
✓ | ✓ | ✓ | ✓ |
exception_ptr |
✓ | ✓ | ✓ | ✓ |
exception |
No | No | No | No |
error_category |
No | No | No | No |
error_code |
✓✓ | ✓✓ | ✓✓ | ✓✓ |
error_condition |
✓✓ | ✓✓ | ✓✓ | ✓✓ |
errc |
✓✓ | ✓✓ | ✓✓ | ✓✓ |
type_index |
✓✓ | ✓✓ | ✓✓ | ✓✓ |
array<T, N> |
C | C | C | C |
deque<T> |
✓ | ✓ | ✓ | ✓ |
deque<T,A> |
C | C | C | C |
forward_list<T> |
✓ | ✓ | ✓ | ✓ |
forward_list<T,A> |
C | C | C | C |
list<T> |
No | No | ✓ | No |
list<T,A> |
No | No | C | No |
{multi,}map<K,V,C,A> |
No | No | C | No |
{multi,}set<T,C,A> |
No | No | C | No |
unordered_{multi,}map<K,V,H,C,A> |
No | No | C? | No |
unordered_{multi,}set<T,H,C,A> |
No | No | C? | No |
vector<T> |
D | ✓ | ✓ | No |
vector<T,A> |
CD | C | C | No |
stack<T,C> |
C | C | C | C |
queue<T,C> |
C | C | C | C |
priority_queue<T,C> |
C | C | C | C |
vector<T,A>::iterator |
C | C | C | C |
unordered_map<K,V,H,C,A>::iterator |
CD | ✓ | C | C |
insert_iterator<Ctr> |
✓✓ | ✓✓ | ✓✓ | ✓✓ |
istream_iterator<T> |
C | C | C | C |
ostream_iterator<T> |
✓✓ | ✓ | ✓✓ | ✓ |
regex |
✓ | ✓ | ✓ | ✓ |
basic_regex<C,T> |
C | C | C | C |
smatch |
D | ✓ | ✓ | No |
string |
D | No | ✓ | No |
basic_string<C,T,A> |
CD | No | C | No |
bitset<N> |
✓✓ | ✓✓ | ✓✓ | ✓✓ |
chrono::system_clock::duration |
✓✓ | ✓✓ | ✓✓ | ✓✓ |
chrono::system_clock::time_point |
✓✓ | ✓✓ | ✓✓ | ✓✓ |
unique_lock<T> |
✓ | ✓ | ✓ | ✓ |
shared_lock<T> |
✓ | ✓ | ✓ | ✓ |
thread |
✓ | ✓ | ✓ | ✓ |
thread::id |
✓✓ | ✓✓ | ✓✓ | ✓✓ |
promise<T> |
✓ | ✓ | ✓ | ✓ |
future<T> |
✓ | ✓ | ✓ | ✓ |
shared_future<T> |
✓ | ✓ | ✓ | ✓ |
function<T> |
No | ✓ | No | No |
packaged_task<T> |
No | ✓ | No | No |
complex<float> |
✓✓ | ✓✓ | ✓✓ | ✓✓ |
mt19937 |
✓✓ | ✓✓ | ✓✓ | ✓✓ |
normal_distribution<float> |
✓✓ | ✓✓ | ✓✓ | ✓✓ |
valarray<T> |
✓ | ✓ | ✓ | ✓ |
The following types aren’t relocatable at all (because they aren’t move-constructible):
atomic<T>
, atomic_flag
, condition_variable
, condition_variable_any
,
lock_guard<T>
, mutex
, seed_seq
, random_device
.
Notes applicable to both libc++ and libstdc++
-
list
,set
, andmap
all contain “end nodes” stored inside themselves (so, there are pointers from the heap to the object itself). -
any
can store non-trivially-relocatable types inside its SBO buffer. -
packaged_task
contains afunction
. -
smatch
contains avector
. -
exception
anderror_category
have non-final virtual destructors. -
regex
does not contain astring
; it contains ashared_ptr
to a DFA. -
basic_regex
contains a member of typeTraits
, sobasic_regex
is trivially relocatable iffTraits
is trivially relocatable. -
regex_traits<char>
contains a member of typelocale
, so it’s not trivially copyable. -
exception_ptr
andlocale
are just gussied-up (copy-only)shared_ptr
s. -
type_index
is just a gussied-up raw pointer to atype_info
.
Notes on libc++
-
“Debugging” for containers means
_LIBCPP_DEBUG_LEVEL >= 2
(which is the same thing as_LIBCPP_DEBUG_MODE
). -
function
contains a pointer to its own SBO buffer. -
function
can store non-trivially-relocatable types inside its SBO buffer. -
vector::iterator
andunordered_map::iterator
hold (possibly fancy)pointer
s, so they’re trivially relocatable iff the allocator’spointer
type is trivially relocatable. -
vector<bool>::iterator
is not trivially copyable. (Making it so would be an ABI break.) -
valarray
is basically avector
as far as its memory management is concerned. It stores two pointers.
Notes on libstdc++
-
function
will storeT
inside its SBO buffer only if__is_location_invariant<T>
. This special trait is true only for trivially copyable types and for a specificpackaged_task
helper type. -
deque
allocates a (sort of) “sentinel node” on the heap even in its default constructor, so it is not nothrow-move-constructible; nonetheless it is (conditionally) trivially relocatable. -
vector::iterator
holds a (possibly fancy)pointer
, butunordered_map::iterator
holds a raw pointer; the latter is always trivially relocatable as far as I can tell. -
allocator
is not trivially copyable. (Making it so would be an ABI break.) -
ostream_iterator
is not trivially copyable. (Making it so would be an ABI break.) -
valarray
is basically avector
as far as its memory management is concerned. It stores a pointer and a length.
Notes on MSVC
-
list
allocates a “sentinel node” on the heap even in its default constructor, so it is not nothrow-move-constructible. However, as far as I can tell, it is (conditionally) trivially relocatable. -
set
(et al.) allocates a “head node” on the heap, which acts as the one-past-the-end sentinel node and also holds pointers to the root node, the leftmost node in the tree, and the rightmost node in the tree. -
unordered_set
(et al.) seem to be trivially relocatable in practice, but I admit I don’t understand the code. Empirical evidence is provided by this test program. -
any
will storeT
inside its SBO buffer only ifis_trivially_move_constructible_v<T>
; but it permits!is_trivially_destructible_v<T>
. There is a fast path for trivially copyable types. -
function
can store non-trivially-relocatable types inside its SBO buffer. -
vector::iterator
andunordered_map::iterator
hold (possibly fancy)pointer
s, so they’re trivially relocatable iff the allocator’spointer
type is trivially relocatable. -
pmr::polymorphic_allocator
(correctly) has=delete
d assignment operators, and MSVC (incorrectly) reports such types as non-trivially copyable. I’ve recorded it as trivially copyable with an asterisk. -
locale
andexception_ptr
are just gussied-up (copy-only)shared_ptr
s. -
valarray
is basically avector
as far as its memory management is concerned. It stores a pointer and a length.
See also Howard Hinnant’s “Container Survey” (June 2015),
which tabulates which containers’ special member functions are noexcept
.