6

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
phön
  • 1,215
  • 8
  • 20
  • This surprises me. I thought C++17 requires copy elision and a compliant implementation is not allowed to print `move constructor`. – nwp Feb 12 '18 at 15:10
  • Looks like gcc understands `(a)` to not be the name of the `object`. Maybe tag it as [tag:language-lawyer]. – nwp Feb 12 '18 at 15:17
  • @Someprogrammerdude there is a difference, but i guess it does not matter in the usual day-to-day programming. for example https://stackoverflow.com/a/25615981/3783662 . the use case comes in with `decltype(auto)` – phön Feb 12 '18 at 15:20
  • @nwp yeah i was surprised too, but maybe there is a special (conversion) rule for parenthesized expressions. i recognized right now the first compiler is only c++14. – phön Feb 12 '18 at 15:23

3 Answers3

7

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

  1. in a return statement
  2. in a function
  3. with a class return type
  4. 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]))
  5. 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.

Community
  • 1
  • 1
nwp
  • 9,623
  • 5
  • 38
  • 68
5

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.

Community
  • 1
  • 1
xskxzr
  • 12,442
  • 12
  • 37
  • 77
  • but why does it not meet the criteria? just because the name is in the parentheses? is this just a plain rule? somewhere i read that the evaluation of the expression will lead to a lvalue, but i dont know about this one. seems strange to me in the end – phön Feb 13 '18 at 08:04
2

(Author of P1155 "More Implicit Move" and P2266 "Simpler Implicit Move" here.)

Short answer: This was a GCC bug. It's fixed now (between GCC 11 and GCC 12).

The language-lawyer answer is probably that it remains unclear, on paper, whether return (x); is supposed to trigger copy elision or not. But in practice I think all vendors are on the same page now.

All [class.copy.elision] says is, "...when the expression is the name of a non-volatile object..." You might think that clearly the name of an object cannot contain parentheses — but, on the other hand, if we really wanted to say "literally the identifier that names a variable," we have a very well-established term for that: we'd say "...when the expression is an id-expression naming a non-volatile object..." And we didn't say that.

Meanwhile, as we cleaned up the wording of "implicit move" in C++20 and again in C++23, we were more painstaking about it. [expr.prim.id.unqual]/4 now (in C++23) says:

In the following contexts, an id-expression is move-eligible:

  • (4.1) If the id-expression (possibly parenthesized) is the operand of a return or co_return statement...
  • (4.2) if the id-expression (possibly parenthesized) is the operand of a throw-expression...

So this means that return (x); 100% for sure undergoes "implicit move." And since vendors know that, they're in the right frame of mind to understand that it should also undergo copy elision when possible.

Quuxplusone
  • 23,928
  • 8
  • 94
  • 159