如果 class 只有一个(模板化的)移动赋值运算符,为什么可以进行复制赋值?

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

我今天被代码绊倒了,我不明白。请考虑以下示例:

#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;
}

编译时,MinGW-w64/g++ 10.2 状态:

..\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

我完全理解错误信息。但我不明白为什么我没有收到与 class A 相同的消息。模板化移动赋值运算符不也是移动赋值运算符吗?那么为什么复制赋值运算符没有被删除呢?这段代码写得好吗?

Isn't the templated move assignment operator also a move assignment operator?

不,它不被视为 move assignment operator

(强调我的)

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&&.

作为效果,A 仍然具有隐式声明的 copy/move 赋值运算符。

顺便说一句:您的模板赋值运算符采用 forwarding reference,它可以接受左值和右值。在 a2 = a1; 中,它在重载决策中胜过生成的复制赋值运算符并被调用。

作为对@songyuanyao 基于标准的回答的补充:这类语言规则是 MISRA/AUTOSAR(安全关键 C++ 开发的语言指南)等指南具有“避免开发人员混淆”规则的常见原因如:

(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.

也就是说,对于开发人员来说,模板 copy/move ctor/assignment 运算符不会抑制(/不会“激活”规则 5)隐式生成的运算符可能会令人惊讶。您通常需要使用 SFINAE 来确保模板化的 ctor/assignment op 不会表现得 就好像 它是 copy/move ctor/assignment 通过允许重载对封闭 class.

的单个参数有效