Почему при возврате совместимого типа требуется явно std:: move?
Я смотрю " Не помогите компилятору" поговорить с STL, где у него есть аналогичный пример на слайде 26:
struct A
{
A() = default;
A(const A&) { std::cout << "copied" << std::endl; }
A(A&&) { std::cout << "moved" << std::endl; }
};
std::pair<A, A> get_pair()
{
std::pair<A, A> p;
return p;
}
std::tuple<A, A> get_tuple()
{
std::pair<A, A> p;
return p;
}
std::tuple<A, A> get_tuple_moved()
{
std::pair<A, A> p;
return std::move(p);
}
При этом следующий вызов:
get_pair();
get_tuple();
get_tuple_moved();
Производит этот вывод:
moved
moved
copied
copied
moved
moved
См. MCVE в действии.
Результат get_pair
построен по ходу движения, что и ожидалось. Движение также может быть полностью отменено NRVO, но оно не соответствует теме настоящего вопроса.
Результат get_tuple_moved
также построен по ходу, что явно указано так. Однако результат get_tuple
копируется, что совершенно неочевидно для меня.
Я думал, что любое выражение, переданное в оператор return
, может считаться неявным move
на нем, поскольку компилятор знает, что он все равно выйдет за рамки. Кажется, я ошибаюсь. Может кто-то уточнить, что здесь происходит?
См. также связанный, но другой вопрос: Когда следует использовать std:: move для возвращаемого значения функции?
Ответы
Ответ 1
Оператор return в get_tuple() должен быть инициализирован с помощью move-constructor, но поскольку тип возвращаемого выражения и тип возвращаемого значения не совпадают, вместо этого выбирается copy-constructor. В С++ 14 произошли изменения, где теперь начальная фаза разрешения перегрузки обрабатывает оператор return как rvalue, когда это просто автоматическая переменная, объявленная в теле.
Соответствующую формулировку можно найти в [class.copy]/p32:
Когда выполняются критерии для исключения операции копирования/перемещения, [..], или когда выражение в операторе return является (возможно, в скобках) id-выражением, которое называет объект с автоматической продолжительностью хранения объявленный в теле [..], разрешение перегрузки для выбора конструктора для копии сначала выполняется так, как если бы объект был обозначен rvalue.
Итак, в С++ 14 все выходные данные должны поступать от конструктора move A.
Варианты брандмауэров clang и gcc уже реализуют это изменение. Чтобы получить такое же поведение в режиме С++ 11, вам нужно будет использовать явный std:: move() в операторе return.