2

The following code, compiled on VS2013, never invokes std::string's move constructor (checked via setting breakpoints, the const ref copy constructor is invoked instead.

#include <iostream>
#include <string>
#include <stdlib.h> /* srand, rand */
#include <time.h> /* time */

struct foo
{
    foo(std::string& str1, std::string& str2) : _str1(str1), _str2(str2) {}

    ~foo() { std::cout << "Either \"" << _str1 << "\" or \"" << _str2 << "\" was returned." << std::endl; }

    std::string& _str1;
    std::string& _str2;
};

std::string foobar()
{
    std::string str1("Hello, World!");
    std::string str2("Goodbye, cruel World.");
    foo f(str1, str2);

    srand(time(NULL));

    return (rand() % 2) ? str1 : str2;
}

int main()
{
    std::cout << "\"" << foobar() << "\" was actually returned." << std::endl;

    return EXIT_SUCCESS;
}

I would expect the return statement in foobar() to invoke the move constructor since I'm returning a local (the rand() is to prevent NRVO), like stated as answers to questions such as Returning std::move of a local variable

The context of this is that I'm trying to add another example for my other question here: https://softwareengineering.stackexchange.com/questions/258238/move-semantics-in-c-move-return-of-local-variables

Community
  • 1
  • 1
Bwmat
  • 4,314
  • 3
  • 27
  • 42
  • Does it use the move constructor if you remove the `foo` variable definition? I would guess the compiler is smart enough to know you've taken a reference to the strings which makes a move invalid (again, a guess). – uesp Oct 07 '14 at 18:16
  • No, it still uses the copy constructor if I do that. – Bwmat Oct 07 '14 at 18:18
  • 1
    The "try rvalue first" for returns applies only "if the copy elision criteria are met or would be met save for the fact that the source object is a function parameter". In C++14 it's extended to all cases where the return statement names a local object directly. (See [this answer](http://stackoverflow.com/questions/25875596/can-returning-a-local-variable-by-value-in-c11-14-result-in-the-return-value-b/25876175#25876175) for the full quote from the standard.) Your conditional expression is neither. – T.C. Oct 07 '14 at 18:18
  • 1
    You say you're returning a local. You're not. `(rand() % 2) ? str1 : str2` is not a local. –  Oct 07 '14 at 18:19
  • Debug (non-optimized) or Release (optimized) build? – Johann Gerell Oct 07 '14 at 18:20
  • 1
    @T.C. - That was it, changing it to if/else instead of ternary operator made it use the move-constructor. I was being a bit lazy it seems (although it seems like it shouldn't have made a difference, semantically) – Bwmat Oct 07 '14 at 18:22
  • This is clearly related to http://stackoverflow.com/questions/22078029/why-does-the-ternary-operator-prevent-return-value-optimization, i.e. it is not enough to always explain behavior with the standard only. – user2672165 Oct 07 '14 at 18:57

1 Answers1

5

C++11 has a special case to allow for copy/move ellision when it's a local variable and is used as the return expression from a function:

C++11 12.8/31 "Copying and moving class objects":

in a return statement in a function with a class return type, when the expression is the name of a non-volatile automatic object (other than a function or catch-clause parameter) with the same cv-unqualified type as the function return type, the copy/move operation can be omitted by constructing the automatic object directly into the function’s return value

But this case for copy elision is not met because the return statement you have is not simply "the name of a non-volatile automatic object".

Later, the standard mentions that

C++11 12.8/32 "Copying and moving class objects":

When the criteria for elision of a copy operation are met or would be met save for the fact that the source object is a function parameter, and the object to be copied is designated by an lvalue, overload resolution to select the constructor for the copy is first performed as if the object were designated by an rvalue. If overload resolution fails, 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 ]

This allows the move operation to be used even when the return specifies an lvalue. However, this special case only applies under the conditions of the first sentence, which are not met in the case of of your example return statement.

You can force the issue:

return (rand() % 2) ? std::move(str1) : std::move(str2);
Michael Burr
  • 333,147
  • 50
  • 533
  • 760