The noexcept
specifications discussed in your question were introduced in C++17 through N4258. This paper differentiated vector
/string
from other containers (deque
, list
, map
, etc.) in terms of noexcept
specifications for move assignment1 and the swap
function, summarized below2:
Container |
Move assignment |
swap |
vector /string |
POCMA || IAE |
POCS || IAE |
Others |
IAE |
IAE |
For containers aside from vector
and string
, relying solely on POCMA
for the noexcept
guarantee concerning move assignment is insufficient. Some implementations require allocating a sentry with the moved allocator to achieve the moved-from state, which could potentially throw. Notably, Microsoft's implementation of std::set
has a throwing move assignment operator. The paper explains:
[...] even with POCMA we can’t guarantee noexcept
if the container is “kind of” node based (deque, lists, associative, unordered): The moved-from object needs to reallocate if the allocator propagated and is not always equal (because I can't steal the
LHS' memory).
Now, concerning swap
, the aforementioned reasoning does not apply since we only need to exchange everything, including the mentioned sentry object. Therefore, no reallocation is necessary, and POCS || IAE
suffices. A similar rationale is provided in P0177R2:
[...] This should not be an issue for swap operations though, as allocators are expected to be exchanged, along with two data structures that satisfy the invariants. [...]
Having discussed these points, it appears there may be a flaw in the current standard. One possible reason could be the committee's intent to allow node-based containers' swap
operations to be implemented based on their move assignments, leading to the same noexcept
specification as the latter. Unfortunately, the discussion mentioned in N4258, which might provide further insights, is non-public (from WG21 Wiki), so this remains speculative.
For additional insights, I investigated the implementations of node-based containers across the three major vendors to ascertain the noexcept
guarantees they offer for swap
functions. Here are the findings3:
As depicted in the table, all these implementations indeed provide reinforced noexcept
specifications for their respective swap
functions.
1Although move assignment isn't directly addressed in the question, it's mentioned here because it could influence the design of noexcept
specifications for swap
operations.
2POCMA
stands for propagate_on_container_move_assignment
, POCS
stands for propagate_on_container_swap
, and IAE
stands for is_always_equal
. Predicates such as Compare
specifications are omitted here for brevity.
3Similar to the earlier footnote, predicate specifications have been omitted for brevity.
std::vector
container". – Shadowynoexcept
specifiers for both swap and move operations. It is much easier to verify that it won't cause implementation problems for vector than for the other containers and vector should also be higher priority. The standard library implementation is allowed to use stricternoexcept
specification than specified in the standard, so that's just the minimum that all implementations should agree upon. – Allinclusivenoexcept(false)
situation can only arise here when using custom, stateful allocators, since thestd::allocator
template is stateless and therefore always equal. Writing such an allocator is something most C++ programmers will not need to do, but apparently Bloomberg had a major codebase using these. Consequently, Bloomberg developers have provided most of the input I found on allocators, swapping and noexcept. The "Lakos rule" on the use of noexcept in the standard library is named after one of them. – RatoIAE
(which is strict), butvector
requiresPOCS || IAE
, which is a much looser requirement. – Peipus