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.
a ctor a copy ctor
withf(4)
buta ctor a ctor
withf(5)
. The same in the second example.f(5)
givesa ctor a ctor
andf(4)
givesa ctor a move ctor
– Tangleberry