Ответ 1
std::vector<T>::push_back()
требует, чтобы T
удовлетворял концепции MoveInsertable (которая фактически включает в себя распределитель Alloc
). Это потому, что push_back
для вектора может потребоваться увеличить вектор, переместив (или скопировав) все элементы, которые уже есть в нем.
Если вы объявите c'or для T
как удаленный, то, по крайней мере для распределителя по умолчанию (std::allocator<T>
), T
больше не будет MoveInsertable. Обратите внимание, что это отличается от случая, когда конструктор перемещения не объявлен, например, потому что неявно генерируется только копия c'tor, или потому что была объявлена только копия c'tor, и в этом случае тип по-прежнему MoveInsertable, но копия c'tor на самом деле называется (это немного нелогично TBH).
Причина, по которой перемещение c'tor на самом деле никогда не вызывается, заключается в том, что вы вставляете только один элемент, и поэтому перемещение существующих элементов не требуется во время выполнения. Важно, что ваш аргумент для push_back
сам по себе является lvalue и поэтому копируется и ни в коем случае не перемещается.
ОБНОВЛЕНИЕ: я должен был рассмотреть это более внимательно (благодаря комментариям в комментариях). Версии MSVC, которые отклоняют код, на самом деле правильны (очевидно, и 2015, и до 2018 года делают это, но 2017 принимает код). Поскольку вы вызываете push_back(const T&)
, T
должен быть CopyInsertable. Однако CopyInsertable определяется как строгое подмножество MoveInsertable. Поскольку ваш тип не является MoveInsertable, он также не является CopyInsertable по определению (обратите внимание, что, как объяснено выше, тип может удовлетворять обеим концепциям, даже если он является только копируемым, если только c Move не был явно удален),
Это, конечно, поднимает еще несколько вопросов: (A) Почему GCC, Clang и некоторые версии MSVC все-таки принимают код, и (B) нарушают ли они стандарт?
Что касается (A), нет другого способа узнать, кроме как поговорить с разработчиками стандартной библиотеки или посмотреть на исходный код… я бы хотел догадаться, что просто не нужно реализовывать эту проверку, если все, что вас волнует, это создание легальных программ Работа. Перемещение существующих элементов во время push_back
(или reserve
и т.д.) Может происходить любым из трех способов в соответствии со стандартом:
- Если
std::is_nothrow_move_constructible_v<T>
, то элементы не перемещаются (и операция строго безопасна для исключений). - В противном случае, если
T
- CopyInsertable, то элементы копируются (и операция строго безопасна для исключений). - В противном случае элементы перемещаются (и эффекты исключений, возникающих в ходе перемещения, не определены).
Так как ваш тип не только перемещается, но имеет копию или нет, можно выбрать второй вариант. Это снисходительно, так как проверка MoveInsertable не выполняется. Это может быть недосмотр реализации, или я мог быть намеренно проигнорирован. (Если тип не MoveInsertable, то весь вызов некорректен, поэтому эта пропущенная проверка не влияет на правильно сформированные программы.)
Что касается (B), IMO реализации, которые принимают код, нарушают стандарт, потому что они не производят диагностику. Это связано с тем, что реализация должна выдавать диагностику для плохо сформированных программ (это включает в себя программы, использующие языковые расширения, предоставляемые реализацией), если в стандарте не указано иное, в данном случае это не так.