Почему производный класс перемещается конструктивно, когда базовый класс не является?
Рассмотрим следующий пример:
#include <iostream>
#include <string>
#include <utility>
template <typename Base> struct Foo : public Base {
using Base::Base;
};
struct Bar {
Bar(const Bar&) { }
Bar(Bar&&) = delete;
};
int main() {
std::cout << std::is_move_constructible<Bar>::value << std::endl; // NO
std::cout << std::is_move_constructible<Foo<Bar>>::value << std::endl; // YES. Why?!
}
Почему компилятор генерирует конструктор перемещения, несмотря на то, что базовый класс невозможен?
Это в стандарте или это ошибка компилятора? Возможно ли "прекрасно размножать" перемещение от базового до производного?
Ответы
Ответ 1
Потому что:
Конструктор перемещения по умолчанию, который определяется как удаленный, игнорируется с помощью разрешения перегрузки.
([class.copy]/11)
Bar
move constructor явно удален, поэтому Bar
не может быть перемещен. Но конструктор перемещения Foo<Bar>
неявно удаляется после того, как он неявно объявлен как дефолт, из-за того, что элемент Bar
не может быть перемещен. Поэтому Foo<Bar>
можно перемещать с помощью его конструктора копирования.
Изменить: я также забыл упомянуть о важном факте, что декларация конструктора наследования, такая как using Base::Base
, не наследует конструкторы по умолчанию, копирование или перемещение, поэтому почему Foo<Bar>
не имеет явно удаленного конструктора перемещения, унаследованного от Bar
.
Ответ 2
1. Поведение std::is_move_constructible
Ожидается поведение std:: is_move_constructible:
Типы без конструктора перемещения, но с конструктором копирования, который принимает аргументы const T&
, удовлетворяют std::is_move_constructible
.
Что означает, что с помощью конструктора копирования все еще можно построить T
из rvalue reference T&&
. И Foo<Bar>
имеет неявно объявленный конструктор копирования.
2. Неявно объявленный конструктор перемещения Foo<Bar>
Почему компилятор создает конструктор перемещения, несмотря на то, что базовый класс невозможен?
Фактически, конструктор перемещения Foo<Bar>
определяется как deleted, но обратите внимание, что удаленный неявно объявленный механизм перемещения игнорируется разрешением перегрузки.
Неявно объявленный или дефолтный конструктор перемещения для класса T
является определяемый как удаленный в любом из следующих утверждений:
...
T has direct or virtual base class that cannot be moved (has deleted, inaccessible, or ambiguous move constructors);
...
Удаленный неявно объявленный механизм перемещения игнорируется разрешением перегрузки (в противном случае это предотвратит инициализацию копирования от rvalue).
3. Различное поведение между Bar
и Foo<Bar>
Обратите внимание, что конструктор перемещения Bar
явно объявлен как deleted
, а конструктор перемещения Foo<Bar>
неявно объявлен и определен как deleted
. Дело в том, что удаленный неявно объявленный конструктор перемещения игнорируется разрешением перегрузки, что позволяет перемещать конструкцию Foo<Bar>
с ее конструктором копирования. Но явно удаленный конструктор перемещения будет участвовать в разрешении перегрузки, значит, при попытке перемещения конструктора Bar
будет выбран удаленный конструктор перемещения, тогда программа будет плохо сформирована.
Вот почему Foo<Bar>
перемещается конструктивно, но Bar
не является.
В стандарте есть явное утверждение об этом. $12.8/11 Копирование и перемещение объектов класса
[Class.copy]
Конструктор перемещения по умолчанию, который определяется как удаленный, игнорируется разрешением перегрузки ([over.match], [over.over]). [Примечание. Конструктор с удаленным ходом в противном случае вмешивался бы в инициализацию из rvalue, которая вместо этого может использовать конструктор копирования. - конечная нота]