从重载模板函数中选择有哪些规则?

时间:2009-07-24 13:40:36

标签: c++ templates

鉴于下面的代码,为什么选择了foo(T*)函数?

如果我删除它(foo(T*))代码仍然编译并正常工作,但G ++ v4.4.0(以及可能还有其他编译器)将生成两个foo()函数:一个用于char [ 4]和一个char [7]。

#include <iostream>
using namespace std;

template< typename T >
void foo( const T& )
{
    cout << "foo(const T&)" << endl;
}

template< typename T >
void foo( T* )
{
    cout << "foo(T*)" << endl;
}

int main()
{
    foo( "bar" );
    foo( "foobar" );
    return 0;
}

4 个答案:

答案 0 :(得分:6)

形式上,在比较转换序列时,忽略左值变换。转化次数分为几个类别,例如资格调整T* - &gt; T const*),左值转换int[N] - &gt ; int*void() - &gt; void(*)())和其他人。

两个候选人之间的唯一区别是左值变换。字符串文字是转换为指针的数组。第一个候选者通过引用接受数组,因此不需要左值变换。第二个候选人需要左值变换。

因此,如果有两个候选者,通过仅查看转换,两个函数模板特化都同样可行,那么规则是通过对两者进行部分排序来选择更专业的一个。

让我们通过查看它们的功能参数列表

的签名来比较它们
void(T const&);
void(T*);

如果我们为第一个参数列表选择一些唯一类型Q并尝试与第二个参数列表匹配,我们会将QT*匹配。这将失败,因为Q不是指针。因此,第二个至少与第一个一样专业。

如果我们采取相反的方式,我们会将Q*T const&进行匹配。删除引用并忽略顶级限定符,剩余的T变为Q*。这是用于部分排序的精确匹配,因此第二候选的第二候选变换参数列表的推断成功。由于另一个方向(与第二个方向相比)没有成功,第二个候选者更多比第一个更专业 - 因此,如果否则会出现歧义,则重载决策将优先于第二个。

13.3.3.2/3

  

如果[...]

,标准转换序列S1是比标准转换序列S2更好的转换序列      
      
  • S1是S2的正确子序列(比较规范形式的转换序列   由13.3.3.1.1定义,不包括任何左值变换;身份转换序列被认为是任何非身份转换序列的子序列)或者,如果不是[...]
  •   

然后13.3.3/1

  
      
  • 让ICSi(F)表示隐式转换序列,它将列表中的第i个参数转换为可行函数F的第i个参数的类型.13.3.3.1定义隐式转换序列和13.3.3.2定义对于一个隐式转换序列而言,它意味着比另一个更好的转换序列或更差的转换序列。
  •   
     

鉴于这些定义,可行函数F1被定义为比另一个可行函数F2更好的函数,如果对于所有自变量i,ICSi(F1)不是比ICSi(F2)更差的转换序列,然后[... ]

     
      
  • F1和F2是功能模板专精,F1的功能模板比F2的模板更专业,根据14.5.5.2中描述的部分排序规则,或者,如果不是,[...]
  •   

最后,这是隐式转换表,可以参与13.3.3.1.1/3的标准转换序列。

Conversion sequences http://img259.imageshack.us/img259/851/convs.png

答案 1 :(得分:2)

完整的答案非常技术性。

首先,字符串文字具有char const[N]类型。

然后存在从char const[N]char const*的隐式转换。

因此,您的模板函数都匹配,一个使用引用绑定,一个使用隐式转换。当它们独自时,你的模板函数都能够处理调用,但是当它们都存在时,我们必须解释为什么第二个foo(用T = char const [N]实例化)比第一个更好(用T = char实例化。如果你看一下重载规则(由litb给出),可以选择

void foo(char const (&x)[4));

void foo(char const* x);

是不明确的(规则相当复杂,但您可以通过使用此类签名编写非模板函数进行检查,并查看编译器是否抱怨)。在这种情况下,选择第二个,因为那个更专业(同样,这个部分排序的规则很复杂,但在这种情况下,这是因为你可以将char const[N]传递给{{1但char const*char const*的方式与char const[N]的方式不同void bar(char const*)更专业,因为您可以将void bar(char*)传递给char* 1}}但不反过来。

答案 2 :(得分:0)

基于重载决策规则(C++ Templates: The Complete Guide的附录B有一个很好的概述),字符串文字(const char [])比T&amp;更接近T *,因为编译器不区分char []和char *,所以T *是最接近的匹配(const T *将是完全匹配)。

事实上,如果你可以添加:

template<typename T>
void foo(const T[] a)

(你不能),你的编译器会告诉你这个函数是一个重新定义:

template<typename T>
void foo(const T* a)

答案 3 :(得分:-1)

原因“”是一个char *,完全适合foo(T *)函数。当你删除它时,编译器将尝试使它与foo(T&amp;)一起使用,这要求你传递对包含字符串的char数组的引用。

编译器无法生成一个接收char引用的函数,因为您传递整个数组,因此必须取消引用它。