I think there's a simple answer for a possibly unexpected reason:
A copy-/move constructor or assignment-operator is never a template (specialization). E.g. [class.copy]/2
A non-template constructor for class
X
is a copy constructor if its first parameter is of typeX&
,const X&
,volatile X&
orconst volatile X&
, and either there are no other parameters or else all other parameters have default arguments.
Also, footnote 122 says:
Because a template assignment operator or an assignment operator taking an rvalue reference parameter is never a copy assignment operator, the presence of such an assignment operator does not suppress the implicit declaration of a copy assignment operator. Such assignment operators participate in overload resolution with other assignment operators, including copy assignment operators, and, if selected, will be used to assign an object.
Example:
#include <iostream>
#include <utility>
template<class T>
struct X
{
X() {}
template<class U>
X(X<U>&&)
{
std::cout << "template \"move\" ctor\n";
}
template<class U>
X& operator= (X<U>&&)
{
std::cout << "template \"move\" assignment-op\n";
return *this;
}
};
int main()
{
X<int> x; // no output
X<int> y(x); // no output
y = std::move(x); // no output
X<double> z( std::move(x) ); // output
y = std::move(z); // output
}
In this example, the implicitly-declared move constructor and move assignment-operator are used.
Therefore, if you don't declare a non-template move ctor and move assignment-operator, they might be declared implicitly. They're not declared implicitly e.g. for the move assignment-op, if you have a user-declared dtor; for details see [class.copy]/11 and [class.copy]/20.
Example: Adding a dtor to the example above:
#include <iostream>
#include <utility>
template<class T>
struct X
{
X() {}
~X() {}
template<class U>
X(X<U>&&)
{
std::cout << "template \"move\" ctor\n";
}
template<class U>
X& operator= (X<U>&&)
{
std::cout << "template \"move\" assignment-op\n";
return *this;
}
};
int main()
{
X<int> x; // no output
X<int> y(x); // no output
y = std::move(x); // output
X<double> z( std::move(x) ); // output
y = std::move(z); // output
}
Here, the first move-assignment y = std::move(x);
calls a specialization of the assignment-operator template, because there's no implicitly declared move assignment-operator.