UPD:
ClassA explicitly declare only copy constructor.
So, ClassA has defaulted copy assignment operator
If the class definition does not explicitly declare a copy assignment operator, one is declared implicitly.
If the class definition declares a move constructor or move assignment operator, the implicitly declared
copy assignment operator is defined as deleted; otherwise, it is defined as defaulted
And hasn't defaulted move constructor
If the definition of a class X does not explicitly declare a move constructor, a non-explicit one will be implicitly
declared as defaulted if and only if
(8.1) — X does not have a user-declared copy constructor,
(8.2) — X does not have a user-declared copy assignment operator,
(8.3) — X does not have a user-declared move assignment operator, and
(8.4) — X does not have a user-declared destructor.
Instead, move constructor ClassA use copy constructor
Note: When the move constructor is not implicitly declared or explicitly supplied, expressions that otherwise
would have invoked the move constructor may instead invoke a copy constructor. — end note ]
Copy elision happen in f(f(str))
in a return statement in a function with a class return type, when the expression is the name of
a non-volatile automatic object (...) 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
Summaries:
f(str) - by default : copy constructor + move constructor; in this case : copy constructor + copy constructor
f(f(str)) - by default : copy constructor + copy elision + move constructor + move constructor; in this case : copy constructor + copy elision + copy constructor + copy constructor
str1 = f(f(str)) - in this case : copy constructor + copy elision + copy constructor + copy constructor + copy assignment operator (not copy constructor What's the difference between assignment operator and copy constructor?)
It's possible to have another result, for example, if use flag "-fno-elide-constructors" in gcc. It disables copy elision
Disabling g++'s return-value optimisation
f(str) return rvalue object. In ClassA f(ClassA &str) you take lvalue references.
Interesting that ClassA f(const ClassA& str) doesn't give error Const reference and lvalue