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:

1 Answer
1

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.