模板函数的重载解析

Overload resolution of template functions

考虑这段代码:

#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++ templates, the completes Guide" 书中读到模板参数推导没有考虑 return 类型,所以为什么 max<int> 不明确而不是 max<double>

return 类型的模板函数真的没有考虑到参数推导吗?

the template argument deduction does not take the return type into account,

是的。 Template argument deduction 根据函数参数执行。

so why max<int> is ambiguous and not max<double>?

给定 ::max<int>(7, 4.),对于第一个重载,第一个模板参数 T1 被指定为 int,并且 T2 被推导出为 double第二个函数参数 4.,则实例化将是 double max(int, double)。对于第二次重载,第一个模板参数 RT 指定为 intT17 推导为 int,推导 T2作为 double 来自 4.,那么实例化将是 int max(int, double)Overload resolution 也没有考虑 return 类型,两个重载都是 完全匹配 然后歧义

给定 ::max<double>(7, 4.),对于第一个重载,第一个模板参数 T1 指定为 double,并且 T2 推导为 double 来自4.,所以实例化将是 double max(double, double)。对于第二次重载,第一个模板参数 RT 指定为 doubleT17 推导为 int,推导 T2作为 4.double,那么实例化将是 double max(int, double)。然后第二个重载在 overload resolution 中获胜,因为它是 完全匹配 ,第一个重载需要从 int 进行 隐式转换 double 第一个参数 7.

让我们看看在重载解析期间将 double 指定为参数对编译器有何影响。

对于"Number1"max模板,它指定第一个参数必须是double类型。尝试进行模板匹配时,编译器推断第二个参数的类型为 double。所以生成的签名是auto max(double, double)。这是一场比赛,尽管它涉及将第一个参数从 int 转换为 double.

对于"Number2"max模板,指定return类型为double。推导参数类型。所以生成的签名是double max(int, double)。这是一个完全匹配,消除了任何歧义。

现在让我们看看指定 int。现在这两个签名是 auto max(int, double)double max(int, double)。如您所见,与重载决议相关的差异没有,导致歧义。

本质上,通过传入 double,您通过强制不必要的转换毒化了其中一个重载;另一个过载从而占据主导地位。相反,传入 int 不会进一步限制任一重载成为完美匹配的能力。

对于您的每个函数调用,编译器都有 2 个函数可供选择,并会选择最佳的一个。未知的模板参数是从除 RT 之外的参数中推导出来的,必须明确指定并且不能推导出来。

auto a = ::max(4, 7.2);

由于RT未指定且无法推导,因此第二个重载不可用,因此被忽略。选择第一个,类型推导为 intdouble.

auto b = ::max<double>(4, 7.4);

RT 现在已指定,因此编译器可以选择使用 max<double,int,double>max<double, double>,3 模板参数版本的参数类型与函数参数完全匹配,而 2模板参数版本需要从 intdouble 的转换,因此选择了 3 参数重载。

auto c = ::max<int>(7, 4.);

RT 现在已指定,因此编译器可以选择使用 max<int,int,double>max<int, double>,两个函数的参数类型现在相同,因此编译器无法在两者之间进行选择他们。