std::vector<T>::push_back()
requires T
to satisfy the MoveInsertable concept (which actually involves the allocator Alloc
). This is because push_back
on a vector may need to grow the vector, moving (or copying) all elements that are already in it.
If you declare the move c'tor of T
as deleted, then, at least for the default allocator (std::allocator<T>
), T
is no longer MoveInsertable. Note that this is different from the case where the move constructor is not declared, e.g. because only a copy c'tor could be implicitly generated, or because only a copy c'tor has been declared, in which case the type is still MoveInsertable, but the copy c'tor is actually called (this is a bit counterintuitive TBH).
The reason why the move c'tor is never actually called is that you only insert one element, and no moving of existing elements is therefore needed at run time. Importantly, your argument to push_back
itself is an lvalue and therefore is copied and not moved in any case.
UPDATE: I had to look at this more closely (thanks to the feedback in the comments). The versions of MSVC that reject the code are actually right in doing so (apparently, both 2015 and pre-2018 do that, but 2017 accepts the code). Since you are calling push_back(const T&)
, T
is required to be CopyInsertable. However, CopyInsertable is defined as a strict subset of MoveInsertable. Since your type is not MoveInsertable, it is not CopyInsertable either, by definition (note that, as explained above, a type may satisfy both concepts, even if it is only copyable, as long as the move c'tor is not explicitly deleted).
This raises some more questions, of course: (A) Why do GCC, Clang and some versions of MSVC accept the code anyway, and (B) are they violating the standard by doing so?
As for (A), there is no way to know other than talking to standard library developers or looking at the source code… my quick guess would be that it is simply not necessary to implement this check if all you care about is making legal programs work. Relocating existing elements during a push_back
(or a reserve
etc.) can happen in any of three ways according to the standard:
- If
std::is_nothrow_move_constructible_v<T>
, then elements are nothrow-moved (and the operation is strongly exception safe).
- Otherwise, if
T
is CopyInsertable, then elements are copied (and the operation is strongly exception safe).
- Otherwise, elements are moved (and the effects of an exception raised in a move c'tor are unspecified).
Since your type is not nothrow move c'tible, but has a copy c'tor, the second option could be chosen. This is lenient in that no check is made for MoveInsertable. This could be an implementation oversight, or i could be deliberately ignored. (If the type is not MoveInsertable, then the whole call is ill-formed, therefore this missing check does not affect well-formed programs.)
As for (B), IMO the implementations that accept the code are violating the standard because they emit no diagnostic. This is because an implementation is required to emit a diagnostic for ill-formed programs (this includes programs that use language extensions provided by the implementation), unless otherwise noted in the standard, neither of which is the case here.