2

I have a object whose copy operation would be too slow so I decided to delete it and force users to only move. A copy of this object wound't make much sense anyway. But then I have this function:

Object loadFromFile(const std::string& name) {
    Object obj;
    ...
    return obj;
}

Even though copy elision happens here and no copy constructor is called, this fails to compile because a copy constructor is required to exist and be accessible. This is my second attempt:

Object&& loadFromFile(const std::string& name) {
    Object obj;
    ...
    return std::move(obj);
}

This compiles. Yay!

But a new problem surges when trying to use it:

Object x = loadFromFile("test.txt");

This again requires a copy constructor. I couldn't get it to work even explicitly using move:

Object x = std::move(loadFromFile("test.txt"));

The only solution I came was:

const Object& x = loadFromFile("test.txt");

But x has to be non-const as it is going to be altered later.

How to deal with it?

Guilherme Bernal
  • 8,183
  • 25
  • 43
  • 1
    "A copy of this object wound't make much sense anyway." judging by the way you use it, this doesn't look true. – Luchian Grigore Feb 25 '15 at 12:04
  • 2
    Wait - won't the first example treat `obj` as an rvalue and so a copy constructor is *not* required? [See here](http://ideone.com/g4rAnN). Looks like your question is based on a problem that doesn't exist. – Joseph Mansfield Feb 25 '15 at 12:05
  • Works correctly here: https://ideone.com/Ex81gj – Jarod42 Feb 25 '15 at 12:17
  • When you delete copy constructor and want object to be moved, please implement *move constructor* yourself. – GreenScape Feb 25 '15 at 13:32

3 Answers3

3

This one is wrong:

Object&& loadFromFile(const std::string& name) {
    Object obj;
    ...
    return std::move(obj);
}

You are returning a reference to a local variable, and this is undefined behaviour. The object dies and what you return is a reference to your nose, so demons can come out of it.

The second one is right:

Object loadFromFile(const std::string& name) {
    Object obj;
    ...
    return obj;
}

Indeed, in this case, lookup is performed first as if obj was an rvalue (Standard 12.8.32):

When the criteria for elision of a copy/move operation are met, but not for an exception-declaration, and the object to be copied is designated by an lvalue, or when the expression in a return statement 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, 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. [ Note: This two-stage overload resolution must be performed regardless of whether copy elision will occur. It determines the constructor to be called if elision is not performed, and the selected constructor must be accessible even if the call is elided. —end note ]

And move-constructor Object(Object&&) should be selected.

Laurent LA RIZZA
  • 2,905
  • 1
  • 23
  • 41
  • And it not only "works" by coincidence, but it must work, since `obj` is a non-volatile automatic object of the same cv-qualified type as the return type. It fulfills all criteria for requiring the compiler to construct it in-situ of the object that takes the function's return value, and the compiler is even allowed to do that when a move (or copy) constructor is present that has observable side effects. – Damon Feb 25 '15 at 13:02
  • 2
    `std::move(obj)` is a pessimization (it inhibits RVO), and the second version will move anyway, because in that context the compiler must first perform overload resolution treating `obj` as an rvalue. @Damon copy elision is never mandatory. – T.C. Feb 25 '15 at 13:33
  • Indeed : Standard / 12.7.31 and 32. I learned something today. – Laurent LA RIZZA Feb 25 '15 at 14:48
2

Ops, my mistake, sorry.

I deleted the copy constructor, but didn't actually implement a move one assuming it would already be there. Creating a move constructor solves the issue.

Thanks for the light @Joseph Mansfield.

Guilherme Bernal
  • 8,183
  • 25
  • 43
0

Can you pass it as an output parameter? Something like:

void loadFromFile(const std::string& name, Object& obj) {
    //Operations on obj
}
Matteo Umili
  • 3,412
  • 1
  • 19
  • 31