I have stumbled over code today, that I don't understand. Please consider the following example:
#include <iostream>
#include <string>
class A
{
public:
template <class Type>
Type& operator=(Type&& theOther)
{
text = std::forward<Type>(theOther).text;
return *this;
}
private:
std::string text;
};
class B
{
public:
B& operator=(B&& theOther)
{
text = std::forward<B>(theOther).text;
return *this;
}
private:
std::string text;
};
int main()
{
A a1;
A a2;
a2 = a1;
B b1;
B b2;
b2 = b1;
return 0;
}
When compiling, MinGW-w64/g 10.2 states:
..\src\Main.cpp: In function 'int main()':
..\src\Main.cpp:41:7: error: use of deleted function 'B& B::operator=(const B&)'
41 | b2 = b1;
| ^~
..\src\Main.cpp:19:7: note: 'B& B::operator=(const B&)' is implicitly declared as deleted because 'B' declares a move constructor or move assignment operator
19 | class B
| ^
mingw32-make: *** [Makefile:419: Main.o] Error 1
I fully understand the error message. But I don't understand why I don't get the same message with class A
. Isn't the templated move assignment operator also a move assignment operator? Why then is the copy assignment operator not deleted? Is this well-written code?
CodePudding user response:
Isn't the templated move assignment operator also a move assignment operator?
No, it's not considered as move assignment operator.
(emphasis mine)
A move assignment operator of class T is a non-template non-static member function with the name
operator=
that takes exactly one parameter of typeT&&
,const T&&
,volatile T&&
, orconst volatile T&&
.
As the effect, A
still has the implicitly-declared copy/move assignment operator.
BTW: Your template assignment operator takes forwarding reference, it could accept both lvalue and rvalue. In a2 = a1;
, it wins against the generated copy assignment operator in overload resolution and gets called.
CodePudding user response:
As complement to @songyuanyao's standard-based answer: these kind of language rules are common reasons for guidelines such as MISRA/AUTOSAR (language guidelines for safety-critical C development) to have "avoid developer confusion" rules such as:
(from AUTOSAR C 14 Guidelines)
Rule A14-5-1 (required, implementation, automated)
A template constructor shall not participate in overload resolution for a single argument of the enclosing class type.
Rationale
A template constructor is never a copy or move constructor and therefore doesn’t prevent the implicit definition of a copy or move constructor even if the template constructor looks similar and might easily be confused. At the same time, copy or move operations do not necessarily only use a copy or move constructor, but go through the normal overload resolution process to find the best matching function to use. This can cause confusion in the following cases:
- a template constructor that looks like a copy/move constructor is not selected
- for a copy/move operation because the compiler has generated an implicit copy/move constructor as well a template constructor is selected in preference over a copy/move constructor because the template constructor is a better match
To avoid these confusing situations, template constructors shall not participate in overload resolution for a single argument of the enclosing class type to avoid a template constructor being selected for a copy/move operation. It also makes it clear that the constructor is not a copy/move constructor and that it does not prevent the implicit generation of copy/move constructors.
Rule M14-5-3 (required, implementation, automated)
A copy assignment operator shall be declared when there is a template assignment operator with a parameter that is a generic parameter.
Namely that it can be surprising for developers that a template copy/move ctor/assignment operator does not suppress (/does not "activate" rule of 5) implicitly-generated ones. You are typically required to use SFINAE to make sure that the templated ctor/assignment op does not act as if it was a copy/move ctor/assignment by allowing the overload to be active for a single argument of the enclosing class.