8

After reading this question. I created this small little test:

class A{
public:
    A(){}
    A(const A&){printf("copy\n");}
    A(A&&){printf("move\n");}

    static A f(){
        A a;
        return a;}

    static A g(){
        A a;
        return (a);}//could be return *&a; too.

    static A h(){
        A a;
        return true?a:a;}

 };

The result is (without RVO and NRVO):

  • f uses move
  • g uses move
  • h uses copy

As far as I know the rules used to decide whether to use copy or move are described in 12.8.32:

  • 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. ...

Which refers to the rules of 12.8.31: (I only show the relevant part)

  • 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 cvunqualified 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
  • when a temporary class object that has not been bound to a reference (12.2) would be copied/moved to a class object with the same cv-unqualified type, the copy/move operation can be omitted by constructing the temporary object directly into the target of the omitted copy/move

Following these rules I understand what happens for f and h:

  • The copy in f is eligible for elision so it is moved. (cf. the bold part)
  • The copy in h is not eligible for elision so it is copied.

What about g?

To me it looks really like h. I am returning an expression which is not the name of an automatic object and as such I thought it would be copied however it is moved. What is going on here?

Community
  • 1
  • 1
Arnaud
  • 3,765
  • 3
  • 39
  • 69
  • `f` and `g` are identical. I would think `h` should move also, what compiler are you using? When you return a local it is supposed to move (in worst case, or RVO/NRVO in best case) – David May 28 '14 at 14:23
  • @Dave I am using Visual Studio 2013. f and g seems identical but I cannot understand why. For h, the standard requires a copy (cf. the answer of Howard Hinnant to the linked question) – Arnaud May 28 '14 at 14:26
  • 3
    §5.1p6: "A parenthesized expression is a primary expression whose type and value are identical to those of the enclosed expression. The presence of parentheses does not affect whether the expression is an lvalue. The parenthesized expression can be used in exactly the same contexts as those where the enclosed expression can be used, and with the same meaning, except as otherwise indicated." – MWid May 28 '14 at 14:29
  • [Similar](http://stackoverflow.com/questions/22078029/why-does-the-ternary-operator-prevent-return-value-optimization), unfortunately that one also does not have an adequate answer. – jliv902 May 28 '14 at 14:53
  • @Arnaud Why are you expecting g to copy? See MWid's comment – David May 28 '14 at 14:59
  • @Dave I was not aware of that rule. – Arnaud May 28 '14 at 15:00
  • @MWid I think this is the right answer and if you care to write an answer instead of a comment I would accept it. – Arnaud May 28 '14 at 15:01
  • Is there any technical reason why `h`'s rv cannot be moved? (I mean, why does the standard say that it can't). – M.M May 28 '14 at 15:24
  • @MattMcNabb I suppose we need to look at this the other way round. The standard defines common usages where elision should occure (returning a temporary objects or a named automatic object). This is a corner case overlooked by the standard. Maybe someone will propose a standard modification for this scenario. – Arnaud May 28 '14 at 15:32

2 Answers2

11

In most cases there is no difference in writing a or (a). The relevant part of the spec is §5.1p6 (emphasis mine):

A parenthesized expression is a primary expression whose type and value are identical to those of the enclosed expression. The presence of parentheses does not affect whether the expression is an lvalue. The parenthesized expression can be used in exactly the same contexts as those where the enclosed expression can be used, and with the same meaning, except as otherwise indicated.

Hence, the same reasoning applies to the return value of your function g as you have given for f.


In the upcomming standard C++14 this has been clarified §12.8p32 (emphasis mine):

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.


For those who want to know, when the parentheses matter, here is an example:

namespace N {
struct S { };

  void f(S);

}

void g() {
  N::S s;
  f(s); // OK: calls N::f
  (f)(s); // error: N::f not considered; parentheses
          // prevent argument-dependent lookup
}
MWid
  • 4,429
  • 3
  • 20
  • 20
0

Note that if you declare

const A a;

in your examples, they will all copy. Section 12.8 of the standard says "overload resolution to select the constructor for the copy is first performed as if the object were designated by an rvalue," but if a is const, that will be a const rvalue, which doesn't match the move constructor.

Mark Gilbert
  • 73
  • 1
  • 5