为什么编译器在分配时调用模板化的复制构造函数?

Why does the compiler invoke a templated copy constructor when assigning?

考虑以下代码:

#include <iostream>

template<class T>
struct X
{
    X() = default;

    template<class U>
    X(const X<U>&)
    {
        std::cout << __PRETTY_FUNCTION__ << std::endl;
    }

/*
    template<class U>
    X& operator=(const X<U>&)
    {
        std::cout << __PRETTY_FUNCTION__ << std::endl;
        return *this;
    }
*/

};

int main()
{
    X<int> a;
    X<double> b;

    b = a;
}

Live on Coliru

如您所见,赋值运算符被注释掉了。然而,行

b = a;

编译正常。我觉得应该编译不过,因为 ab 有不同的类型,编译器默认生成的 operator=(const X&) 会被底层类型实例化,所以不会'将 X<int> 分配给 X<double>

令我惊讶的是,代码编译通过了,而且似乎正在调用模板化的复制构造函数。为什么是这样?是因为编译器首先尝试将 a 转换为 b 然后调用默认生成的 B::operator=?

如果 TU 是不同的类型,template<class U> X(const X<U>&) 不是复制构造函数,因为它的参数是不同的类型。也就是说,X<int>X<double>是不相关的类型,所以这个构造函数只是它们之间的自定义转换。

请注意,此代码不会打印任何内容:

X<int> a;
X<int> b { a };

因为在这种情况下,将调用形式为 X<int>::X(const X<int>&) 的隐式声明的复制构造函数。

编译器正在生成对 X<double>(const X<int>&) 的调用,以将 X<int> 转换为 X<double>。然后它调用生成的赋值运算符 X<double>& X<double>::operator =(const X<double>&); 进行赋值。

如果您明确地写出所有步骤,它将是:

b.operator =(X<double>(a));

template<class U> X(const X<U>&) 是通用的用户定义转换——请参阅 Anton Savin 的回答。

隐式类型转换的规则相当复杂,因此您应该警惕用户定义的转换函数(More Effective C++ Item 5)。模板函数更是如此。

代码

#include <iostream>

template<class T>
struct X
{
    X() = default;

    template<class U>
    X(const X<U>&)
    {
        std::cout << "generalized ctor: " << __PRETTY_FUNCTION__ << std::endl;
    }

/*
    template<class U>
    X& operator=(const X<U>&)
    {
        std::cout << "generalized assignment: " << __PRETTY_FUNCTION__ << std::endl;
        return *this;
    }
*/
};

int main()
{
    X<int> a;
    X<double> b;

    b = a;
}

returns

generalized ctor: X::X(const X&) [with U = int; T = double]

但是operator=没有注释掉,所以returns

generalized assignment: X& X::operator=(const X&) [with U = int; T = double].

所以你是正确的,隐式生成的 operator=(const X&) 将由基础类型实例化并且不会将 X<int> 分配给 X<double>。正如 Scott Meyers(参见上面的参考资料)所解释的,您的编译器面临着对 X<double>b.operator= 的调用,它需要一个 const X<double>,发现没有这个函数。因此,您的编译器然后会尝试找到一个可接受的隐式类型转换序列,它可以应用它来使调用成功,see rules in the cpp reference。您的编译器找到您的通用用户定义转换并将您的 X<int> 转换为 X<double> 以获得 b.operator=.

的正确参数类型