Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why does the following code not invoke std::string's move constructor?

Tags:

c++

c++11

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

like image 423
Bwmat Avatar asked Apr 20 '26 00:04

Bwmat


1 Answers

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);
like image 69
Michael Burr Avatar answered Apr 21 '26 13:04

Michael Burr