Возвращается ли подпоследовательный объект временного объекта по возврату?

#include <string>
#include <vector>

using namespace std;

auto f()
{
    vector<string> coll{ "hello" };

    //
    // Must I use move(coll[0]) ?
    //
    return coll[0]; 
}

int main()
{
    auto s = f();
    DoSomething(s);
}

Я знаю: если я просто return coll;, то coll гарантированно будет возвращен.

Однако я не уверен: Возможно ли, что coll[0] будет перемещено по возврату?

Update:

#include <iostream>

struct A
{
    A() { std::cout << "constructed\n"; }
    A(const A&) { std::cout << "copy-constructed\n"; }
    A(A&&) { std::cout << "move-constructed\n"; }
    ~A() { std::cout << "destructed\n"; }
};

struct B
{
    A a;
};

A f()
{
    B b;
    return b.a;
}

int main()
{
    f();
}

gcc 6.2 и clang 3.8 выводит то же самое:

построен

копия возведенных

разрушаются

разрушаются

Ответ 1

Чистая формулировка правила "неявного перемещения" находится в [class.copy.elision]/3 текущего рабочего документа:

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

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

  • [...]

разрешение перегрузки для выбора конструктора для копии сначала выполняются так, как если бы объект был обозначен rvalue. Если первый ошибка перегрузки не выполняется или не выполнялась, или если тип первый параметр выбранного конструктора не является ссылкой rvalue к типу объекта (возможно, с квалификацией cv), разрешение перегрузки выполняется снова, считая объект как lvalue.

Ни b.a, ни coll[0] не является id-выражением. Поэтому нет никакого неявного движения. Если вы хотите двигаться, вам нужно будет сделать это явно.

Ответ 2

При возврате локального объекта не будет использоваться ни копия, ни перемещение, а копия elision, которая предпочтительнее перемещаться. Это связано с тем, что правила, регулирующие копирование и перемещение локальных объектов, одинаковы. Вместо этого принудительно перемещаясь, явно используя std::move, как в

template<typename T>
std::string make_string(T const& x)
{
  std::ostringstream str;
  str << x
  return std::move(str.str());    // not recommended
}

последние версии clang выдают предупреждение

перемещение временного объекта предотвращает копирование elision [-Wpessimizing-move]

Однако ситуация в вашем коде отличается. В отличие от std::ostringstream::str(), который возвращает объект (a std::string), std::vector<>::operator[], возвращает ссылку, которая должна быть преобразована в объект (поскольку auto удаляет ссылки). В этом случае исключение копирования невозможно (поскольку фактический объект является частью другого объекта с нетривиальным деструктором), а std::move() следует использовать, чтобы избежать копирования.

Эти соображения предполагают использовать std::move(), если не уверены, но удалите его, если проблемы с clang выше предупреждения.