I am messing around with throwing move constructors and assignment operators.
It is a well known problem with std::vector
that the vector elements have to be copied on reallocation, and cannot be moved, if the move constructor of said elements is not noexcept
. This is to preserve the strong exception safety guarantee of the vector (if moving an element throws and we were in the middle of moving the entire vector, we'd be left with half a vector of moved elements, so it's better to copy so we can at least rollback if the operation fails).
However, what I did not know is that std::tuple
suffers from this problem as well. Given a std::tuple<A, B, A>
, where the move assignment operator of B throws, moving said tuple results in just the first A
object being moved into the new tuple, while the latter two remain in the old tuple. We essentially get the object split in half. I just tested this exact scenario and, sure enough, the tuple is now in an inconsistent state.
I then tried the same thing with just a struct
with those same three members, A
, B
and A
again. I was horrified to find out that it has the exact same problem. The default move assignment operator does not take care of exception safety.
Is this actually the intended behavior? Does std::tuple
not provide the strong exception safety guarantee? Does the default move assignment operator not provide said guarantee?
It seems like a massive issue. Do we have to explicitly provide the move constructor and move assignment operator for the cases in which the moving of members might throw? This essentially means always providing it in generic/template contexts. It is a lot of work. What can we do about it?