Разрешено ли копирование / перемещение elision, чтобы программа, использующая удаленные функции, имела правильный формат?

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

#include <iostream>

struct Thing
{
    Thing(void)                       {std::cout << __PRETTY_FUNCTION__ << std::endl;}
    Thing(Thing const &)              = delete;
    Thing(Thing &&)                   = delete;
    Thing & operator =(Thing const &) = delete;
    Thing & operator =(Thing &&)      = delete;
};

int main()
{
    Thing thing{Thing{}};
}

Я ожидаю, что оператор Thing thing{Thing{}}; будет означать создание временного объекта класса Thing с использованием конструктора по умолчанию и создание объекта thing класса Thing с использованием конструктора перемещения с только что созданным временным объектом в качестве аргумента. И я ожидаю, что эта программа будет считаться плохо сформированной, потому что она содержит вызов конструктора удаленного перемещения, даже если он потенциально может быть опущен. Раздел стандарта class.copy.elision, похоже, тоже требует этого:

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

Формулировка гарантированного исключения копий с помощью упрощенных категорий значений / a>, похоже, тоже этого не допускает.

Однако gcc 7.2 (и clang 4 тоже, но не VS2017, который все еще не поддерживает гарантированное исключение копирования) будет просто скомпилировать этот код, исключив перемещение вызов конструктора.

Какое поведение было бы правильным в этом случае?


person user7860670    schedule 06.09.2017    source источник
comment
Почему вы ожидаете временное значение здесь, а не в Thing thing{f()};, где f() возвращает Thing?   -  person Bo Persson    schedule 06.09.2017
comment
Кроме того, я подозреваю, что доступный - значит не частный.   -  person Bo Persson    schedule 06.09.2017
comment
@BoPersson Нет, он также работает с частными конструкторами.   -  person Hatted Rooster    schedule 06.09.2017
comment
@BoPersson Я ожидал, что Thing thing{f()}; будет означать вызов конструктора перемещения с временным, возвращаемым f() вызовом аргумента. Мы надеемся, что этого удастся избежать накладных расходов на создание временных файлов.   -  person user7860670    schedule 06.09.2017
comment
Формулировка для гарантированного исключения копий с помощью упрощенных категорий значений, похоже, тоже не позволяет этого Да, но на удивление мало людей понимают как (или не торопятся отвечать) . Выбранный ответ показывает только пример, но не является нормативной формулировкой. И нормативная формулировка перед Примером не гарантирует, что весь T(T(T())) сворачивается в () или что конструктор копирования / перемещения не выбран при разрешении перегрузки.   -  person Language Lawyer    schedule 18.06.2020
comment
Кстати, эта формулировка, похоже, даже не применима к вашему делу.   -  person Language Lawyer    schedule 18.06.2020


Ответы (1)


Он не создает плохо сформированную программную сборку. Он полностью избавляется от ссылки на удаленную функцию. Соответствующая формулировка предложения находится здесь:

[dcl.init] пуля 17.6

Если выражение инициализатора является prvalue, а версия cv-unqualified исходного типа является тем же классом, что и класс назначения, выражение инициализатора используется для инициализации объекта назначения. [Пример: Т х = Т (Т (Т ())); вызывает конструктор по умолчанию T для инициализации x. ]

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

Следует отметить, что удаленная функция никогда не используется odr, когда копии исключаются из-за категорий значений, поэтому программа не ссылается на нее.

Это важное различие, поскольку другая форма исключения копирования по-прежнему odr-использует c'tor копирования, как описано здесь:

[basic.def.odr] / 3

... Конструктор, выбранный для копирования или перемещения объекта типа класса, используется odr, даже если вызов фактически отменен реализацией ([class.copy] ...

[class.copy] описывает другую форму допустимого (но не обязательного) исключения из копирования. Что, если мы продемонстрируем с вашим классом:

Thing foo() {
    Thing t;
    return t; // Can be elided according to [class.copy.elision] still odr-used
}

Должен сделать программу некорректной. И GCC жалуется на это, как и ожидалось.


И кстати. Если вы думаете, что предыдущий пример в онлайн-компиляторе - это фокус фокусников, а GCC жалуется, потому что ему нужно вызвать c'tor перемещения. Посмотрите, что происходит, когда мы даем определение.

person StoryTeller - Unslander Monica    schedule 06.09.2017
comment
Спасибо за подробный ответ. Я думаю, что следующий пункт (17.6.2) на самом деле более интересен: в противном случае, если инициализация является прямой инициализацией, или если это инициализация копией, где cv-неквалифицированная версия исходного типа является тем же классом, что и, или производный класс, класс назначения, конструкторы рассматриваются. То есть ссылочные конструкторы rvalue не учитываются при передаче соответствующего prvalue, поэтому программа, похоже, остается правильно сформированной, потому что удаленный конструктор не учитывался. Но, похоже, это несколько несовместимо с требованием копировать / перемещать раздел исключения. - person user7860670; 06.09.2017
comment
@VTT - Раздел «Копирование / перемещение», на самом деле, о другом. Я думаю, уместно отметить, что, хотя в разговорной речи предлагается обязательное копирование / перемещение, вместо этого он использует термин временная материализация. По сути, он не определяет копию, которую можно опустить. Он просто откладывает инициализацию до максимально возможной поздней стадии, поэтому фактически нет необходимости в копии. Вот как происходит волшебство. - person StoryTeller - Unslander Monica; 06.09.2017
comment
Проблема в том, что с удаленными / недоступными конструкторами, которые не учитываются, эта отложенная инициализация позволяет передавать объекты вокруг по стоимости, что в конечном итоге приведет к строительству объекта в местах, которые в противном случае были бы запрещены. Я могу представить себе некоторые преимущества (например, инкапсуляцию класса mutex и наличие метода, непосредственно возвращающего lock_guard вместо возврата ссылки на mutex для создания lock_guard на стороне пользователя). Но это все еще кажется неправильным. - person user7860670; 06.09.2017
comment
@VTT - Я понимаю, почему это неправильно. Это своего рода серьезный сдвиг парадигмы. Но, как вы сказали, это тоже много преимуществ. - person StoryTeller - Unslander Monica; 06.09.2017
comment
@VTT: Вы не передаете предметы. Вы передаете prvalue, которые явно еще не являются объектами. Это уловка, благодаря которой гарантированное исключение работает. Prvalue еще не объект. Это инициализатор для объекта. Переопределяя значение prvalues, вы удаляете операции, которые были бы пропущены. - person Nicol Bolas; 06.09.2017
comment
Соответствующая формулировка предложения здесь. Инициализатор - это не выражение prvalue, а список в фигурных скобках, насколько он уместен? - person Language Lawyer; 18.06.2020