考虑到constness,templated-ness和genericness,C ++如何解析专用模板?

时间:2015-05-05 22:09:18

标签: c++ templates overloading overload-resolution

我有以下代码,可能看似令人费解,但来自真实的代码:

#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;
}

Ideone link

我理解4:对于Hrg的假const int没有专门化,因此调用通用版本。

我的问题是,为什么给定的函数调用其他情况? 3似乎称为专用const版本,因为const intA匹配“更直接”。我想知道为什么会更具体地发生这种情况。

那么,12呢?特别是, 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

对我来说,这是一个更直观的结果。

2 个答案:

答案 0 :(得分:4)

过载分辨率在以下步骤中发生:

  1. 组装了一组候选函数。这组候选者包括非模板函数和函数模板的特化。如果模板参数扣除在功能模板上失败,则会从候选集中静默删除。
  2. 确定候选函数的子集可行。这意味着它具有的参数数量与参数数量兼容,并且每个参数可以隐式转换为相应的参数类型。 (注意一个函数,即使转换是不明确的,但仍然可行;但在这种情况下,仍然可以选择此函数,然后在之后出现编译错误。)
  3. 比较可行的功能。对于给定的一对可行函数,在某种意义上,为了从给定参数初始化函数的参数,需要较少的隐式转换,被认为比其他函数“更好”。请注意,给定两个函数可能没有一个比另一个好。通常,这些规则足以证明单个可行功能优于所有其他功能。然后该功能将赢得重载决议。
  4. 如果有两个功能并且两个功能都不比另一个好,那么在某些情况下[1]应用了一个决胜局规则,它可能仍然决定一个比另一个更好。如果规则3无法确定两个可行函数中哪一个更好,但只有一个是非模板,则非模板更好;如果两者都是模板特化,但是一个是从更专业的模板生成的,那么该函数更好。在决胜局之后,如果有一个最好的可行功能(优于所有其他功能),该功能将赢得重载决策。如果模糊性仍然存在,则重载解析失败并且调用不明确。
  5. 重要的是要记住步骤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&无法绑定到riconst。第一个重载是通用的,它是唯一可行的函数,所以它赢了。

    我将重载决议留在第二个代码块中作为读者练习。

    [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更好的转换序列,如果...
   - S1S2是引用绑定(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),然后
   - ......
   - F2F1是函数模板特化,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>