Reputation: 3954
I am suspecting I vaguely know the reason for what I am observing, but I would like confirmation or correction and some explanation to it.
I have the following code:
template <class T>
class C
{
public:
C () = default;
C(const C& rhs) : mem(rhs.mem)
{
std::cerr << "copy" << "\n";
}
// does not call copy constructor twice
// friend C operator<<(C& src, unsigned shift)
// {
// std::cerr << 1 << "\n";
// C tmp(src);
// std::cerr << 2 << "\n";
// tmp <<= shift;
// std::cerr << 5 << "\n";
// return tmp;
// }
// does call copy constructor twice
friend C operator<<(C& src, unsigned shift)
{
std::cerr << 1 << "\n";
C tmp(src);
std::cerr << 2 << "\n";
return (tmp <<= shift);
}
friend C& operator<<=(C& src, unsigned shift)
{
std::cerr << 3 << "\n";
src.mem <<= shift;
std::cerr << 4 << "\n";
return src;
}
T mem;
};
int main()
{
C<int> c1;
c1 << 3;
}
I have two versions of C<T>::operator<<(C<T>, unsigned)
the difference is the one returns the result of an expression:
return (tmp <<= shift);
the other returns a variable:
return tmp
So far I thought the two functions would be semantically identical and the one with return (tmp <<= shift);
simply be better style, like return a + 1
would be better style than int ret = a + 1; return ret
. This is apparently not so and probably only holds true for atomic data types. The output of the version with return (tmp <<= shift);
looks like this:
1
copy
2
3
4
copy
The output of the other like this:
1
copy
2
3
4
5
Is my assumption right that infact return (tmp <<= shift);
does not return tmp
after invoking <<=
on itm but rather create a new object as a copy of tmp
after <<=
has been invoked?
Upvotes: 1
Views: 177
Reputation: 10604
the operator <<=
may not return the reference to the input parameter (maybe it will return reference to another static
/global
variable), so the compiler can not easily decide to use return value optimization, and need to invoke the copy constructor.
In your question: When you write return (tmp <<= shift)
the compiler doesn't know whether or not (tmp <<= shift)
will return reference to tmp, but if you write return tmp
the compiler knows it and can optimize it out.
Upvotes: 0
Reputation: 141638
When returning by value, the return value must be initialized. The return value is an object of type C
here.
In the case return (tmp <<= shift);
, it means that (tmp << shift)
is the initializer for a C
. Since that is an lvalue of type C
, this is a copy-construction.
There is a special rule that for a return statement of the form return identifier;
, then it can be move-construction even though identifier
is an lvalue. But that rule doesn't extend to other expressions (yet).
The other version of your code does activate the special rule:
tmp <<= shift;
return tmp;
Here tmp
may be treated as an rvalue, making it movable (and also this makes it a copy elision context).
In your testing your compiler implemented copy-elision. To test without copy-elision (if your compiler supports letting the user configure that) you'd need to also give your C
a move-constructor. A user-provided copy-constructor suppresses implicit generation of a move-constructor.
Upvotes: 1