Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Is the sub-object of a temporary object guaranteed to be moved on return?

#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);
}

I know: If I just return coll;, then coll is guaranteed to be moved on return.

However, I am not sure: Whether coll[0] is also guaranteed to be moved on return?

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 and clang 3.8 outputs the same:

constructed

copy-constructed

destructed

destructed

like image 765
xmllmx Avatar asked Jan 07 '17 08:01

xmllmx


2 Answers

When returning a local object, neither a copy nor a move will be used, but copy elision, which is to be preferred over moving. This is because the rules governing copy elision and moving of local objects are the same. When instead forcing a move by explicitly using std::move as in

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

recent versions of clang issue a warning

moving a temporary object prevents copy elision [-Wpessimizing-move]

However, the situation in your code is different. Unlike std::ostringstream::str(), which returns an object (a std::string), std::vector<>::operator[], returns a reference, which must be converted to an object (since auto removes references). In this case, copy elision is not possible (because the actual object is part of another object with non-trivial destructor) and std::move() should be used to avoid a copy.

These considerations suggest to use std::move() if unsure, but remove it if clang issues above warning.

like image 194
Walter Avatar answered Oct 24 '22 01:10

Walter


The cleanest formulation of the "implicit move" rule is in [class.copy.elision]/3 of the current working paper:

In the following copy-initialization contexts, a move operation might be used instead of a copy operation:

  • If the expression in a return statement ([stmt.return]) is a (possibly parenthesized) id-expression that names an object with automatic storage duration declared in the body or parameter-declaration-clause of the innermost enclosing function or lambda-expression, or

  • [...]

overload resolution to select the constructor for the copy is first performed as if the object were designated by an rvalue. If the first overload resolution fails or was not performed, or if the type of the first parameter of the selected constructor is not an rvalue reference to the object's type (possibly cv-qualified), overload resolution is performed again, considering the object as an lvalue.

Neither b.a nor coll[0] is an id-expression. Therefore, there is no implicit move. If you want a move, you'll have to do it explicitly.

like image 3
T.C. Avatar answered Oct 24 '22 03:10

T.C.