为什么编译器在分配时调用模板化的复制构造函数?
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;
}
如您所见,赋值运算符被注释掉了。然而,行
b = a;
编译正常。我觉得应该编译不过,因为 a
和 b
有不同的类型,编译器默认生成的 operator=(const X&)
会被底层类型实例化,所以不会'将 X<int>
分配给 X<double>
。
令我惊讶的是,代码编译通过了,而且似乎正在调用模板化的复制构造函数。为什么是这样?是因为编译器首先尝试将 a
转换为 b
然后调用默认生成的 B::operator=
?
如果 T
和 U
是不同的类型,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=
.
的正确参数类型
考虑以下代码:
#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;
}
如您所见,赋值运算符被注释掉了。然而,行
b = a;
编译正常。我觉得应该编译不过,因为 a
和 b
有不同的类型,编译器默认生成的 operator=(const X&)
会被底层类型实例化,所以不会'将 X<int>
分配给 X<double>
。
令我惊讶的是,代码编译通过了,而且似乎正在调用模板化的复制构造函数。为什么是这样?是因为编译器首先尝试将 a
转换为 b
然后调用默认生成的 B::operator=
?
如果 T
和 U
是不同的类型,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=
.