MSVC не может вернуть объект, который можно скопировать, но нельзя переместить

Когда я возился с copy elision, я столкнулся со странным поведением:

class Obj {
 public:
  Obj() = default;

  Obj(Obj&&) = delete;
  Obj(const Obj&) { std::cout << "Copy" << std::endl; }
};

Obj f1() {
  Obj o;
  return o; // error C2280: move constructor is deleted
}

Obj f2() {
  Obj o;
  return Obj(o); // this however works fine
}

int main() {
  Obj p = f1();
  Obj q = f2();

  return 0;
}

GCC и Clang принимают этот код и могут использовать исключение копирования в обоих случаях.

В f1() MSVC жалуется, что не может вернуть o, потому что конструктор перемещения Obj удален. Однако я ожидал, что он сможет использовать конструктор копирования. Это ошибка в MSVC или это желаемое поведение (которое я не понимаю) и GCC / Clang слишком снисходительны?

Если я предоставлю конструктор перемещения, MSVC сможет исключить перемещение при компиляции как Release.

Интересно, что MSVC умеет компилировать f2(). Насколько я понимаю, это связано с обязательным исключением копии, когда возвращается результат вызова конструктора. Однако кажется нелогичным, что я могу вернуть o по значению, только если скопирую его вручную.

Я знаю, что эта ситуация может не иметь отношения к практическому использованию, поскольку копируемые объекты обычно также являются подвижными, но меня интересует лежащая в основе механика.

Вот онлайн-пример для тестирования: https://godbolt.org/z/sznds7


person Devon Cornwall    schedule 27.02.2021    source источник
comment
В f1 копирование не является обязательным, в f2 нет. Msvc прав, но gcc и clang тоже. См. en.cppreference.com/w/cpp/language/copy_elision.   -  person Bernd    schedule 28.02.2021
comment
@Bernd Я понимаю эту часть. Но суть вопроса остается неизменной: почему MSVC не просто использует конструктор копирования (без какой-либо оптимизации)?   -  person Devon Cornwall    schedule 28.02.2021
comment
Насколько я понимаю, если конструктор перемещения выбран с помощью разрешения перегрузки и удален, компиляция должна завершиться ошибкой независимо от того, будет ли он удален.   -  person doug    schedule 28.02.2021
comment
Объявление функции удаленной - это не то же самое, что ее полное исключение. Удаленная функция по-прежнему участвует в разрешении перегрузки, и если она действительно выбрана, программа имеет неправильный формат. Просто отбросьте конструктор перемещения; компилятор не будет неявно объявлять это, если существует определяемый пользователем конструктор копирования.   -  person Igor Tandetnik    schedule 28.02.2021
comment
Хотя функции deleted участвуют в разрешении перегрузки, похоже, существует исключение для специальных функций-членов, где они явно игнорируются. Мне кажется, это ошибка MSVC.   -  person cigien    schedule 28.02.2021
comment
@cigien - это применимо? этот конструктор перемещения не является перемещением по умолчанию (= default) ...   -  person davidbak    schedule 28.02.2021
comment
Также следует отметить, что тип с конструктором копирования, но no конструктором перемещения является своего рода сломанным типом. Нет (почти) причин для создания такого типа, поскольку конструктор перемещения может просто делать то, что делает конструктор копирования.   -  person Nicol Bolas    schedule 28.02.2021
comment
Это не совсем так. Цитата: Специальная функция-член перемещения по умолчанию ([class.copy.ctor], [class.copy.assign]), которая определена как удаленная, исключается из набора функций-кандидатов во всех контекстах. Думаю, это касается =delete.   -  person cigien    schedule 28.02.2021
comment
@cigien - но в примере, приведенном в этом разделе, обсуждаемый конструктор перемещения для struct B - это = default, но он определен как удаленный, потому что struct B содержит член, который является структурой, которая имеет удаленный (= delete) конструктор перемещения ...   -  person davidbak    schedule 28.02.2021
comment
Хм, ладно, возможно, я неправильно понимаю, что в этой цитате означает «удалено».   -  person cigien    schedule 28.02.2021
comment
@cigien Когда специальная функция-член объявляется как заданная по умолчанию, компилятор предоставляет неявное определение, следуя набору правил. Иногда в этих правилах говорится, что функцию следует удалить (например, конструктор перемещения для класса, у которого есть неподвижный член); тогда функция определяется как удаленная. В этом случае разрешение перегрузки ведет себя так, как если бы функция вообще не была объявлена.   -  person Igor Tandetnik    schedule 28.02.2021
comment
@IgorTandetnik А, теперь в этом больше смысла. Я явно неправильно прочитал этот текст. Спасибо за объяснение.   -  person cigien    schedule 28.02.2021


Ответы (2)


Отсутствие ошибки на f1() является ошибкой как в clang, так и в gcc. Исправлено в конце ствола clang.

f1() не имеет права на обязательную проверку копии.

Удаленные функции участвуют в разрешении перегрузки. Если они выбраны как лучшая перегрузка, программа плохо сформирована. В f1() удаленный конструктор перемещения выбирается разрешением перегрузки.

В f2(), начиная с C ++ 17, скопируйте / move elision гарантируется, поэтому разрешение перегрузки в конструкторах перемещения / копирования не выполняется. В C ++ 11/14 f2() также является ошибкой (та же ошибка, что и f1()), поскольку исключение копирования / перемещения не гарантируется.

Также см. Это руководство: Никогда не удаляйте специальные элементы перемещения, которые, по общему признанию, были написаны до C ++ 17.

person Howard Hinnant    schedule 27.02.2021
comment
Я немного сбит с толку, похоже, вы говорите, что GCC / Clang разрешил удаленный конструктор перемещения. Это A) ошибка в компиляторе, B) поведение, определенное в спецификации, или C) поведение undefined (что делает его не ошибкой)? Похоже, вы говорите C, поэтому f1 всегда будет иметь неопределенное поведение с объектами, у которых есть удаленный конструктор перемещения. Если это так, то была ли ошибка в том, что GCC / Clang определил конструктор перемещения без конструктора копирования? Это баг, если это УБ? - person jrh; 28.02.2021
comment
Первое предложение в ответе - это полный ответ. Все остальное является вспомогательной информацией для первого предложения. - person Howard Hinnant; 28.02.2021
comment
@jrh Плохо сформированные программы - это не UB. Плохо сформированные программы требуют диагностики (ошибка). Плохо сформированные программы, не требующие диагностики, могут компилировать и генерировать UB, но это не плохо сформированный случай отчета о недоставке; последнее, что я подсчитал, вы можете пересчитать плохо оформленные случаи NDR по одной руке. - person Yakk - Adam Nevraumont; 01.03.2021
comment
Спасибо @ Yakk-AdamNevraumont. Вы поняли вопрос младшего, а я - нет. Хороший ответ. После десятилетий, проведенных в этой сфере деятельности, я попал в ловушку ошибочных стандартов - говорить за простой английский. - person Howard Hinnant; 01.03.2021

о, мне стыдно, я только что понял, что другой ответ принадлежит Говарду Хиннанту, единственному человеку, чьи труды заставили меня понять то, что я мучительно пытаюсь объяснить здесь, это немного смешно ...

Поскольку оба конструктора копирования и перемещения объявлены, они оба существуют. В частности, здесь вы позаботились о том, чтобы сами определить конструктор копирования; без этого он был бы определен как удаленный (см. стр. 28 этой презентации).

Аспект удаленный - это просто подробное описание определения, но они оба фактически объявлены, а затем имеют право на перегрузку разрешения. В f1(), если происходит исключение копирования, нет необходимости выбирать между конструктором копирования и перемещения; ни один из них не используется. С другой стороны, если исключение копирования не происходит, тогда должна быть выбрана лучшая перегрузка для построения результата; здесь это конструктор перемещения, потому что он существует (он объявлен, см. здесь), и наконец определение обнаруживается как удаленное, но уже слишком поздно, выбор уже сделан.

В f2() явно запрашивается явная копия, тогда конструктор копирования - лучший выбор.

Что довольно сбивает с толку, когда мы читаем =delete, мы думаем, что «это не может быть выбрано в разрешении перегрузки», но это неверно; =delete считается после устранения перегрузки, когда уже слишком поздно найти лучшее совпадение.

person prog-fh    schedule 28.02.2021
comment
Не столько смешно, сколько забавно, похоже, у вас есть соревнования по туфу;) Я ценю ваши усилия, так что это мой голос. - person anastaciu; 28.02.2021
comment
Как говорит Анастасиу, это забавнее всего на свете. В этом определенно нечего стыдиться. Мы все учимся друг у друга; вот так оно и есть :) - person cigien; 28.02.2021
comment
На самом деле мне больше нравится этот ответ, поскольку он гораздо более подробно объясняет разрешение перегрузки удаленного метода. - person Mooing Duck; 28.02.2021