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

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

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

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

Это стандарт или ошибка компилятора? Можно ли «идеально размножить» конструкцию перехода от базового класса к производному?


person Dmitry    schedule 25.08.2016    source источник
comment
Что ж, у вас не будет этой проблемы, если вы не настаиваете на написании классов, которые можно копировать, но не перемещать, и нет причин когда-либо делать это.   -  person Brian Bi    schedule 25.08.2016
comment
Идея, лежащая в основе этого вопроса, заключается в возможности создания ненавязчивых классов-оболочек, которые демонстрируют точно такое же внешнее поведение, что и их основы. P.S. Было действительно сложно выбрать, какой ответ принять (я не могу принять оба, правда?) :)   -  person Dmitry    schedule 25.08.2016
comment
@ Дмитрий: Нет, вы не можете принять более одного, но вы должны проголосовать за любую правдоподобную попытку помочь вам разобраться в проблеме. Кстати, отличный вопрос, раскрывающий неинтуитивное поведение!   -  person thokra    schedule 25.08.2016
comment
@ Брайан: class FamousPaintingInTheLouvre?   -  person einpoklum    schedule 25.08.2016
comment
@einpoklum В идеале это можно было бы перемещать, но не копировать.   -  person JAB    schedule 25.08.2016
comment
@einpoklum Вы бы не удалили конструктор перемещения для такого класса, вы просто позволили бы конструкции из rvalue использовать конструктор копирования.   -  person Brian Bi    schedule 25.08.2016
comment
Не существует правила, согласно которому конструктор перемещения должен действительно перемещать что-либо - char * является конструктивным перемещением.   -  person David Schwartz    schedule 25.08.2016


Ответы (2)


Потому что:

Конструктор перемещения по умолчанию, который определен как удаленный, игнорируется разрешением перегрузки.

([class.copy] / 11)

Конструктор перемещения Bar явно удален, поэтому Bar не может быть перемещен. Но конструктор перемещения Foo<Bar> неявно удаляется после неявного объявления значения по умолчанию из-за того, что элемент Bar не может быть перемещен. Следовательно, Foo<Bar> можно перемещать с помощью его конструктора копирования.

Изменить: я также забыл упомянуть важный факт, что объявление наследующего конструктора, такое как using Base::Base, не наследует конструкторы по умолчанию, копировать или перемещать, поэтому Foo<Bar> не имеет явно удаленного конструктора перемещения, унаследованного от Bar.

person Brian Bi    schedule 25.08.2016
comment
Хм, я этого не понимаю. Причина, по-видимому, в том, что конструктор перемещения But Foo ‹Bar› неявно удаляется после неявного объявления значения по умолчанию из-за того, что элемент Bar не может быть перемещен. Поэтому Foo ‹Bar› можно перемещать с помощью его конструктора копирования. как вы описали. Итак, почему он не утверждает, что конструктор перемещения Bar явно удален, поэтому Bar можно перемещать с помощью его конструктора копирования? - person Johannes Schaub - litb; 25.08.2016
comment
Думаю, Следовательно, Foo<Bar> можно переместить с помощью его конструктора копирования. сформулирован очень запутанно. Возможно, было бы лучше перефразировать так, чтобы не предполагалось, что объект перемещается из, а только то, что (пример) вызов Foo<Bar>(std::move(other)) является допустимым. Или что-то. - person user703016; 25.08.2016
comment
@AndreasPapadopoulos ИМО, было бы лучше сказать, что Foo<Bar> можно переместить, построенный с использованием его копии ctor. - person songyuanyao; 25.08.2016
comment
@Andreas Простым смертным вроде меня кажется, что нет разницы между построенным движением и перемещенным в него. Если для базы предикат A<Base> не выполняется, это кажется очень запутанным, что для производного класса, который должен выполнить операцию с объектом Base, который A<Base> утверждает, чтобы заявить о возможности, предикат внезапно дает true. Мне показалось бы логичным, если это относится к недостаткам этих предикатов, что они могут пропустить ошибки, которые происходят не в непосредственных контекстах, таких как побочные экземпляры и т. Д. Но если есть концептуальная причина, я ее еще не понимаю. - person Johannes Schaub - litb; 25.08.2016
comment
@ JohannesSchaub-litb: ключевое различие между ними состоит в том, что Base явно удаляет ctor перемещения (- ›ошибка времени компиляции при перемещении), а A<Base> неявно удаляет его из-за того, что указано в пункте, который не полностью назван Сунъюаняо. Поскольку неявно удаленные объекты перемещения не принимаются во внимание при попытке переместить экземпляр A<Base>, и поскольку A<Base>::A(A const&) в этом случае остается единственным объектом, вызываемым при перемещении, я понимаю, почему предикат true для производных и false для базы. Это чертовски сбивает с толку, но, черт возьми, интуиция, верно? - person thokra; 25.08.2016

1. Поведение std::is_move_constructible

Это ожидаемое поведение std :: is_move_constructible:

Типы без конструктора перемещения, но с конструктором копирования, который принимает const T& аргумента, удовлетворяют std::is_move_constructible.

Это означает, что с помощью конструктора копирования все еще возможно построить T из ссылки rvalue T&&. И Foo<Bar> имеет неявно объявленный конструктор копирования.

2. Неявно объявленный конструктор перемещения Foo<Bar>

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

Фактически, конструктор перемещения Foo<Bar> определяется как удаленный, но обратите внимание, что удаленный неявно объявленный конструктор перемещения игнорируется разрешением перегрузки.

Неявно объявленный или заданный по умолчанию конструктор перемещения для класса 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, который вместо этого может использовать конструктор копирования. - конец примечания]

person songyuanyao    schedule 25.08.2016
comment
Мне неясно, где этот ответ касается разницы в поведении между классами Bar и Foo<Bar>. - person Marc van Leeuwen; 25.08.2016
comment
Хм ... мне все еще не ясно. Почему бы также не быть правдой, что конструктор перемещения для Bar, который явно удален, игнорируется разрешением перегрузки? Я угадаю. Несмотря на то, что он удален, он все еще существует, участвует в разрешении перегрузки, и когда он соответствует, бинго, программа плохо сформирована. Примечание eel.is/c++draft/dcl.fct.def. удалить пункт 2, кажется, говорит об этом. Комитет по стандартам, безусловно, заслуживает награды за изобретение запутанной терминологии (здесь удалено для чего-то, что продолжает существовать; что-то вроде запрещенного или отравленного было бы более наводящим на размышления). - person Marc van Leeuwen; 25.08.2016
comment
@MarcvanLeeuwen Неявно объявленный конструктор перемещения также существует, даже если он удален. Поэтому я думаю, что комитет просто хочет сделать разницу между явно удаленным и неявно удаленным. Если вы deleted это явно, предполагается, что класс вообще не поддерживает семантику перемещения, включая конструкцию перемещения. - person songyuanyao; 25.08.2016
comment
@MarcvanLeeuwen: Потому что вы явно заявляете, что НЕ игнорируете это. В противном случае не было бы способа явно указать, что ваш тип вообще не может быть перемещен. - person thokra; 25.08.2016
comment
@songyuanyao: +1 к вашему ответу, но, пожалуйста, сделайте еще две вещи: а) добавьте соответствующие номера пунктов и б) проясните, что явное и неявное удаление приводят к разному поведению. - person thokra; 25.08.2016
comment
@thokra: Вдруг я понял. Бог явно пометил яблоко в Эдемском саду как =delete, чтобы Адам и Ева НЕ пропустили его. Блестяще. - person Marc van Leeuwen; 25.08.2016
comment
@MarcvanLeeuwen: Это чертовски запутанно ... подобные вещи - проклятие C ++ в моих глазах. - person thokra; 25.08.2016
comment
@thokra Я попытался добавить дополнительные пояснения. Номер статьи, вы имели ввиду для всех заявлений? - person songyuanyao; 25.08.2016
comment
@songyuanyao: Было бы неплохо, хотя бы для полноты картины. - person thokra; 25.08.2016
comment
Типы без конструктора перемещения, но с конструктором копирования, принимающим const T& аргументы, удовлетворяют std::is_move_constructible. Разве тогда Bar не должен быть перемещаемым, поскольку у него есть копирующий конструктор? - person tkausl; 25.08.2016
comment
@songyuanyao: Теперь вы структурировали свой ответ более четко, но я имел в виду номера пунктов в стандарте C ++ (например, $ 12,8 / 11). :) - person thokra; 25.08.2016
comment
@thokra Только последний абзац взят из стандарта, как написано в моем ответе, это от $ 12.8 / 11 Копирование и перемещение объектов класса [class.copy]. - person songyuanyao; 25.08.2016