Most things in programming languages are not completely and totally free. Unless you're writing compile-time only code, writing an identity function is unlikely to be free.
Let's rework your code a bit:
#include <algorithm>
#include <iostream>
template <typename T>
T id1(T&& t)
{
return t;
}
template <typename T>
T id2(T&& t)
{
return std::move(t);
}
class X
{
public:
X()
{ output0("Xdef"); }
X(std::string const& s) : label_(s)
{ output1("Xstr",s); }
X(X const& x) : label_(x.label_)
{ output1("Xcopy", x); }
X(X&& x) : label_(std::move(x.label_))
{ output1("Xmove", x); }
X& operator =(X const& x)
{
output1("operator =copy", x);
label_ = x.label_;
return *this;
}
X& operator =(X&& x)
{
using std::swap;
output1("operator =move", x);
swap(label_, x.label_);
return *this;
}
~X()
{ output0("~X"); }
private:
void output_id() const
{
std::cout << this << '[' << label_ << "]";
}
void output0(std::string const& name) const
{
output_id();
std::cout << ": " << name << "()" << std::endl;
}
void output1(std::string const& name, std::string const& str) const
{
output_id();
std::cout
<< ": " << name
<< "(\"" << str
<< "\")" << std::endl;
}
void output1(std::string const& name, X const& arg) const
{
output_id();
std::cout << ": " << name << '(';
arg.output_id();
std::cout << ')' << std::endl;
}
std::string label_;
};
int main()
{
{
std::cout << "CASE A:\n";
auto x = X("x1");
}
std::cout << "\n";
{
std::cout << "CASE B:\n";
auto x = id1(X("x2"));
}
std::cout << "\n";
{
std::cout << "CASE C:\n";
auto x = id2(X("x3"));
}
std::cout << "\n";
{
std::cout << "CASE D:\n";
X x = id1(X("x4"));
}
std::cout << "\n";
{
std::cout << "CASE E:\n";
X x = id2(X("x5"));
}
}
and when run it outputs (using a GCC v4.8 snapshot):
$ ./a.out
CASE A:
0x7fff411fc530[x1]: Xstr("x1")
0x7fff411fc530[x1]: ~X()
CASE B:
0x7fff411fc540[x2]: Xstr("x2")
0x7fff411fc520[x2]: Xcopy(0x7fff411fc540[x2])
0x7fff411fc540[x2]: ~X()
0x7fff411fc520[x2]: ~X()
CASE C:
0x7fff411fc540[x3]: Xstr("x3")
0x7fff411fc520[x3]: Xmove(0x7fff411fc540[])
0x7fff411fc540[]: ~X()
0x7fff411fc520[x3]: ~X()
CASE D:
0x7fff411fc540[x4]: Xstr("x4")
0x7fff411fc520[x4]: Xcopy(0x7fff411fc540[x4])
0x7fff411fc540[x4]: ~X()
0x7fff411fc520[x4]: ~X()
CASE E:
0x7fff411fc540[x5]: Xstr("x5")
0x7fff411fc520[x5]: Xmove(0x7fff411fc540[])
0x7fff411fc540[]: ~X()
0x7fff411fc520[x5]: ~X()
$
Case A simply invokes the constructor for X. The =
in this instance is equivalent to passing the right-hand side of the =
to X, i.e., it is not an assignment.
Case B invokes id1()
which does not move its return argument. Since the returned value was not defined on the call stack of id() and the value is an lvalue (holding an rvalue) it was not automatically moved upon return and therefore was copied.
Case C invokes id2()
which does invoke the move constructor on return.
Cases D and E are the same as Cases B and C respectively except that auto
is not used if you were sceptical about such.
Moves should be seen as optimized copies and as bad as copies in the worst case (although they will often be much better). Even an optimal move has a cost (e.g., copying some data (usually) from one stack frame to another). The only way copies/moves are completely avoided in run-time code is when return value optimization and copy ellison are eligible for the compiler to use.