17

So, this provides the intended output:

void f(std::string&& s)
{
   s += " plus extra";
}

int main(void)
{
   std::string str = "A string";
   f( std::move(str) );
   std::cout << str << std::endl;

   return 0;
}

A string plus extra

That is, it works when I run it on Ideone, but is it UB? Adding extra string initializations before and after the call to f didn't change anything.

Matt
  • 223
  • 1
  • 8
  • 2
    Yes it will still be valid but what it contains is *undefined*. The only methods you should call on the string after *moving* from it are to replace its contents with something new. For example `str.clear();`. – Galik Mar 25 '16 at 18:56
  • 1
    Related: [What can I do with a moved-from object?](http://stackoverflow.com/q/7027523/3425536) – Emil Laine Mar 25 '16 at 18:57
  • 4
    The string isn't moved, an rvalue reference is sent and the ownership isn't took away. This code is completely valid. No UB is involved here and no _undefined content_, or whatever that means, is involved here. – Guillaume Racicot Mar 25 '16 at 18:58
  • 4
    That example doesn't actually move the string btw. – Galik Mar 25 '16 at 18:59
  • 1
    It doesn't have to, but in general, when I see a function that accepts an rvalue reference, I expect it to consume the object. – zneak Mar 25 '16 at 19:02
  • @GuillaumeRacicot Thank you, but how do you know the string isn't moved? Is this because `s` is never assigned to anything within the function body? – Matt Mar 25 '16 at 19:03
  • 1
    Yes, because you directly receive the reference and you do not make any operation that would end up invalidating the string. – Guillaume Racicot Mar 25 '16 at 19:12

4 Answers4

18

It is valid, not UB.

It is also horribly obfuscated code. std::move(s) is nothing but a cast to rvalue. By itself it does not actually generate any code at all. Its only purpose is to turn an lvalue into an rvalue so that client code can overload on lvalue/rvalue expressions (of string in this case).

You should pass by lvalue-reference for this case:

void f(std::string& s)
{
   s += " plus extra";
}
...
f( str );

Or alternatively, pass by value and return a new string:

std::string f(std::string s)
{
   s += " plus extra";
   return s;
}
...
str = f( std::move(str) );
Howard Hinnant
  • 206,506
  • 52
  • 449
  • 577
10

std::move doesn't move anything. It indicates that an object may be "moved from", by casting its argument to an rvalue.

Your code is valid and there is no move operation performed.

You would get the behavior where the state of str is unspecified after calling f(), if you move-construct another string object from s. The move-constructor performs the actual move operation.

Example:

std::vector<std::string> sv;

void f(std::string&& s)
{
    s += " plus extra";
    sv.push_back(std::move(s));         // move s (str) into a new object
}

int main(void)
{
   std::string str = "A string";
   f(std::move(str));                 
   std::cout << str << std::endl;       // output: empty string
   std::cout << sv.back() << std::endl; // output: "A string plus extra"

   return 0;
}
sergej
  • 17,147
  • 6
  • 52
  • 89
  • Might also want to mention copy elision, and how `&&` is largely unnecessary outside move constructors/move assignment operators. – Kevin Mar 26 '16 at 01:56
9

The code is valid, because there no actual moving is performed. Here is how you could make it invalid:

string f(std::string&& s) {
    std::string res(std::move(s));
    res += " plus extra";
    return res;
}

The state of str after this call would be valid, but unspecified. This means that you could still assign a new value to str to put it back into a valid state, but you wouldn't be able to output it without invoking unspecified behavior (demo). See this Q&A for specifics on the moved-from state.

Community
  • 1
  • 1
Sergey Kalinichenko
  • 714,442
  • 84
  • 1,110
  • 1,523
3

std::move is just casting your object to an rvalue reference. Since your function takes the reference and just do something with it, no ownership is taken here, so your string is still in a valid state and can be used safely.

I would not recommend using this in your code because it's misleading, as a lot of people would consider your string as invalid, because taking ownership is the primary use of rvalue reference, hence std::move.

If you're really need to call this function this way, I would recommend writing this:

std::string myString{"a string"};

// leave a comment here to explain what you are doing.
f(static_cast<std::string&&>(myString));

However, please note that your example would be really different if the function f took value instead of a reference. In that case, calling it with both std::move or static_cast would invalidate the string.

Guillaume Racicot
  • 39,621
  • 9
  • 77
  • 141