【问题标题】:This case of template function overloading eludes my understanding这种模板函数重载的案例让我无法理解
【发布时间】:2015-10-02 04:16:25
【问题描述】:
#include <iostream>

template<typename T>
struct identity
{
    typedef T type;
};

template<typename T> void bar(T) { std::cout << "a" << std::endl; }
template<typename T> void bar(typename identity<T>::type) { std::cout << "b" << std::endl; }

int main ()
{
    bar(5); // prints "a" because of template deduction rules
    bar<int>(5); // prints "b" because of ...?

    return EXIT_SUCCESS;
}

我预计bar&lt;int&gt;(5) 至少会产生歧义。这里涉及到什么关于模板函数重载解析的疯狂规则?

【问题讨论】:

  • 我会说typename identity&lt;T&gt;::typeT 更专业。
  • @Jarod42 typename identity&lt;int&gt;::type 只是语法上更冗长的表达方式int;它并不表示更专业的int。如果我们在两个函数中用int 替换模板参数T,我们会得到一个只使用int,另一个使用typename identity&lt;int&gt;::type这应该是模棱两可的。我的意思是,假设您有一个简单的 A 类,其中包含 typedef int B。与在同一参数位置使用 int 的函数相比,您不会优先考虑使用 A::B 的函数。

标签: c++ templates language-lawyer overload-resolution


【解决方案1】:

一旦我们得到我们的候选函数集(都是bars),然后将其缩减为可行函数(仍然是bars),我们必须确定最佳可行函数.如果有多个,我们会得到一个歧义错误。 [over.match.best] 中列出了我们为确定最佳方案而采取的步骤:

[A] 可行函数 F1 被定义为比另一个可行函数 F2 更好的函数,如果对于所有参数 i,ICSi(F1) 不是更差转换顺序比ICSi(F2),然后
— 对于某些论点 j,ICSj(F1) 是比 ICSj(F2) 更好的转换序列,或者,如果不是这样,

两个函数都采用int 类型的参数,因此两个转换序列是相同的。我们继续。

——上下文是用户定义转换的初始化[...]

不适用。

— 上下文是对函数类型的引用的直接引用绑定 (13.3.1.6) 的转换函数的初始化,[...]

不适用。

— F1 不是函数模板特化,F2 是函数模板特化,或者,如果不是,则

bar&lt;int&gt;s 都是函数模板的特化。因此,我们转到最后一个要点来确定最佳可行功能。

——F1和F2是函数模板特化,F1的函数模板更加特化 根据 14.5.6.2 中描述的部分排序规则,而不是 F2 的模板。

部分排序规则基本上归结为我们为bar 重载的参数合成新的唯一类型,并对另一个重载执行模板推导。

首先考虑“b”重载。合成一个类型typename identity&lt;Unique1&gt;::type 并尝试对T 执行模板推导。那成功了。有最简单的模板推导。

接下来,考虑“a”重载。合成一个类型Unique2 并尝试对typename identity&lt;T&gt;::type 执行模板推导。这失败!这是一个非演绎的上下文 - 没有演绎可以成功。

由于模板类型推导只在一个方向上成功,bar(typename identity&lt;T&gt;::type) 重载被认为更专业,并被选为最佳可行候选者。


bogdan 提供了另一个有趣的案例来研究偏序。考虑改为比较:

template <typename T> void bar(T, T); // "c"
template <typename T> void bar(T, typename identity<T>::type ); // "d"

bar(5,5);
bar<int>(5, 5);

同样,两个候选者都是可行的(这一次即使没有明确指定T),所以我们看看偏序规则。

对于“c”重载,我们综合UniqueC, UniqueC 类型的参数并尝试对T, typename identity&lt;T&gt;::type 执行推导。这成功了(使用T == UniqueC)。所以“c”至少和“d”一样特化。

对于“d”重载,我们合成UniqueD, typename identity&lt;UniqueD&gt;::type 类型的参数并尝试对T, T 执行推导。这失败了!论据有不同的类型!所以“d”至少不像“c”那么专业。

因此,调用了“c”重载。

【讨论】:

  • 我认为值得注意的是,针对active issue 1391 的拟议决议可能会使这一点变得模棱两可。这整个区域的指定很差 - 只需将第一个声明更改为 bar(T, T) 并将第二个声明更改为 bar(T, typename identity&lt;T&gt;::type) 并享受惊喜......
  • @bogdan 是的,值得添加作为示例。这是一个有趣的!
  • "对于 "c" 重载,[...]" - 我们不能指望编译器知道 identity&lt;T&gt;::type 是为了 any 发明的 UniqueC ,因此在这种情况下,在部分排序期间,扣除实际上不会成功。特别是,我们一般不能说identity&lt;T&gt;::type 总是T,这就是我们可以使这个推论成功的方法。如果可以的话,我们可以对“d”重载做同样的事情,当我们合成 UniqueD, typename identity&lt;UniqueD&gt;::type 类型的参数时——那些将等价于 UniqueD, UniqueD,反过来也可以使推导成功。
  • 简而言之,按照这个推理,演绎要么双向失败,要么双向成功。无论哪种情况,它都不会产生更专业的模板。据我所知,实际发生的情况是大多数编译器(Clang、GCC 和 EDG)将typename identity&lt;T&gt;::type 视为等同于另一个不相关的参数U。这使得从“c”到“d”的演绎成功,反之则失败,使得“c”更加专业化。不用说,MSVC 不这么认为,并报告了一个模棱两可的情况(有趣的是,这更符合标准似乎正在采取的方向)。
  • @bogdan 它只是说我们用合成类型替换。它并没有说我们实际上实例化了该替换产生的任何模板。
猜你喜欢
  • 1970-01-01
  • 2019-10-11
  • 2018-05-31
  • 2016-04-02
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多