从重载的模板函数中选择的规则是啥?

Posted

技术标签:

【中文标题】从重载的模板函数中选择的规则是啥?【英文标题】:What are the rules for choosing from overloaded template functions?从重载的模板函数中选择的规则是什么? 【发布时间】:2009-07-24 13:40:35 【问题描述】:

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

如果我删除它(foo(T*)),代码仍然可以编译并正常工作,但 G++ v4.4.0(可能还有其他编译器)将生成两个foo() 函数:一个用于 char[4],一个用于对于字符 [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;

【问题讨论】:

有趣的是,MSVC9 在调试符号中将函数命名为“foo”,但是 - 显然,因为它认识到实现是相同的 - 对两个调用使用相同的实现。 - 无论如何,有趣的问题。 AFAIK VC 折叠导致相同代码的模板实例。 GCC (4.2.4) 使 foo 和 foo 实例化,直到 -O3 为止,它只是完全省略了这些实现并使得它内联。 尼克:我明确禁止 VC 内联它们 @peterchen:显然 VC 仍然折叠相同的实例。 【参考方案1】:

通常,在比较转换序列时,会忽略左值转换。转化分为几类,如资格调整 (T* -> T const*)、左值变换 (int[N] -> int*void() -> void(*)()) 等。

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

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

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

void(T const&);
void(T*);

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

如果我们反过来,我们将Q*T const&amp; 匹配。引用被删除并忽略***限定符,剩余的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 可能参与标准转换序列的隐式转换表。

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

【讨论】:

这不是非模板偏好的情况,因为编译器没有可供选择的非模板函数。事实是 const char [] 与 T* 的匹配比 T& 更接近(因为 const char [] 和 const char * 是等价的)。 对不起,我忽略了第二个之前的模板<...>子句。固定:) 您认为与T* 的匹配更接近的断言是错误的:) @litb:如果我的断言是错误的,编译器不会很同意我的观点。 const T[N] > const T* > const T&,在这种情况下。当然,他不提供 const T[N] 重载,因此 const T* 获胜(好吧,在 OP 的情况下,T*,它更弱,但仍然比 const T& 更适合编译器)。跨度> @litb:我认为我们只是不同意“更接近”的定义......我只是说“更接近”在标准眼中“更可取”(因此,编译器) .【参考方案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] 传递给 char const* 但不能char const*char const[N] 的方式与 void bar(char const*) 相同,比 void bar(char*) 更专业,因为您可以将 char* 传递给 char const*,但反之则不行。

【讨论】:

【参考方案3】:

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

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

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

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

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

【讨论】:

T* 绝不是更接近的匹配项。试试void f(char const(&amp;)[1]); void f(char const*); int main() f(""); :这个调用,类似于上面使用非模板的情况,是模棱两可的。 @litb:在这种情况下,T* 比 T& 更接近匹配。是的,如果你包含一个特定的 SIZE (&[N]),那么它就会变得模棱两可,但是 T* 比 T& 更接近地匹配任意数组。 @Nick,虽然 void foo(T x[]) 是 void foo(T* x) 的同义词,但这并不意味着你不能用 T 实例化 void foo(T&)是一个数组类型。等价于 void foo(ArrayBaseType (&)[ArraySize]) @AProgrammer:是的,我并不是说你不能这样做(事实上,很明显编译器会这样做,没有其他选择),只是它更少首选。 @Nick,请引用相应的标准部分,而不是提出越来越多的声明。坦率地说,你的分析对我来说似乎有点不对劲。【参考方案4】:

因为 " " 是一个 char*,非常适合 foo(T*) 函数。当您删除它时,编译器将尝试使其与 foo(T&) 一起工作,这需要您传递对包含字符串的 char 数组的引用。

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

【讨论】:

错了。请不要猜测。 ""char const[1]。你也不能pass reference。表达式始终具有非引用类型。 我从来没有说过它不是(实际上,它是“”,它是 const[2]),我的意思是如果存在的话,选择 T* 函数是首选。至于passing ref 声明,我想说的是,不太受欢迎的选项是 T&,它基本上是对 T 的引用(在这种情况下,是对 char[N] 的引用),尽管我的回答听起来不太正确,你的权利。如果我错了,请纠正我(在你这样做之后,我会删除我的帖子,因为它与你的相比没有用,它更完整)。

以上是关于从重载的模板函数中选择的规则是啥?的主要内容,如果未能解决你的问题,请参考以下文章

C++ 函数重载,函数模板和函数模板重载,选择哪一个?

函数模板和类模板

在 C++ 中选择重载模板函数时的优先级

C++ 提高教程 模板-普通函数与函数模板调用规则

模板专业化 VS 函数重载

,带你玩转 函数模板以及重载解析,函数还可以这样玩?