Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

When does returning a value outside of a function uses move vs copy?

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?

like image 816
Arnaud Avatar asked May 28 '14 14:05

Arnaud


1 Answers

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
}
like image 70
MWid Avatar answered Nov 17 '22 09:11

MWid