2

Is it ok that the following code

#include <iostream>

struct Foo
{
    Foo() { std::cout << "Foo::Foo" << std::endl; }
    ~Foo() { std::cout << "Foo::~Foo" << std::endl; }

    Foo(const Foo&) { std::cout << "Foo::Foo(const Foo&)" << std::endl; }
    Foo& operator=(const Foo&) { std::cout << "Foo::operator=(const Foo&)" << std::endl; return *this; }

    Foo(Foo&&) { std::cout << "Foo::Foo(Foo&&)" << std::endl; }
    Foo& operator=(Foo&&) { std::cout << "Foo::operator=(Foo&&)" << std::endl; return *this; }
};

Foo foo()
{
    Foo second;
    return second;
}

int main()
{
    foo();
}

produces such output:

Foo::Foo
Foo::Foo(Foo&&)
Foo::~Foo
Foo::~Foo

Why does it call move constructor instead of the copy constructor?

4
  • Related Commented Aug 11, 2017 at 1:34
  • which compiler do you use and what are compiler arguments? Commented Aug 11, 2017 at 1:44
  • @Dev Null MSVC-14.0 Commented Aug 11, 2017 at 1:45
  • 3
    I suspect that optimisation is turned off in your settings when you compile because I would expect move constructor and one of destructor calls to be optimised away due to RVO. Commented Aug 11, 2017 at 1:51

2 Answers 2

3

The answer is simply because the standard says so. Section 12.8, paragraph 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.

And that's basically all there is to it. Logically, if the variable has automatic storage duration, and you return it, then immediately after the return, the variable will not exist any more when the local scope cleans up. That is why, even though it is an lvalue, it is quite similar to an rvalue. Because it's still technically an lvalue, this exception has to be made explicitly in the standard, and here we are. Note that this whole situation is specific to returning from a function, and does not include scope ending in general.

Sign up to request clarification or add additional context in comments.

1 Comment

The standard also says the return is a candidate for copy elision. It is very strange that RVO doesn't take place here.
2

second is returned from foo, and, being a local object, is treated as an rvalue (thanks to Nir Friedman for the correct explanation). The returned Foo object is constructed from a temporary, which binds to the move constructor.

Modern compiler should skip the move constructor call and use copy elision (demo).

6 Comments

Why does it created from a temporary? I thought that the second is an l-value here
because second goes out of scope and can't be assigned (i.e. be at the left side of =) anymore.
But it is still in the scope during the return statement, isn't it?
yes, but after return it's no longer in scope, a Foo object in scope of main is created from a temporary object that is no longer in scope as foo has finished.
Ok, I see. Thanks
|

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.