What library types are 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 copyable (which implies trivially relocatable). ✓ means it’s always trivially relocatable.

means it’s always trivially relocatable. C means it’s a template that is conditionally trivially relocatable.

means it’s a template that is conditionally trivially relocatable. No means it’s never trivially relocatable.

means it’s never trivially relocatable. D means it’s never trivially relocatable in debug mode, but always trivially relocatable in release.

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 it’s never trivially relocatable in debug mode, but conditionally trivially relocatable in release. ? means I haven’t hazarded a guess yet.

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 ? No {multi,}set<T,C,A> No No ? No unordered_{multi,}map<K,V,H,C,A> CD No ? No unordered_{multi,}set<T,H,C,A> CD No ? 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 , and map 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 a function .

smatch contains a vector .

exception and error_category have non-final virtual destructors.

regex does not contain a string ; it contains a shared_ptr to a DFA.

basic_regex contains a member of type Traits , so basic_regex is trivially relocatable iff Traits is trivially relocatable.

regex_traits<char> contains a member of type locale , so it’s not trivially copyable.

exception_ptr and locale are just gussied-up (copy-only) shared_ptr s.

type_index is just a gussied-up raw pointer to a type_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 and unordered_map::iterator hold (possibly fancy) pointer s, so they’re trivially relocatable iff the allocator’s pointer type is trivially relocatable.

vector<bool>::iterator is not trivially copyable. (Making it so would be an ABI break.)

valarray is basically a vector as far as its memory management is concerned. It stores two pointers.

Notes on libstdc++

function will store T inside its SBO buffer only if __is_location_invariant<T> . This special trait is true only for trivially copyable types and for a specific packaged_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 , but unordered_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 a vector as far as its memory management is concerned. It stores a pointer and a length.

Notes on MSVC