我有以下代码,可能看似令人费解,但来自真实的代码:
#include <iostream>
using namespace std;
template <class Hrm, class A>
void foo(Hrm& h, A& a)
{
cout << "generic" << endl;
}
template <template <bool> class Hrg>
void foo(Hrg<false>& h, int& a)
{
cout << "specialized int" << endl;
}
template <template <bool> class Hrg>
void foo(Hrg<true>& h, const int& a)
{
cout << "specialized const-int" << endl;
}
template <bool W>
struct what;
template<> struct what<true> { };
template<> struct what<false> { };
int main() {
what<true> wt;
what<false> wf;
int i = 5;
const int& ri = i;
foo(wt, i); // 1) generic
foo(wf, i); // 2) specialized int
foo(wt, ri); // 5) specialized const-int
foo(wf, ri); // 6) generic
return 0;
}
我理解4
:对于Hrg
的假const int
没有专门化,因此调用通用版本。
我的问题是,为什么给定的函数调用其他情况? 3
似乎称为专用const版本,因为const int
与A
匹配“更直接”。我想知道为什么会更具体地发生这种情况。
那么,1
和2
呢?特别是, 1
对我来说非常令人惊讶:为什么调用generic
版本而不是专门的const-int?
附加说明:如果我将两个foo
专精更改为:
template <template <bool> class Hrg>
void _foo(Hrg<false>& h, int& a)
{
cout << "specialized int" << endl;
}
template <template <bool> class Hrg>
void _foo(Hrg<true>& h, const int& a)
{
cout << "specialized const-int" << endl;
}
template <class Hrg>
void foo(Hrg& h, int& a)
{
return _foo(h, a);
}
template <class Hrg>
void foo(Hrg& h, const int& a)
{
return _foo(h, a);
}
然后输出变为:
foo(wt, i); // a) specialized const-int
foo(wf, i); // b) specialized int
foo(wt, ri); // c) specialized const-int
//foo(wf, ri); // d) compilation error
对我来说,这是一个更直观的结果。
答案 0 :(得分:4)
过载分辨率在以下步骤中发生:
重要的是要记住步骤4在步骤3之后; “通用性”或“模板性”完全是打破平局规则。
让我们在您的第一个代码块中查看所有示例。
(1)扣除在第一次和第三次超载时成功;第二次无法推断出Hrg
。因此候选人是第一和第三(规则1)。两者都是可行的(规则2)。第一个重载会将i
绑定到int&
,而第三个重载会将i
绑定到const int&
。优先绑定到较少cv限定的参考(规则3)。 (Barry有标准的特定引用。)第一个(通用)重载获胜。
(2)Hrg
不能推断出第三次重载,因此它不是候选者(规则1)。第一和第二是候选人并且是可行的(规则2)。第一次和第二次重载都完全匹配,不需要转换,并且规则3无法区分。第二次重载因为它更专业(规则4)而获胜。
(5)Hrg
的扣除因第二次超载而失败,因此它不是候选者,而第一次和第三次超载(规则1)。请注意,对于第一次重载,A
被推导为const int
,从而为第三次重载生成相同的签名。它们都是可行的(规则2),并且在规则3结束时无法区分。第三次重载胜出因为它更专业(规则4)。
(6)Hrg
的扣除因第三次超载而失败,因此它不是候选者,而第一次和第二次超载(规则1)。第二个重载不可行(规则2),因为int&
无法绑定到ri
,const
。第一个重载是通用的,它是唯一可行的函数,所以它赢了。
我将重载决议留在第二个代码块中作为读者练习。
[1]正如T.C.在评论中指出,这里有一个微妙的地方。只有当对于给定的一对函数,从参数初始化参数所需的隐式转换序列对每对相应参数进行相等排序时,才应用起搏器规则。如果第一个函数对于一个参数具有更好的隐式转换序列而第二个函数具有针对不同参数的更好的隐式转换序列,则不应用平局判断规则,并且仍然存在歧义。但是,在问题的示例中不会出现这种情况。
答案 1 :(得分:2)
按顺序通过四个不同的测试用例。在所有情况下,转换序列都是相同的 - 不需要转换,因此我们必须继续进行下一阶段的重载解析。
foo(wt, i); // 1) generic
这里有两个潜在的重载。所有内容和Hrg<true>
template <class Hrm, class A> void foo(Hrm& h, A& a);
template <template <bool> class Hrg> void foo(Hrg<true>& h, const int& a);
在[over.ics.rank]中,我们(感谢@dyp):
标准转换序列
S1
是一个比标准转换序列S2
更好的转换序列,如果...
-S1
和S2
是引用绑定(8.5.3),引用引用的类型相同 除了顶级 cv - 限定符之外的类型,以及S2
初始化引用的类型 比S1
初始化的引用引用的类型更符合 cv 的条件。
const int
比<{1}}更符合 cv ,因此首选int
重载 - 这是通用的。
int
这里有两个重载
foo(wf, i); // 2) specialized int
这两个转换序列在这里是相同的,因此该部分中的任何内容都不能区分彼此。所以我们转到[over.match.best]:
鉴于这些定义,可行函数
template <class Hrm, class A> void foo(Hrm& h, A& a) template <template <bool> class Hrg> void foo(Hrg<false>& h, int& a)
被定义为更好函数而不是另一个可行函数F1
如果所有参数 i ,则ICS i (F2
)的转换序列不比ICS i 更差(F1
),然后
- ......
-F2
和F1
是函数模板特化,F2
的函数模板更专业 根据14.5.6.2中描述的部分排序规则,F1
的模板。
关于“更专业”的规则是复杂的,但基本上意味着第二次过载对于第一种可行的类型的严格子集是可行的,因此它是优选的。
F2
在这里,我们有与第一种情况相同的两个重载:
foo(wt, ri); // 5) specialized const-int
转换序列是相同的,但第二个重载比第一个过载更专业,因此专门的重载是首选,原因与(2)中的相同。
template <class Hrm, class A> void foo(Hrm& h, A& a);
template <template <bool> class Hrg> void foo(Hrg<true>& h, const int& a);
这里“通用”过载是唯一可行的过载。
更新您添加的新测试比前四个更简单。鉴于两个foo(wf, ri); // 6) generic
重载:
foo
使用template <class Hrg> void foo(Hrg& h, int& a);
template <class Hrg> void foo(Hrg& h, const int& a);
调用时,只有第二个重载是可行的。但是当使用ri
进行调用时,首选的重载是首选的,原因与上面的(1)相同 - i
cv - 比int
更少。< / p>