#include <iostream>
template <class U, class T>
void foo(U&, T&)
{
std::cout << "first";
}
template <class T>
void foo(int&, const T&)
{
std::cout << "second";
}
int main()
{
int a;
double g = 2.;
foo(a, g); // prints "first"
return 0;
}
要调用第二个foo
重载,编译器只需要执行一次模板类型推导,但对于第一次重载,它需要执行两次。你能解释为什么要调用第一个重载吗?
答案 0 :(得分:16)
过载分辨率分多步完成。
首先,通过名称查找,我们选择可行的候选人名单。在这种情况下,即:
template <class U, class T>
void foo(U&, T&); // with U = int, T = double
template <class T>
void foo(int&, const T&) // with T = double
接下来,我们确定每个可行候选者的每个参数所需的转换序列。这是[over.ics.rank]:
标准转换序列
S1
是比标准转换序列更好的转换序列S2
如果[...]
- S1是S2的正确子序列(比较规范形式的转换序列 由13.3.3.1.1定义,不包括任何左值变换;身份转换序列是 被认为是任何非身份转换序列的子序列)或者,如果不是,
- S1的等级优于S2的等级,或者S1和S2具有相同的等级并且是可区分的 根据以下段落中的规则,或者,如果不是,
对于第一次调用,转换序列为(Identity,Identity)。对于第二次调用,转换序列是(Identity,Identity)。所以我们在那里平等。这两个要点都没有区分这两个要求。所以我们继续前进。
- S1和S2是引用绑定(8.5.3),并且都不引用a的隐式对象参数 声明的非静态成员函数没有ref-qualifier,S1绑定一个右值引用 右值和S2绑定左值参考值。
不相关。
- S1和S2是引用绑定(8.5.3),S1将左值引用绑定到函数左值和 S2将右值引用绑定到函数左值。
不。
- S1和S2的不同之处仅在于他们的资格转换,并且产生类似的类型T1和T2(4.4), 类型T1的cv资格签名是cv资格的适当子集 T2型签名。
资格转换是一个指针,nope。
- S1和S2是引用绑定(8.5.3),引用引用的类型相同 除了顶级cv限定符之外的类型,以及由S2初始化的引用所引用的类型 比由S1初始化的引用引用的类型更符合cv。
在这种情况下,第一个重载将其第二个参数作为double&
,而第二个重载采用const double&
。前者比后者更少 cv - 因此我们在这里停止 - 更喜欢foo(U&,T&)
。
只有在确定哪个转换序列更好的步骤之后,我们才能进入更专业的模板首选的步骤。 [over.match.best]中的完整规则排序是:
鉴于这些定义,可行函数F1被定义为比另一个可行函数更好的函数 F2如果对于所有参数i,ICSi(F1)不是比ICSi(F2)更差的转换序列,然后
- 对于某些参数j,ICSj(F1)是比ICSj(F2)更好的转换序列,或者,如果不是,
这就是我们刚刚经历的事情。
- 上下文是用户定义的转换初始化[...]
- 上下文是通过转换函数初始化直接引用绑定[...]
- F1不是功能模板专业化,F2是功能模板专业化,或者,如果不是,
- F1和F2是功能模板专精,F1的功能模板更专业 根据14.5.6.2中描述的部分排序规则,比F2的模板。
这就是我们选择foo(U&, T&)
的原因。但是,如果删除const
,则所有步骤中的两个转换序列都是相同的 - 因此,此时,更专业的模板(foo(int&, T&)
)将获胜。
请注意,更专业的是确定最佳候选人的最后机制。这是最终的决胜局。
另请注意,模板扣除的编号无关紧要。 可能在选择作为模板的重载和不是模板的重载之间起作用 - 但是在具有 x 模板参数的重载之间进行选择并不重要。 y&gt;的重载x 模板参数。
答案 1 :(得分:7)
在第二个函数中声明第二个参数为const
。下面的应用修正示例调用第二个示例:
#include <iostream>
template <class U, class T>
void foo(U&, T&)
{
std::cout << "first";
}
template <class T>
void foo(int&, T&)
{
std::cout << "second";
}
int main()
{
int a;
double g = 2.;
foo(a, g);
return 0;
}
另一方面,当您在const
中明确声明第二个参数时,请main()
,应用程序会按照预期在上面的示例中调用第二个函数:
#include <iostream>
template <class U, class T>
void foo(U&, T&)
{
std::cout << "first";
}
template <class T>
void foo(int&, const T&)
{
std::cout << "second";
}
int main()
{
int a;
const double g = 2.;
foo(a, g);
return 0;
}
答案 2 :(得分:3)
观察:
因为您将g
作为非常量左值传递,编译器会选择第一次重载。