However, is there any real case where redirecting, move-construct (or swap) can actually throw away?
Yes. Consider the implementation of std::list . The end iterator must indicate "one minus the last item" in the list. There are implementations of std::list , where what end points to is a dynamically allocated node. Even the default constructor allocates such a node, so when you call end() , there is something to indicate.
In such an implementation, each constructor must allocate a node for end() to indicate ... even a move constructor. This distribution may fail and throw an exception.
The same behavior can apply to any container node.
There are also implementations of these node-based containers that perform short-string optimization: they embed the end of the node inside the container class itself instead of dynamically highlighting it. Thus, the default constructor (and the move constructor) should not highlight anything.
The forwarding assignment operator can throw for any container<X> if propagate_on_container_move_assignment::value is false for the container allocator, and if the allocator in lhs is not equal to the allocator in rhs. In this case, the transition assignment operator is not allowed to transfer memory ownership from rhs to lhs. This cannot be the case if you use std::allocator , since all instances of std::allocator are equal to each other.
Update
Here is an example of the correspondence and portability of the case where propagate_on_container_move_assignment::value is false. It has been tested against the latest version of VS, gcc and clang.
#include <cassert> #include <cstddef> #include <iostream> #include <vector> template <class T> class allocator { int id_; public: using value_type = T; allocator(int id) noexcept : id_(id) {} template <class U> allocator(allocator<U> const& u) noexcept : id_(u.id_) {} value_type* allocate(std::size_t n) { return static_cast<value_type*>(::operator new (n*sizeof(value_type))); } void deallocate(value_type* p, std::size_t) noexcept { ::operator delete(p); } template <class U, class V> friend bool operator==(allocator<U> const& x, allocator<V> const& y) noexcept { return x.id_ == y.id_; } }; template <class T, class U> bool operator!=(allocator<T> const& x, allocator<U> const& y) noexcept { return !(x == y); } template <class T> using vector = std::vector<T, allocator<T>>; struct A { static bool time_to_throw; A() = default; A(const A&) {if (time_to_throw) throw 1;} A& operator=(const A&) {if (time_to_throw) throw 1; return *this;} }; bool A::time_to_throw = false; int main() { vector<A> v1(5, A{}, allocator<A>{1}); vector<A> v2(allocator<A>{2}); v2 = std::move(v1); try { A::time_to_throw = true; v1 = std::move(v2); assert(false); } catch (int i) { std::cout << i << '\n'; } }
This program displays:
1
which indicates that the motion assignment operator vector<T, A> is copying / moving its elements when propagate_on_container_move_assignment::value is false and the two allocators in question are not compared equal. If any of these copies / movements is thrown, then the job of moving the container throws.