Source: http://open-std.org/JTC1/SC22/WG21/docs/papers/2019/p1144r2.html
Timestamp: 2019-04-24 00:24:19+00:00

Document:
We define a new verb, "relocate," which is equivalent to a move and a destroy (analogous to the existing verb "swap," which is equivalent to a move, two move-assignments, and a destroy). For many C++ types, the "relocate" operation is implementable as a single memcpy. We provide a standard trait to detect types which are trivially relocatable, for the benefit of library writers. Finally, we provide a portable way for a user-defined type (e.g. boost::shared_ptr) to warrant to the implementation that it is trivially relocatable.
4.6 Type traits is_relocatable etc.
5.1 Why not destructive move?
C++17 knows the verbs "move," "copy," "destroy," and "swap," where "swap" is a higher-level operation composed of several lower-level operations. To this list we propose to add the verb "relocate," which is a higher-level operation composed of exactly two lower-level operations. Given an object type T and memory addresses src and dst, the phrase "relocate a T from src to dst" means no more and no less than "move-construct dst from src, and then immediately destroy the object at src."
Just as the verb "swap" produces the adjective "swappable," the verb "relocate" produces the adjective "relocatable." Any type which is both move-constructible and destructible is relocatable. The notion can be modified by adverbs: we say that a type is nothrow relocatable if its relocation operation is noexcept, and we say that a type is trivially relocatable if its relocation operation is trivial (which, just like trivial move-construction and trivial copy-construction, means "the operation is tantamount to a memcpy").
Almost all relocatable types are trivially relocatable: std::unique_ptr<int>, std::vector<int>, std::string. Non-trivially relocatable types exist but are rare; see Appendix C: Examples of non-trivially relocatable class types.
[Bench] (presented at C++Now 2018) shows a 3x speedup on std::vector<std::unique_ptr<int>>::resize. This Reddit thread demonstrates a similar 3x speedup using the online tool Quick-Bench.
As observed in [CppChat] (@21:55): Just as with C++11 move semantics, you can write benchmarks to show whatever speedup you like. The more complicated your types' move-constructors and destructors, the more time you save by eliminating calls to them.
Given a reliable way of detecting trivial relocatability, we can de-duplicate the code generated by small-buffer-optimized (SBO) type-erasing wrappers such as std::function and std::any. For these types, a move of the wrapper object is implemented in terms of a relocation of the contained object. (See for example libc++'s std::any, where the function that performs the relocation operation is confusingly named __move.) In general, the relocate operation for a contained type C must be uniquely codegenned for each different C, leading to code bloat. But a single instantiation suffices to relocate every trivially relocatable C in the program. A smaller number of instantiations means faster compile times, a smaller text section, and "hotter" code (because a relatively higher proportion of your code now fits in icache).
Given a reliable way of detecting trivial relocatability, we can optimize the move-constructor of fixed_capacity_vector<R,N>, which can be implemented naïvely as an element-by-element move (leaving the source vector’s elements in their moved-from state), or can be implemented efficiently as an element-by-element relocate (leaving the source vector empty).
Note: boost::container::static_vector<R,N> currently implements the naïve element-by-element-move strategy.
Some concurrent data structures might reasonably assert the trivial relocatability of their elements, just as they sometimes assert the stronger property of trivial copyability today.
The improvement in user experience for real-world codebases (such as [Folly], [EASTL], BDE, Qt, etc.) is the most important benefit to be gained by this proposal.
What C++ lacks is a standard way for library writers to detect the (existing) trivial relocatability of a type T, so that they can reliably apply their (existing) optimizations. All we really need is to add detection, and then all the optimizations described above will naturally emerge without any further special effort by WG21.
There are three kinds of object types that we want to make sure are correctly detected as trivially relocatable. These three cases are important for improving the performance of the standard library, and for improving the correctness of programs using libraries such as [Folly]'s fbvector.
This could be done unilaterally by the library vendor — via a non-standard attribute ([[clang::trivially_relocatable]]), or a member typedef with a reserved name, or simply a vendor-provided specialization of std::is_trivially_relocatable<std::string>.
That is, we can in principle solve §2.1 while confining our "magic" to the headers of the implementation itself. The programmer doesn’t have to learn anything new, so far.
Here struct A follows the Rule of Zero: its move-constructor and destructor are both defaulted. If they were also trivial, then we’d be done. In fact they are non-trivial; and yet, because the type’s bases and members are all of trivially relocatable types, the type as a whole is trivially relocatable.
§2.2 asks specifically that we make the static_assert succeed without breaking the "Rule of Zero." We do not want to require the programmer to annotate struct A with a special attribute, or a special member typedef, or anything like that. We want it to Just Work. Even for lambda types. This is a much harder problem than §2.1; it requires standard support in the core language. But it still does not require any new syntax.
via some standard annotation applied to class type B (which in this example is standing in for boost::shared_ptr).
Note: We cannot possibly do it without annotation, because there exist examples of types that look just like B and are trivially relocatable (for example, libstdc++'s std::function) and there exist types that look just like B and are not trivially relocatable (for example, libc++'s std::function). The compiler cannot "crack open" the definitions of B(B&&) and ~B() to see if they combine to form a trivial operation: the definitions of B(B&&) and ~B() might not even be available in the current translation unit. So, without some kind of opt-in annotation, we cannot achieve our goal.
This use-case is the only one that requires us to design the "opt-in" syntax. In §2.1, any special syntax is hidden inside the implementation’s own headers. In §2.2, our design goal is to avoid special syntax. In §2.3, WG21 must actually design user-facing syntax.
Therefore, I believe it would be acceptable to punt on §2.3 and come back to it later. We say, "Sure, that would be nice, but there’s no syntax for it. Be glad that it works for core-language and library types. Ask again in three years." As long as we leave the design space open, I believe we wouldn’t lose much by delaying a solution to §2.3.
This paper does propose a standard syntax for §2.3 — an attribute — which in turn provides a simple and portable way for library vendors to implement §2.1.
This paper proposes five separate additions to the C++ Standard. These additions introduce "relocate" as a well-supported C++ notion on par with "swap," and furthermore, successfully communicate trivial relocatability in each of the three use-cases above.
A new standard algorithm, uninitialized_relocate(first, last, d_first), in the <memory> header.
Additional type traits, is_relocatable<T> and is_nothrow_relocatable<T>, in the <type_traits> header.
A new type trait, is_trivially_relocatable<T>, in the <type_traits> header. This is the detection mechanism.
A new core-language rule by which a class type’s "trivial relocatability" is inherited according to the Rule of Zero.
A new attribute, [[trivially_relocatable]], in the core language. This is the opt-in mechanism for program-defined types.
These five bullet points are severable to some degree. For example, if the [[trivially_relocatable]] attribute (point 5) is adopted, library vendors will certainly use it in their implementations; but if the attribute is rejected, library vendors could still indicate the trivial relocatability of certain standard library types by providing library specializations of is_trivially_relocatable (point 3).
Points 1 and 2 are completely severable from points 3, 4, and 5; but we believe these algorithms should be provided for symmetry with the other uninitialized-memory algorithms in the <memory> header (e.g. uninitialized_move) and the other trios of type-traits in the <type_traits> header (e.g. is_destructible, is_nothrow_destructible, is_trivially_destructible). I do not expect these templates to be frequently useful, but I believe they should be provided, so as not to surprise the programmer by their absence.
Points 3 and 4 together motivate point 5. In order to achieve the goal of §2.2 Program-defined types that follow the Rule of Zero, we must define a core-language mechanism by which we can "inherit" trivial relocatability. This is especially important for the template case.
We strongly believe that std::is_trivially_relocatable<T> should be just a plain old class template, exactly like std::is_trivially_destructible<T> and so on. The core language should not know or care that the class template is_trivially_relocatable exists, any more than it knows that the class template is_trivially_destructible exists.
We expect that the library vendor will implement std::is_trivially_relocatable, just like std::is_trivially_destructible, in terms of a non-standard compiler builtin whose natural spelling is __is_trivially_relocatable(T). This builtin has been implemented in Clang; see [D50119]. The compiler computes the value of __is_trivially_relocatable(T) by inspecting the definition of T (and the definitions of its base classes and members, recursively). This recursive process "bottoms out" at primitive types, or at any type with a user-provided move or destroy operation. For safety, classes with user-provided move or destroy operations (e.g. Appendix C: Examples of non-trivially relocatable class types) must be assumed not to be trivially relocatable. To achieve the goal of §2.3 Program-defined types with non-defaulted special members, we must provide a way that such a class can "opt in" and warrant to the implementation that it is in fact trivially relocatable (despite being non-trivially move-constructible and/or non-trivially destructible).
The attribute overrides the compiler’s usual computation.
An example of a "conditionally" trivially relocatable class is shown in Conditionally trivial relocation.
The attribute is severable; WG21 could adopt all the rest of this proposal and leave vendors to implement [[clang::trivially_relocatable]], [[gnu::trivially_relocatable]], etc., as non-standard extension mechanisms. In that case, we would strike §4.5 and one bullet point from §4.4; the rest of this proposal would remain exactly the same.
The wording in this section is relative to WG21 draft N4762, that is, the current draft of the C++17 standard.
[definitions] is probably the wrong place for the core-language definition of "relocation operation"
Remarks: If an exception is thrown, some objects in the range [first, last) are left in a valid but unspecified state.
Note: We are guided by [P0884R0] to make uninitialized_relocate unconditionally noexcept(false). This is consistent with uninitialized_move and destroy_at, both of which are unconditionally noexcept(false).
Remarks: If an exception is thrown, some objects in the range [first, std::next(first,n)) are left in a valid but unspecified state.
all of whose base classes are trivially relocatable.
Note: We could simplify the wording by removing the words "either is final, or has a final destructor, or". However, this would lead to the compiler’s failing to identify certain (unrealistic) class types as trivially relocatable, when in fact it has enough information to infer that they are trivially relocatable in practice. This would be an inaccurate corner case in an otherwise perfectly accurate feature. I tentatively prefer to optimize for both maximum performance and absence of corner cases, over spec simplicity.
Note: There is no special treatment for volatile subobjects. Using memmove on volatile subobjects can cause tearing of reads and writes. This paper introduces no new issues in this area; see [Subobjects]. The existing issues with volatile are addressed narrowly by [P1153R0] and broadly by [P1152R0].
Note: There is no special treatment for possibly overlapping subobjects. Using memmove on possibly overlapping subobjects can overwrite unrelated objects in the vicinity of the destination. This paper introduces no new issues in this area; see [Subobjects].
The relevant move constructor, copy constructor, and/or destructor must be public and unambiguous. We imply this via the words "A move-constructible, destructible object type". However, "move-constructible" and "destructible" are library concepts, not core language concepts, so maybe it is inappropriate to use them here.
We must find a rule that makes neither A nor B trivially relocatable, because the move-construction A(std::move(a)) invokes user-provided copy constructor MA(MA&) and the move-construction B(std::move(b)) invokes user-provided copy constructor MB(const volatile MB&).
We would like to find a rule that makes J trivially relocatable, because the J : I pattern is used to implement "conditionally trivial relocatability" for all allocator-aware containers in my libc++ reference implementation. (The move-constructor and destructor of J are moved into a base class template I which is conditionally marked with [[trivially_relocatable]]. The copy constructor, assignment operators, etc. are not moved into the base class because they are not expected to interfere with trivial relocatability.) If we adopted the syntax proposed by §5.2 Attribute [[trivially_relocatable(bool)]], we might not care about this J example.
The attribute-token trivially_relocatable specifies 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 std::memcpy. It shall appear at most once in each attribute-list and no attribute-argument-clause shall be present. It may be applied to the definition of a class. If a type is defined with the trivially_relocatable attribute in one translation unit and the same type is defined without the trivially_relocatable attribute in another translation unit, the program is ill-formed, no diagnostic required.
If a type T is declared with the trivially_relocatable attribute, and T is either not move-constructible or not destructible, the program is ill-formed.
If a class type is declared with the trivially_relocatable attribute, the implementation is permitted to replace relocation operations involving that type (such as those performed by the library functions std::swap and std::vector::resize) with simple copies of the underlying bytes.
If a class type is declared with the trivially_relocatable attribute, and the program relies on observable side-effects of relocation other than a copy of the underlying bytes, the behavior is undefined.
"If a type T is declared with the trivially_relocatable attribute, and T is either not move-constructible or not destructible, the program is ill-formed." We might want to replace this wording with a mere "Note" encouraging implementations to diagnose. See this example where a diagnostic might be unwanted.
4.6. Type traits is_relocatable etc.
template<class T> struct is_relocatable; is_move_constructible_v<T> is true and is_destructible_v<T> is true T shall be a complete type, cv void, or an array of unknown bound.
template<class T> struct is_nothrow_relocatable; is_relocatable_v<T> is true and both the indicated move-constructor and the destructor are known not to throw any exceptions. T shall be a complete type, cv void, or an array of unknown bound.
template<class T> struct is_trivially_relocatable; T is a trivially relocatable type. T shall be a complete type, cv void, or an array of unknown bound.
Note: This concept is exactly equivalent to MoveConstructible<T>.
5.1. Why not destructive move?
As discussed in EWGI at San Diego, this proposal does not give us a general user-provided "destructive move" facility.
Denis Bider’s [P0023R0] and Pablo Halpern’s [N4158] went in that direction and did not succeed. People have been chasing "destructive move" for decades; maybe it’s time to try something different.
We get the performance benefit only when the library (e.g. std::vector::resize) can detect that "relocate/destructive move" is tantamount to memcpy. If we permit a user-provided "destructive move" operation, we must also design a way for the user to warrant that their "destructive move" is tantamount to memcpy. No previous proposal has shown how to do this.
P1144’s approach is explicitly based on existing industry practice: [Folly], [EASTL], and [BSL] all use this exact idea in practice and it seems to work for them. Marc Glisse has been integrating the same idea into GNU libstdc++; see [Deque]. The term "relocation" is due to [EASTL] (has_trivial_relocate) and [Folly] (IsRelocatable). The same concept appears in pre-C++11 libraries under the name "movable": Qt (Q_MOVABLE_TYPE) and [BSL] (IsBitwiseMoveable). P1144’s sole innovation is to give a consistent set of core-language rules by which the compiler can deduce the trivial relocatability of some class types which follow the Rule of Zero.
It has been suggested by numerous reviewers that [[trivially_relocatable]] should take a (perhaps optional) boolean parameter: [[trivially_relocatable(true)]]. This would allow us to write complicated conditions directly inline, instead of using metaprogramming to inherit the right behavior from a conditional base class.
See Conditionally trivial relocation for an example of how this would be used.
There is no technical obstacle to adding an arbitrary C++ expression as the parameter to an attribute. The grammar for balancing  and () in attribute parameters has been there since C++11. There is already prior art for arbitrary expressions in attributes; see for example Clang’s [[diagnose_if(bool)]] attribute.
I’m amenable to this idea. The major downside I see to it is that it could lead to an arbitrarily complicated C++ expression appearing in an awkward position. But this is not necessarily worse, and perhaps better, than having to do the metaprogramming tricks shown in Conditionally trivial relocation.
The Clang patch currently available on Godbolt Compiler Explorer supports both [[clang::trivially_relocatable]] and another attribute called [[clang::maybe_trivially_relocatable]], which John McCall requested that I explore.
P1144 talks in terms of "warranting" that a class is trivially relocatable; this can be done manually via annotation, or as a sort of "auto-warrant" done automatically by the compiler when we follow the Rule of Zero.
The first level, [[clang::maybe_trivially_relocatable]], means "I warrant that even though I may have user-provided, non-defaulted, special member functions, I have designed them so that my relocation operation will not do anything substantially different from memberwise relocation." So if all of my member + base subobjects are trivially relocatable (and not mutable and not volatile), then I myself will be trivially relocatable.
Using [[clang::trivially_relocatable]], I can write a class that "overrules" a decision made by one of its members. I can make a trivially relocatable class that contains a data member of type boost::interprocess::offset_ptr<int> (using fundamentally non-trivial pieces to build a trivially relocatable whole: see B below). I can make a trivially relocatable class that contains a data member of type boost::shared_ptr<int> (wrapping a fundamentally trivial type in a wrapper that explicitly warrants that triviality in a compiler-visible way: see C below).
Using [[clang::maybe_trivially_relocatable]], I am forbidden to write either of those classes; but I also am forbidden to write a trivially relocatable class that contains a data member of type std::list<int> (wrapping a fundamentally non-trivial type in a wrapper that explicitly, incorrectly, warrants its own triviality: see D below).
The single "Strongly Against" vote on poll 1 was due to concerns that P1144 permits a class to warrant its own trivial relocatability — overruling the compiler’s assumptions — not only when the compiler’s assumptions are based on the presence of special members (A below), but also when the compiler’s assumptions are based partly or wholly on the non-triviality of member or base subobjects (B, C, D below).
// Everyone agrees that we want to allow this.
// P1144 explicitly wants to allow this; but some want to forbid it.
// Arguably this code has poor style.
// This code is buggy.
// P1144 allows you to shoot yourself in the foot like this.
In a P1144 [[trivially_relocatable]] world, this is no problem; the annotation happens at a very high level, where the class designer of optional can be sure that their move-constructor (inherited from optional_move_base) and their destructor (inherited from optional_destruct_base) will play together in the right way.
In a [[clang::maybe_trivially_relocatable]] world, someone needs to annotate optional_destruct_base so that its non-relocatability will not block the high-level optional from becoming trivially relocatable. But then we have a weird scenario: the non-moveable class optional_destruct_base seems to be claiming that it is "trivially relocatable," even though it is not relocatable, nor even moveable! Yet, if we don’t annotate it, we break the high-level optional. This strikes me as a spooky situation where actions in one part of the codebase can have effects on distant parts.
Lastly, the concern over [[trivially_relocatable]] seems to be based on the assumption that working programmers will frequently use this attribute in practice. In reality, I don’t think they will. P1144 is designed so that the Rule of Zero will do the right thing, and all library types will do the right thing. Most programmers won’t ever use the annotation, just as most programmers don’t use [[maybe_unused]] or [[no_unique_address]].
The proposed attribute is a simple, sharp knife. It cuts what you point it at; point it carefully. [[maybe_trivially_relocatable]] is a slightly dull knife; it doesn’t necessarily cut on the first try. A dull knife is often more dangerous than a sharp one.
Pablo Halpern’s [N4158] proposed a completely user-space mechanism: an ADL customization point named uninitialized_destructive_move. In N4158, the user could overload this function for their own types, and thus achieve any arbitrary behavior for the N4158 "destructive move" operation. In contrast, P1144 provides for no middle ground between "memcpy" and "call the move-constructor followed by the destructor."
That middle ground would be useful for types such as MSVC’s std::list. list is fundamentally non-trivially-relocatable, because it must fix up the "prev" pointer in its first node (see Allocated memory contains pointer to self). So P1144 proposes no change to the status quo for list. MSVC’s list is fundamentally non-nothrow-move-constructible because it must heap-allocate a new sentinel node. Thus, on MSVC, vector<list<T>>::reserve must copy each list<T> element, which means calling T's copy-constructor many times.
But if there were a way for MSVC’s list to express its non-trivial "relocate" operation via a customization point, then it could make that "relocate" operation noexcept. And then MSVC’s vector<list<T>>::reserve could be done efficiently by relocating each list<T> element, which avoids performing any operations on T.
N4158’s ADL-customization-point approach seems to have a very high cost-to-benefit ratio. I am satisfied with P1144’s avoiding that approach.
Since Widget is non-nothrow move-constructible, P1144 calls it non-nothrow relocatable. So, looking at how Widget interacts with the type-traits, we are in the awkward position that Widget simultaneously claims "My relocation operation might throw" and "My relocation operation is trivial." These claims seem inconsistent.
This is a real-world concern because GNU libstdc++'s std::deque<T, std::allocator<T>> works like Widget: its move-constructor is noexcept(false) (it must allocate) but it is trivially relocatable. As of 2019-01-18, libstdc++ marks its deque as trivially relocatable (see [Deque]).
However, I believe that it would be incorrect and unsafe for the library to claim that Widget was "nothrow relocatable." "Nothrow relocatable" should imply that a generic algorithm could relocate it (as if by std::uninitialized_relocate) without worrying about catching exceptions. "T is trivially relocatable" means that T is relocatable as if by memcpy; it does not mean that every relocation of T must be performed literally by memcpy.
I believe P1144’s proposed behavior is the best behavior. However, another plausible behavior would be to simply eliminate the is_nothrow_relocatable type-trait from the standard library. If we don’t provide is_nothrow_relocatable, then we don’t have to defend its mildly unintuitive behavior.
Thanks to Elias Kosunen, Niall Douglas, and John Bandela for their feedback on early drafts of this paper.
Many thanks to Matt Godbolt for allowing me to install the prototype Clang implementation on Compiler Explorer (godbolt.org). See also [Announcing].
Thanks to Nicolas Lesser for his relentless feedback on drafts of P1144R0, and for his helpful review comments on the Clang implementation [D50119].
Thanks to Howard Hinnant for appearing with me on [CppChat], and to Jon Kalb and Phil Nash for hosting us.
Thanks to Pablo Halpern for [N4158], to which this paper bears a striking and coincidental resemblance —including the meaning assigned to the word "trivial," and the library-algorithm approach to avoiding the problems with "lame duck objects" discussed in the final section of [N1377]. See discussion of N4034 at Rapperswil (June 2014) and discussion of N4158 at Urbana (November 2014).
Significantly different approaches to this problem have previously appeared in Rodrigo Castro Campos’s [N2754], Denis Bider’s [P0023R0] (introducing a core-language "relocation" operator), and Niall Douglas’s [P1029R0] (treating relocatability as an aspect of move-construction in isolation, rather than an aspect of the class type as a whole).
Thanks to John McCall for his thought-provoking review comments on the Clang implementation [D50119].
Thanks to Marc Glisse for his work integrating a "trivially relocatable" trait into GNU libstdc++ and for answering my questions on GCC bug 87106.
We approve of the general idea that user-defined classes should be able to warrant their own trivial relocatability via a standard mechanism.
We approve of the general idea that user-defined classes which follow the Rule of Zero should inherit the trivial relocatability of their bases and members.
Nobody should be able to warrant the trivial relocatability of class C except for class C itself (i.e., we do not want to see a customization point analogous to std::hash).
A class should be able to warrant its own trivial relocatability via the attribute [[trivially_relocatable]], as proposed in this paper (P1144R0).
A class should be able to warrant its own trivial relocatability via some attribute, but not necessarily under that exact name.
A class should be able to warrant its own trivial relocatability as proposed in this paper (P1144R0), but via a contextual keyword rather than an attribute.
If a trait with the semantics of is_trivially_relocatable<T> is added to the <type_traits> header, the programmer should be permitted to specialize it for program-defined types (i.e., we want to see that trait itself become a customization point analogous to std::hash).
Trivial relocatability should be assumed by default. Classes such as those in Appendix C should indicate their non-trivial relocatability via an opt-in mechanism.
To simplify Conditionally trivial relocation, if an attribute with the semantics of [[trivially_relocatable]] is added, it should take a boolean argument.
The algorithm uninitialized_relocate(first, last, d_first) should be added to the <memory> header, as proposed in this paper (P1144R0).
The type trait is_relocatable<T> (and its _v version) should be added to the <type_traits> header, as proposed in this paper (P1144R0).
If is_relocatable<T> is added, then we should also add is_nothrow_relocatable<T> (and its _v version), as proposed in this paper (P1144R0).
The type trait is_trivially_relocatable<T> (and its _v version) should be added to the <type_traits> header, under that exact name, as proposed in this paper (P1144R0).
If is_trivially_relocatable<T> is added, under that exact name, then the type trait is_trivially_swappable<T> (and its _v version) should also be added to the <type_traits> header.
The "Strongly Against" vote on poll 1 was due to concerns that P1144 permits a class to warrant its own trivial relocatability, overruling the compiler’s assumptions, not only when the compiler’s assumptions are based on the presence of special members, but also when the compiler’s assumptions are based partly or wholly on the non-triviality of member or base subobjects. See further discussion under §5.3 Attribute [[maybe_trivially_relocatable]].
The "Against" vote on poll 10, uninitialized_relocate, was due to its exception guarantee, which was weaker in P1144R0. P1144R1 has strengthened the guarantee (and tightened the constraint on the source iterator from InputIterator to ForwardIterator) to better match the other uninitialized_foo algorithms.
The "Strongly Against" vote on poll 11, is_relocatable, was from a desire to save the name relocatable for something different, such as a built-in destructive-move operation.
Should we commit additional committee time to solving the problem P1144R0 is trying to solve, knowing it will leave less time to other work?
The type trait is_trivially_relocatable<T> (and its _v version) should be added to the <type_traits> header, under that exact name, as proposed in this paper.
We approve of the general idea that user-defined classes should be able to warrant their own trivial relocatability.
We expect, but do not require, that std::optional<T> should be trivially relocatable if and only if T itself is trivially relocatable. We propose no dedicated syntax for conditional [[trivially_relocatable]].
The following abbreviated implementation shows how to achieve an optional<T> which has the same trivial-move-constructibility as T, the same trivial-destructibility as T, and the same trivial-relocatability as T.
The primitives of move-construction and destruction are provided by four specializations of optional_a; then two specializations of optional_b extend optional_a and either do or do not apply the [[trivially_relocatable]] attribute; and finally the public optional extends the appropriate specialization of optional_a.
I have implemented the entire Standard Library using the proposed [[trivially_relocatable]] attribute; you can find the source code on my GitHub and explore the resulting codegen on Godbolt Compiler Explorer.
For why one entry in this table is "problematic," see §5.3 Attribute [[maybe_trivially_relocatable]].
This fictional short_string illustrates a mechanism that can apply to any small-buffer-optimized class. libc++'s std::function uses this mechanism (on a 24-byte buffer) and is thus not trivially relocatable.
However, different mechanisms for small-buffer optimization exist. libc++'s std::any also achieves small-buffer optimization on a 24-byte buffer, without (necessarily) sacrificing trivial relocatability.
std::list needs somewhere to store its "past-the-end" node, commonly referred to as the "sentinel node," whose prev pointer points to the list’s last node. If the sentinel node is allocated on the heap, then std::list can be trivially relocatable; but if the sentinel node is placed within the list object itself (as happens on libc++ and libstdc++), then relocating the list object requires fixing up the list’s last node’s next pointer so that it points to the new sentinel node inside the destination list object. This fixup of an arbitrary heap object cannot be simulated by memcpy.
Traditional implementations of std::set and std::map also store a "past-the-end" node inside themselves and thus also fall into this category.
The offset_ptr provided by [Boost.Interprocess] is an example of this category.
In the following snippet, struct Widget is relocatable, but not trivially relocatable, because the relocation operation of destroying a Widget at point A and constructing a new Widget at point B has behavior that is observably different from a simple memcpy.
godbolt.org, under the name "x86-64 clang (experimental P1144)"
Side-by-side case studies of [[trivially_relocatable]], [[maybe_trivially_relocatable]], and [[trivially_relocatable(bool)]] are given in Conditionally trivial relocation.
As of November 2018, libstdc++ performs the vector::resize optimization for any type which has manually specialized std::__is_trivially_relocatable. (See it on Compiler Explorer here.) Manual specialization is also the approach used by [Folly], [EASTL], and [BSL]. As of 2019-01-18, the only libstdc++ library type for which __is_trivially_relocatable has been specialized is deque; see [Deque] and §5.5 Unintuitive is_nothrow_relocatable.

References: §2

§2
 §2
 §2
 §2
 §2
 §2
 §2
 §2
 §2
 §2
 §2
 §4
 §4
 §5
 §5
 §5
 §5