Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why is copy assigment possible, if a class has only a (templated) move assignment operator?

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?

like image 213
Benjamin Bihler Avatar asked Oct 28 '21 09:10

Benjamin Bihler


People also ask

What does a move assignment operator do?

In the C++ programming language, the move assignment operator = is used for transferring a temporary object to an existing object. The move assignment operator, like most C++ operators, can be overloaded. Like the copy assignment operator it is a special member function.

Why does copy assignment return a reference?

Strictly speaking, the result of a copy assignment operator doesn't need to return a reference, though to mimic the default behavior the C++ compiler uses, it should return a non-const reference to the object that is assigned to (an implicitly generated copy assignment operator will return a non-const reference - C++03 ...

What is difference between copy constructor and assignment operator?

The Copy constructor and the assignment operators are used to initializing one object to another object. The main difference between them is that the copy constructor creates a separate memory block for the new object. But the assignment operator does not make new memory space.

When copy constructor is called and when assignment operator is called?

In a simple words, Copy constructor is called when a new object is created from an existing object, as a copy of the existing object. And assignment operator is called when an already initialized object is assigned a new value from another existing object.


2 Answers

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 type T&&, const T&&, volatile T&&, or const 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.

like image 161
songyuanyao Avatar answered Nov 14 '22 23:11

songyuanyao


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.

like image 33
dfrib Avatar answered Nov 14 '22 23:11

dfrib