详解 C++ 的隐式类型转换与函数重载( 二 )

上述代码中,类型转换的目标由1中的A -> double修改为A -> long double,由于A类定义的两个类型转换都不再是精确匹配,此时编译器将直接判定此行为具有二义性 。
再来看下一个例子:
// 同时定义int -> A, int -> B, double -> C的转换构造函数struct A { A(int) {} };struct B { B(int) {} };struct C { C(double) {} };void test(const A &) {}void test(const B &) {}void test(const C &) {}int main{// 由于同时存在多个可行的用户定义的类型转换,包括:// 1. 0 -> A// 2. 0 -> B// 3. 0 -> double -> C// 此调用将直接被判定为二义性test(0);// 同理,这样的调用存在的可行的类型转换如下:// 1. 0. -> int -> A// 2. 0. -> int -> B// 3. 0. -> C// 故即使0. -> C是精确匹配,也将被判定为二义性test(0.);}上述代码中,我们为类A,B,C都定义了int或double到类类型的转换构造函数,同时,我们定义了分别以A,B,C作为形参类型的三个重载函数,并使用int或double作为实参进行调用 。此时,由于int与double都存在向A,B,C进行基于用户定义的隐式类型转换的方案,故此种调用将直接被编译器判定为二义性 。
由此,我们得到了第一条用户定义的类型转换方案与重载确定之间的重要规则:如果存在多个可行的用户定义的类型转换,且没有一个类型转换是精确匹配的,则此种情况将被编译器判定为二义性 。
3. 只存在一个可行的用户定义的类型转换,但不是精确匹配
参考以下代码:
struct A { operator int { return 0; } };int main{static_cast<long double>(A); // A -> int -> long double}上述代码中,根据上文“用户定义的隐式类型转换方案与算术类型转换各可出现一次,且顺序不限”的性质,此种情况是可行的 。当我们试图将A转换为long double时,A通过其唯一的类型转换操作符operator int被转换为一个int,然后再通过算术类型转换转为long double 。
4. 只存在一个用户定义的类型转换,但存在多个算术类型转换
参考以下代码:
struct A { A(int) {} A(double) {} };int main{A('0'); // char -> int优于char -> double,故进行char -> int -> A的类型转换A(0l); // long -> int与long -> double都不是更优的算术类型转换,故此调用具有二义性}首先需要明确的是,“不同的类型转换”指多个转换目标不同的类型转换,与转换起点无关,由于上述代码中的两个转换构造函数的转换目标都是A,所以其并不是两个不同的类型转换,即不是上述第二点所述的情况 。
对于此例,我们首先需要引入第二条重要规则:如果只存在一个可行的用户定义的类型转换,但存在多个不同的算术类型转换时,重载确定以算术类型转换的优劣作为依据 。
考察对A的两次实例化:A('0')涉及两种隐式类型转换:char -> int -> A与char -> double -> A,由于char -> int的算术类型转换等级较之char -> double更高,故编译器选择了char -> int -> A的隐式类型转换方案 。而对于A(0l),由于long -> int与long -> double都不是更好的算术类型转换,故编译器判定此调用为二义性 。
2.隐式类型转换与函数模板
7.1 隐式模板实例化与隐式类型转换
C++中,函数模板通过模板实参推导过程实例化出最适合于当前调用参数的函数版本,那么显然,所有实例化出的函数版本互为重载函数,即:这些实例化函数之间不能存在任何的二义性 。
同时,由于函数模板对于实参类型的高度不确定性,隐式类型转换几乎不会发生在模板实例化过程中,取而代之的是一个新的函数版本 。
为了同时满足“最适合当前调用参数的版本”,以及“互为重载函数”这两个要求,我们不难发现,模板实例化时必须要忽略少量类型转换,而这正是重载确定中被视为“精确匹配”的条目,包括以下几点:

  1. 顶层const的有无
  2. 数组到指针的类型转换
参考以下代码:
template <typename T>void test(T) {}int main{const char testStr[10] = "";const char * const strPtr = "";test(testStr); // T = const char *,const char [10]转为const char *test(""); // T = const char *test(strPtr); // T = const char *,const char * const转为const char *}上述代码中,虽然我们使用const char [10]类型的变量调用模板函数,编译器依然会实例化出一个test(const char *)版本进行调用,这是由“数组即指针”这一底层特性决定的 。而对于const char * const类型,显然,编译器将忽略其顶层const 。


推荐阅读