8

Say we have an utility function:

std::string GetDescription() { return "The description."; }

Is it OK to return the string literal? Is the implicitly created std::string object copied?

I thought about always returning it like this:

std::string GetDescription() { return std::move(std::string("The description.")); }

But it's of course longer and more verbose. We could also assume that compiler RVO will help us a bit

std::string GetDescription() { return std::string("The description."); }

Yet still, I don't know what it really has to do, instead of what can it do.

Bartek Banachewicz
  • 38,596
  • 7
  • 91
  • 135

2 Answers2

13
std::string GetDescription() { return "XYZ"; }

is equivalent to this:

std::string GetDescription() { return std::string("XYZ"); }

which in turn is equivalent to this:

std::string GetDescription() { return std::move(std::string("XYZ")); }

Means when you return std::string("XYZ") which is a temporary object, then std::move is unnecessary, because the object will be moved anyway (implicitly).

Likewise, when you return "XYZ", then the explicit construction std::string("XYZ") is unnecessary, because the construction will happen anyway (implicitly).


So the answer to this question:

Is the implicitly created std::string object copied?

is NO. The implicitly created object is after all a temporary object which is moved (implicitly). But then the move can be elided by the compiler!

So the bottomline is this : you can write this code and be happy:

std::string GetDescription() { return "XYZ"; }

And in some corner-cases, return tempObj is more efficient (and thus better) than return std::move(tempObj).

Nawaz
  • 353,942
  • 115
  • 666
  • 851
  • Um.. really? Why's that then? Where does the `std::move` come from? – Lightness Races in Orbit Dec 13 '12 at 10:22
  • 2
    The `move` is unnecessary, the constructed string is a temporary. – Luc Touraille Dec 13 '12 at 10:23
  • 3
    The requirement for the move is that whenever copy elision is permitted, C++11 *requires* that the compiler use a move constructor if there is one. That move can then be elided (just like the copy can be) at the compiler's choice. And of course in a sensible compiler it will be, so most likely the answer to "is the temporary copied" and "is the temporary moved" are both "no". Nominally it is moved, actually the move is elided and the return value is constructed using the literal. – Steve Jessop Dec 13 '12 at 10:28
  • 1
    @SteveJessop: But *semantically* it is *moved* (even if it is elided), isn't it? – Nawaz Dec 13 '12 at 10:30
  • 3
    @Nawaz: well, I say "nominally moved" rather than "semantically moved". The semantics for some classes are different according to whether the move is actually elided or not (if the move constructor has side-effects). But I absolutely agree with you that in C++11, it is not permitted to be copied if the type has a move constructor (which string does). So the thing that's eligible for elision is a move not a copy. – Steve Jessop Dec 13 '12 at 10:33
  • @SteveJessop: I think *"nominally moved"* is a better terminology. Thanks. :-) – Nawaz Dec 13 '12 at 10:37
  • 1
    A second issue, as I said under the other answer I don't believe `return std::move(std::string("XYZ"));` really is equivalent to `return std::string("XYZ");` because the eligibility for elision is different. It's equivalent at what I'm calling the "nominal" stage of analysis, prior to elision :-) – Steve Jessop Dec 13 '12 at 10:42
  • @SteveJessop: It is functionally equivalent, isn't? If one of them can be elided, then the other too can be elided, right? – Nawaz Dec 13 '12 at 10:43
  • @Nawaz: now you're introducing another new terminology without precise definition ;-) In general, if copy/move elision is disabled then it is equivalent. But if it's enabled then specifying `std::move` suppresses the elision. If that's what you mean by "functionally equivalent", then OK. For `string` in particular the move constructor has no side-effects anyway. So I guess what I haven't accounted for is the fact that the compiler could perform the same optimization under the "as-if" rule even though move elision doesn't apply. So for `string` it's equivalent, no qualifications. – Steve Jessop Dec 13 '12 at 10:45
  • 3
    @Nawaz: btw, the reason that `std::move` suppresses the elision is that elision is permitted from a temporary *if no reference has been taken to the temporary*. But `move` does take a reference to the temporary. So under certain circumstances `move` is a pessimization (if you apply it to an object that's expensive to move, and that would have been elided had you left it alone). Not that `string` is expensive to move. – Steve Jessop Dec 13 '12 at 10:50
  • @SteveJessop: That is short yet excellent explanation. Thanks once again. So it is better to write `return "XYZ"` than `return std::move(std::string("XYZ"))`. – Nawaz Dec 13 '12 at 10:54
  • @Steve: Thanks. I wasn't aware that C++11 mandated the move ctor be invoked in scenarios where copy elision is permitted. It's nice for this area of the language to finally get some sensible semantics! – Lightness Races in Orbit Dec 13 '12 at 11:33
  • @LightnessRacesinOrbit: the language for the exciting part is something like, "if copy constructor elision is permitted then a copy from an lvalue will first look for a constructor overload that matches an rvalue", but I can't remember where it is. In non-exciting cases like this, the temporary matches the rvalue constructor anyway. – Steve Jessop Dec 13 '12 at 12:34
1

Is it OK to return the string literal? Is the implicitly created std::string object copied?

It is OK. What you get, is the (implicit) constructor for std::string, creating a local copy, returned then as a rvalue reference. Taking the result in client code into a string, will set that string from an rvalue reference.

If you use the second piece of code, you "say too much". The code is correct, and they are (almost) equivalent (they should be equivalent, but the optimizations that the compiler is permitted to perform in the first case are better*).

I would go for:

std::string GetDescription() { return std::string("The description."); }

This way it is explicit that you return a string, and the code is (almost) minimal: you rely on the std::string move-construction.

*) edited accordingly, after comment by @SteveJessop.

utnapistim
  • 26,809
  • 3
  • 46
  • 82
  • "they should be equivalent, but it may be that the optimizations the compiler could perform in the first case are better" -- I believe you're right, the optimizations the compiler is *permitted* to perform are better without the `move`. If you put the `std::move` in explicitly then I think that the temporary is no longer eligible for elision. So "should be equivalent" is "the standard should allow move elision in that case". – Steve Jessop Dec 13 '12 at 10:38