Почему возвращение std :: optional иногда перемещается и иногда копируется?

Смотрите пример ниже возвращение факультативного из UserName - подвижный/копируемого класса.

std::optional<UserName> CreateUser()
{
   UserName u;
   return {u}; // this one will cause a copy of UserName
   return u;   // this one moves UserName
}


int main()
{
   auto d = CreateUser();
}

Почему return {u} вызывает копию и return u перемещение?

Здесь соответствующий образец coliru: http://coliru.stacked-crooked.com/a/6bf853750b38d110

Другой случай (спасибо комментарию от @Slava):

std::unique_ptr<int> foo() 
{ 
    std::unique_ptr<int> p; 
    return {p};  // uses copy of unique_ptr and so it breaks...
}

Ответ 1

Поскольку возврат имени объекта с автоматической продолжительностью хранения рассматривается как возвращающее значение r объекта. Обратите внимание, что это работает, только если выражение в операторе return является (возможно, в скобках, не включая фигурные скобки), например return u; или return (u); , поэтому return {u}; работает как обычно, т.е. вызывается конструктор копирования.

Связанная с этим часть в стандарте [class.copy.elision]/3:

В следующих контекстах копирования-инициализации вместо операции копирования может использоваться операция перемещения:

  • Если выражение в операторе return ([stmt.return]) является (возможно, в скобках) id-выражением, которое называет объект с автоматическим временем хранения, объявленным в теле или параметром-объявлением-предложением самой внутренней охватывающей функции или лямбда-выражения, или же
  • ...

разрешение перегрузки для выбора конструктора для копии сначала выполняется так, как если бы объект был обозначен rvalue.

Ответ 2

Это своего рода бит-init-список. [dcl.init.list]/1.3

Чтобы быть более конкретным, это " expr-or-braced-init-list [dcl.init]/1

заявления о возврате " [stmt.return]/2

Оператор return с любым другим операндом должен использоваться только в функции, тип возврата которой не является vv void; оператор return инициализирует результат glvalue или объект результата prvalue (явный или неявный) вызов функции путем инициализации копии из операнда.

С этого момента позвольте мне ответить xskxzr, что упоминание [class.copy.elision]/3

В следующих контекстах копирования-инициализации вместо операции копирования может использоваться операция перемещения:

  • Если выражение в операторе return ([stmt.return]) является (возможно, в скобках) id-выражением, которое называет объект с автоматическим временем хранения, объявленным в теле или параметром-объявлением-предложением самой внутренней охватывающей функции или лямбда-выражения, или же

В нормальных человеческих словах причина, что копия вызывается вместо перемещения, потому что бит-init-list вызывает u который оказался lvalue.

Таким образом, вы можете знать, если бит-init-list вызывает u то есть rvalue...

return {std::move(u)};

Ну, u перемещается в новое значение UserName и копирует работу elision сразу после.

Таким образом, это занимает один ход, как в

return u;

godbolt.org/g/b6stLr

wandbox.org/permlink/7u1cPc0TG9gqToZD

#include <iostream>
#include <optional>

struct UserName
{
  int x;
  UserName() : x(0) {};
  UserName(const UserName& other) : x(other.x) { std::cout << "copy " << x << "\n"; };
  UserName(UserName&& other)      : x(other.x) { std::cout << "move "  << x << "\n"; };
};

std::optional<UserName> CreateUser()
{
  UserName u;
  return u;   // this one moves UserName
}

std::optional<UserName> CreateUser_listinit()
{
  UserName u;
  auto whatever{u};
  return whatever;
}

std::optional<UserName> CreateUser_listinit_with_copy_elision()
{
  UserName u;
  return {u};
}

std::optional<UserName> CreateUser_move_listinit_with_copy_elision()
{
  UserName u;
  return {std::move(u)};
}

int main()
{
  std::cout << "CreateUser() :\n";
  [[maybe_unused]] auto d = CreateUser();

  std::cout << "\nCreateUser_listinit() :\n";
  [[maybe_unused]] auto e = CreateUser_listinit();

  std::cout << "\nCreateUser_listinit_with_copy_elision() :\n";
  [[maybe_unused]] auto f = CreateUser_listinit_with_copy_elision();

  std::cout << "\nCreateUser_move_listinit_with_copy_elision() :\n";
  [[maybe_unused]] auto g = CreateUser_move_listinit_with_copy_elision();
}

Распечатать

CreateUser() :
move 0

CreateUser_listinit() :
copy 0
move 0

CreateUser_listinit_with_copy_elision() :
copy 0

CreateUser_move_listinit_with_copy_elision() :
move 0

Ответ 3

return {arg1, arg2,...};

инициализация списка копий. объект (return) инициализируется из списка инициализаторов путем инициализации копирования для инициализации списка копий