C++ Move Operator – Using Move Operator with Templates in C++11

c++c++11move-semanticstemplates

I have a templated class that I want to avoid copying (because of the potential cost to do so). I can implement a move constructor, but I would also like to allow moving "accross template parameter". Here is what I'm trying to compile:

template <class T>
class Foo
{
public:
    Foo() {}
    template <class U> Foo(Foo<U>&&) {}

private:
    Foo(const Foo&);
};

Foo<int> f() { Foo<float> y; return move(y); }
Foo<int> g() { Foo<int> x; return x; }
Foo<int> h() { Foo<float> z; return z; }

I understand why technically f compiles: the type of move(y) is Foo(float)&& and there happens to be a handy constructor that takes a Foo(U)&&, so the compiler manages to find that U=float works.

h doesn't compile. z is of type Foo(float) and I guess that's too far from Foo(U)&& to figure out that the move constructor can be invoked if U=float is chosen…

I'm not sure why g compiles, but it does. The type of x is Foo(int). How does the compiler manage to use the move operator (it can't just implicit cast from Foo(int) to Foo(int)&&, can it?)

So my questions are: what are the rules? why does h compiles but g doesnt? is there something I can change in Foo to make h compile?

Thank you

Best Answer

A copy or move constructor mustn't be a template. From 12.8(2, 3):

A non-template constructor for class X is a copy constructor if its first parameter is of type X&, const X&, volatile X& or const volatile X&, and either there are no other parameters or else all other parameters have default arguments (8.3.6). [Example: X::X(const X&) and X::X(X&,int=1) are copy constructors.]

A non-template constructor for class X is a move constructor if its first parameter is of type X&&, const X&&, volatile X&&, or const volatile X&&, and either there are no other parameters or else all other parameters have default arguments (8.3.6). [Example: Y::Y(Y&&) is a move constructor.]

So your example f and g work because you're invoking an ordinary constructor (not a move-constructor).

f works for obvious reasons, because the result of move(y) can bind to Foo<float>&&. g works for a different reason: Since the type of x is the same as the return type of the function, the value of the expression x in the return statement matches Foo<int>&&. This is because of 12.8(31, 32):

in a return statement in a function with a class return type, when the expression is the name of a non-volatile automatic object (other than a function or catch-clause parameter) with the same cv-unqualified type as the function return type, [...]

When the criteria for elision of a copy operation are met or would be met save for the fact that the source object is a function parameter, and the object to be copied is designated by an lvalue, overload resolution to select the constructor for the copy is first performed as if the object were designated by an rvalue.

Finally we see why h doesn't work: The value of the expression z in the return statement cannot bind to Foo<float>&&, because it is not explicitly cast (via std::move), nor given the special dispensation of clause 12.8(32), since its type is not the same as the function's return type. (It can only bind to Foo<float>& (which would almost surely be wrong) or to Foo<float> const &.)


By the way, there's no point moving objects that don't manage external resources (e.g. primitives). The actual object data has to be copied anyway.

Related Question