问题
i was fooling around with the following code and got different results using my visual studio 2017 application and two different online compilers. in release mode visual studio does elide the copy/move in both cases, while the two online compilers just do it in case of the unparenthesized return statement. my question is: who is right and more importantly what are the underlaying rules. (i know you can use the parentheses in conjunction with the decltype(auto)
syntax. but this is not the current usecase).
example code:
#include <iostream>
#include <cstdio>
struct Foo
{
Foo() { std::cout << "default constructor" << std::endl; }
Foo(const Foo& rhs) { std::cout << "copy constructor" << std::endl; }
Foo(Foo&& rhs) { std::cout << "move constructor" << std::endl; }
Foo& operator=(const Foo& rhs) { std::cout << "copy assignment" << std::endl; return *this; }
Foo& operator=(Foo&& rhs) { std::cout << "move assignment" << std::endl; return *this; }
};
Foo foo_normal()
{
Foo a{};
return a;
}
Foo foo_parentheses()
{
Foo a{};
return (a);
}
int main()
{
auto a = foo_normal();
auto b = foo_parentheses();
std::getchar();
}
online compiler 1: http://cpp.sh/75bux
online compiler 2: http://coliru.stacked-crooked.com/a/c266852b9e1712f3
the output for visual studio in release mode is:
default constructor
default constructor
in the two other compilers the output is:
default constructor
default constructor
move constructor
回答1:
GCC is right.
According to [class.copy.elision] paragraph 1:
This elision of copy/move operations, called copy elision, is permitted in the following circumstances (which may be combined to eliminate multiple copies):
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 parameter or a variable introduced by the exception-declaration of a handler ([except.handle])) with the same type (ignoring cv-qualification) as the function return type, the copy/move operation can be omitted by constructing the automatic object directly into the function call's return object...
Parenthesized expression in return
statement does not meet the criteria for copy elision.
In fact, until the resolution of CWG 1597, parenthesized id-expression in return
statement cannot even be considered as an rvalue to perform a move.
回答2:
This is the relevant quote from the standard:
This elision of copy/move operations, called copy elision, is permitted in the following circumstances (which may be combined to eliminate multiple copies):
(1.1) - 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 parameter or a variable introduced by the exception-declaration of a handler ([except.handle])) with the same type (ignoring cv-qualification) as the function return type, the copy/move operation can be omitted by constructing the automatic object directly into the function call's return object
So the requirements are
- 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 parameter or a variable introduced by the exception-declaration of a handler ([except.handle]))
- with the same type (ignoring cv-qualification) as the function return type
I would argue that requirements 1, 2, 3 and 5 are fulfilled, but requirement 4 is not. (a)
is not the name of an object. Therefore for the given code copy-elision does not apply. Since the move-constructor has side-effects it also cannot be elided under the as-if rule.
Therefore gcc is right and visual studio (and clang) are wrong here.
来源:https://stackoverflow.com/questions/48749440/c-nrvo-copy-elision-with-return-statement-in-parentheses