请考虑以下代码:
#include <iostream>
//Number1
template<typename T1, typename T2>
auto max (T1 a, T2 b)
{
std::cout << "auto max(T1 a, T2 b)" <<std::endl;
return b < a ? a : b;
}
//Number2
template<typename RT, typename T1, typename T2>
RT max (T1 a, T2 b)
{
std::cout << "RT max(T1 a, T2 b)" << std::endl;
return b < a ? a : b;
}
int main()
{
auto a = ::max(4, 7.2); //Select Number1
auto b = ::max<double>(4, 7.4); //Select Number2
auto c = ::max<int>(7, 4.); //Compile-time error overload ambiguous
auto c = ::max<double>(7, 4.); //Select Number2
}
auto c = ::max<int>(7, 4.);
:由于重载歧义,此行无法编译,并显示以下消息:
maxdefault4.cpp:9:27: error: call of overloaded 'max(int, double)' is ambiguous
auto c = ::max<int>(7, 4.);
^
maxdefault4.cpp:9:27: note: candidates are:
In file included from maxdefault4.cpp:1:0:
maxdefault4.hpp:4:6: note: auto max(T1, T2) [with T1 = int; T2 = double]
auto max (T1 a, T2 b)
^
maxdefault4.hpp:11:4: note: RT max(T1, T2) [with RT = int; T1 = int; T2 = double]
RT max (T1 a, T2 b)
^
而以下代码:àuto c = ::max<double>(7, 4.)
成功,为什么我们没有相同的错误消息,说调用对max<double>
max<int>
失败的方式相同?
为什么double
没有问题?
我在《C++模板,完整指南》一书中读到,模板参数推导没有考虑返回类型,那么为什么max<int>
模棱两可而不max<double>
呢?
在参数 dedcution 中真的不考虑模板函数的返回类型吗?
模板参数推导不考虑返回类型,
是的。模板参数推导基于函数参数执行。
那么为什么
max<int>
是模棱两可的而不是max<double>
呢?
给定::max<int>(7, 4.)
,对于第一个重载,第一个模板参数T1
被指定为int
,并且T2
从第二个函数参数4.
推导出为double
,则实例化将是double max(int, double)
。对于第 2 个重载,第一个模板参数RT
被指定为int
,T1
从7
推导出为int
,T2
从4.
推导为double
,则实例化将被int max(int, double)
。重载解析也不考虑返回类型,这两个重载既完全匹配,又不明确。
给定::max<double>(7, 4.)
,对于第一个重载,第一个模板参数T1
被指定为double
,并且T2
被推导出为从4.
double
,所以实例化将是double max(double, double)
。对于第 2 个重载,第一个模板参数RT
被指定为double
,T1
从7
推导为int
,T2
从4.
推导为double
,则实例化将被double max(int, double)
。然后第二个重载在重载分辨率中获胜,因为它是完全匹配的,第一个重载需要第一个参数7
从int
隐式转换为double
。
对于每个函数调用,编译器都有 2 个函数可供选择,并选择最好的一个。未知模板参数是从参数中推导出来的,除了RT
参数之外,这些参数必须显式指定且无法推导。
auto a = ::max(4, 7.2);
由于未指定RT
且无法推断,因此第二个重载不可用,因此被忽略。选择第一个,并推断出类型int
和double
。
auto b = ::max<double>(4, 7.4);
现在指定了RT
,以便编译器可以选择使用max<double,int,double>
或max<double, double>
3 模板参数版本的参数类型与函数参数完全匹配,而 2 模板参数版本需要从int
转换为double
,因此选择了 3 参数重载。
auto c = ::max<int>(7, 4.);
现在指定了RT
,以便编译器可以选择使用max<int,int,double>
或max<int, double>
,这两个函数的参数类型现在相同,因此编译器无法在它们之间进行选择。
让我们看看在重载解析期间将double
指定为参数对编译器的作用。
对于"Number1"max
模板,它指定第一个参数的类型必须是double
。尝试执行模板匹配时,编译器推断第二个参数的类型为double
。所以生成的签名是auto max(double, double)
.这是一场比赛,尽管它涉及将第一个参数从int
投射到double
。
对于"Number2"max
模板,它指定返回类型为double
。推导参数类型。所以生成的签名是double max(int, double)
.这是一个完全匹配,消除了任何歧义。
现在让我们看一下指定int
。现在两个签名是auto max(int, double)
和double max(int, double)
。如您所见,与过载分辨率相关的没有区别,从而导致歧义。
从本质上讲,通过传入double
,你通过强制不必要的转换来毒害其中一个重载;另一个重载因此占据主导地位。相比之下,传球int
并不能进一步限制任何一个过载成为完美匹配的能力。