Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why does the following code not result in moving the object instead of copying?

#include <iostream>
#include <time.h>

class A
{
public:
   A() { std::cout << "a ctor\n"; }
   A(const A&) { std::cout << "a copy ctor\n"; }
   A(A&&) { std::cout << "a move ctor\n"; }
};

A f(int i)
{
   A a1;
   return i != 0 ? a1 : A{};
}

int main()
{
   srand(time(0));
   f(rand());
   return 0;
}

The output is:

a ctor

a copy ctor

I would expect that a1 in f() will be moved not copied. If I change f() just a little bit, it's not a copy anymore but a move:

A f(int i)
{
   A a1;
   if (i != 0)
   {
      return a1;
   }
   return A{};
}

The output is:

a ctor

a move ctor

Could you explain me how does this work? GCC 9.3.0

(edit: added random number to prevent RVO)

like image 457
Peresztegi Péter Avatar asked Jun 27 '20 14:06

Peresztegi Péter


1 Answers

The difference you're seeing is due to the use of the ternary/conditional operator. The ternary operator determines a common type and value category for its second and third operands, and that is determined at compile time. See here.

In your case:

return i != 0 ? a1 : A{};

the common type is A and the common value category is prvalue since A{} is an prvalue. However, a1 is an lvalue and a prvalue temporary copy of it will have to be made in the lvalue-to-rvalue conversion. This explains why you see the copy constructor invoked when the condition is true: a copy of a1 is made to convert it to an prvalue. The prvalue is copy elided by your compiler.

In the second example, where you have an if statement, these rules don't apply as in the case of the ternary operator. So no copy constructor invoked here.


To address your comment about a conditional statement with lvalue for the second and third operands, according to the rules of copy elision it is allowed if:

In a return statement, when the operand is the name of a non-volatile object with automatic storage duration, which isn't a function parameter or a catch clause parameter, and which is of the same class type (ignoring cv-qualification) as the function return type. This variant of copy elision is known as NRVO, "named return value optimization".

A conditional statement like

return i != 0 ? a1 : a1;

where the second and third operands are lvalues, does not fulfill this criteria. The expression is a conditional, not the name of an automatic object. Hence no copy elision and the copy constructor is invoked.

like image 69
jignatius Avatar answered Oct 13 '22 13:10

jignatius