C++ 从重载模板函数中选择的规则是什么?

C++ 从重载模板函数中选择的规则是什么?,c++,templates,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&a

鉴于下面的代码,为什么选择了
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;
}
#包括
使用名称空间std;
模板
无效foo(常数T&)
{
coutCause“”是一个char*,非常适合foo(T*)函数。当您删除它时,编译器将尝试使它与foo(T&)一起工作,这要求您传递对包含字符串的char数组的引用


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

正式地说,在比较转换序列时,将忽略左值转换。转换分为几个类别,如限定调整(
t*
->
t const*
),左值转换(
int[N]
->
int*
void()
->
void(*)(
),以及其他

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

因此,如果有两个候选者,通过只查看转换,两个函数模板专门化都是同样可行的,那么规则是通过对这两个函数模板进行偏序来选择更专门化的函数模板

让我们通过查看函数参数列表的签名来比较这两者

void(T const&);
void(T*);
如果我们为第一个参数列表选择某个唯一的类型
Q
,并尝试与第二个参数列表进行匹配,我们将
Q
T*
进行匹配。这将失败,因为
Q
不是指针。因此,第二个参数列表至少与第一个参数列表一样专用

如果我们反过来做,我们将
Q*
T常量&
匹配。删除引用并忽略顶级限定符,剩下的
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定义了一个隐式转换序列比另一个更好或更差的转换序列的含义
根据这些定义,如果对于所有参数i,ICSi(F1)不是比ICSi(F2)更差的转换序列,则可行函数F1被定义为比另一个可行函数F2更好的函数,然后[…]

  • F1和F2是函数模板专用化,根据14.5.5.2中所述的偏序规则,F1的函数模板比F2的模板更专用,如果不是,则为[…]
最后,这里是可能参与标准转换序列的隐式转换表,位于
13.3.3.1.1/3


根据重载解析规则(的附录B有一个很好的概述),字符串文字(const char[])比T&更接近于T*,因为编译器不区分char[]和char*,所以T*是最接近的匹配(const T*将是精确匹配)

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

template<typename T>
void foo(const T[] a)
模板
无效foo(常数T[]a)
(您不能),编译器会告诉您该函数是对以下内容的重新定义:

template<typename T>
void foo(const T* a)
模板
无效foo(常数T*a)

完整的答案相当技术性

首先,字符串文字具有
char const[N]
type

然后是从
char const[N]
char const*
的隐式转换

因此,两个模板函数都匹配,一个使用引用绑定,一个使用隐式转换。当它们单独存在时,两个模板函数都能够处理调用,但当它们都存在时,我们必须解释为什么第二个foo(用T=char const[N]实例化)比第一个foo更匹配(用T=char实例化)

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

是不明确的(规则相当复杂,但您可以通过编写带有此类签名的非模板函数进行检查,并查看编译器是否抱怨)。在这种情况下,选择第二个,因为该函数更专业(同样,这种偏序的规则是复杂的,但在这种情况下,这是因为您可以将
char const[N]
传递给
char const*
而不是
char const*
传递给
char const[N]
的方式与
void bar(char const*)相同。
void bar(char*)更专业化。)
因为您可以将
char*
传递给
char-co
void foo(char const* x);