I propose this implementation of swap
, if valid, is superior to the current implementation of std::swap
:
#include <new>
#include <type_traits>
template<typename T>
auto swap(T &t1, T &t2) ->
typename std::enable_if<
std::is_nothrow_move_constructible<T>::value
>::type
{
alignas(T) char space[sizeof(T)];
auto asT = new(space) T{std::move(t1)};
// a bunch of chars are allowed to alias T
new(&t1) T{std::move(t2)};
new(&t2) T{std::move(*asT)};
}
The page for std::swap
at cppreference, implies it uses move-assignment, because the noexcept
specification depends on whether the move-assignment is no-throw. Furthermore, it has been asked here how is swap
implemented and that is what I see in the implementations for libstdc++ and libc++
template<typename T>
void typicalImplementation(T &t1, T &t2)
noexcept(
std::is_nothrow_move_constructible<T>::value &&
std::is_nothrow_move_assignable<T>::value
)
{
T tmp{std::move(t1)};
t1 = std::move(t2);
// this move-assignment may do work on t2 which is
// unnecessary since t2 will be reused immediately
t2 = std::move(tmp);
// this move-assignment may do work on tmp which is
// unnecessary since tmp will be immediately destroyed
// implicitly tmp is destroyed
}
I dislike using move-assignment as in t1 = std::move(t2)
because that implies executing the code to release the resources held in t1
if resources are held, even though it is known the resources in t1
where released already. I have a practical case in which releasing resources occur across a virtual method call, thus the compiler can't eliminate that unnecessary work because it can't know the virtual override code, whatever it is, won't do anything because there are no resources to release in t1
.
If this is illegal in practice, could you please point out what it violates in the standard?
So far, I have seen in the answers and comments two objections that may make this illegal:
- The ephemeral object created in
tmp
is not destructed, but there may be some assumption in user code that if aT
is constructed it will be destructed T
may be a type with constants or references that can't be changed, the move assignment may be implemented swapping the resources without touching those constants or rebinding references.
Thus, it seems this construct is legal for any type except those that either hit case 1 or 2 above.
For illustration, I put a link to a compiler explorer page which shows all three implementations for the case of swapping vectors of ints, that is, the typical default implementation of std::swap
, the specialized for vector
, and the one I am proposing. You may see the proposed implementation performs less work than the typical, exactly the same as the specialized in the standard.
Only the user can decide to swap doing "all-move-construction" versus "one move construction, two move assignments", your answers inform users of then the "all-move-construction" is not valid.
After more side-band conversations with colleagues, what I am asking boils down to this works for types in which move can be seen as destructive, hence there is no need to balance the constructions with destructions.