Подтвердить что ты не робот

Почему производный класс перемещается конструктивно, когда базовый класс не является?

Рассмотрим следующий пример:

#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?!
}

Почему компилятор генерирует конструктор перемещения, несмотря на то, что базовый класс невозможен?

Это в стандарте или это ошибка компилятора? Возможно ли "прекрасно размножать" перемещение от базового до производного?

4b9b3361

Ответ 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, которая вместо этого может использовать конструктор копирования. - конечная нота]