【发布时间】:2015-02-18 20:31:01
【问题描述】:
考虑这段 C++11 代码:
#include <iostream>
#include <cstddef>
template<typename T> void f(T, const char*) //#1
{
std::cout << "f(T, const char*)\n";
}
template<std::size_t N> void f(int, const char(&)[N]) //#2
{
std::cout << "f(int, const char (&)[N])\n";
}
int main()
{
f(7, "ab");
}
好的,那么...选择了哪个重载?在使用编译器输出溢出 bean 之前,让我们尝试对此进行推理。
(所有对章节的引用均针对 C++11 的最终标准文档,ISO/IEC 14882:2011。)
T from #1 推导出为int,N from #2 推导出为3,两个专业都是候选,两者都是可行的,到目前为止一切顺利。哪个最好?
首先,考虑将函数参数与函数参数匹配所需的隐式转换。对于第一个参数,在任何一种情况下都不需要转换(身份转换),int 无处不在,所以这两个函数同样好。第二个,参数类型为const char[3],两次转换分别为:
- 对于#1,数组到指针的转换,类左值转换,根据
[13.3.3.1.1];根据[13.3.3.2]比较转换序列时忽略此转换类别,因此这与身份转换基本相同; - 对于#2,参数是引用类型,直接绑定参数,所以,根据
[13.3.3.1.4],这又是身份转换。李>
同样,运气不好:这两个功能仍然同样出色。两者都是模板特化,我们现在必须看看哪个函数模板(如果有的话)更特化([14.5.6.2] 和 [14.8.2.4])。
编辑 3:下面的描述很接近,但不太准确。请参阅我的回答,了解我认为对流程的正确描述。
- 以#1为参数,#2为参数的模板参数推导:我们发明了一个值
M来代替N,T推导为int,const char*作为参数可以从char[M]类型的参数初始化,一切都很好。据我所知,对于所有涉及的类型,#2 至少与 #1 一样专业。 - 以#2为参数,#1为参数的模板参数推导:我们发明了一个
U类型来代替T,一个@类型的参数987654341@不能从U类型的参数初始化(不相关的类型),char[N]类型的参数不能从const char*类型的参数初始化,非类型参数N的值不能从论点推导出来,所以......一切都失败了。据我所知,对于所有涉及的类型,#1 至少不像 #2 那样专门化。
编辑 1:以上内容已根据 Columbo 和 dyp 的 cmets 进行编辑,以反映在这种情况下尝试模板参数推导之前已删除引用的事实。
编辑 2:根据来自 hvd 的信息,顶级 cv-qualifiers 也被删除。在这种情况下,这意味着 const char[N] 变为 char[N],因为数组元素上的 cv-qualifiers 也适用于数组本身(array of const 也是 const array,可以这么说);这在 C++11 标准中根本不明显,但已在 C++14 中得到澄清。
基于上述,我想说函数模板的部分排序应该选择 #2 作为更专业的,并且调用应该毫无歧义地解析它。
现在,回到严酷的现实。 GCC 4.9.1 和 Clang 3.5.0,具有以下选项
-Wall -Wextra -std=c++11 -pedantic
以不明确的方式拒绝调用,并显示类似的错误消息。来自 Clang 的错误是:
prog.cc:16:2: error: call to 'f' is ambiguous
f(7, "ab");
^
prog.cc:4:27: note: candidate function [with T = int]
template<typename T> void f(T, const char*) //#1
^
prog.cc:9:30: note: candidate function [with N = 3]
template<std::size_t N> void f(int, const char(&)[N]) //#2
^
Visual C++ 2013 的 IntelliSense(据我所知,基于 EDG 编译器)也将调用标记为不明确。有趣的是,VC++ 编译器继续编译代码,没有错误,选择#2。 (耶!它同意我,所以它一定是对的。)
对于专家来说显而易见的问题是,为什么调用不明确?我缺少什么(我猜是在偏序区域)?
【问题讨论】:
-
我也不是,这是一个非常详细的问题,显示了努力。不过,无法帮助您解决问题。
-
@KerrekSB 不,我不这么认为。正如问题中所解释的,
const char*和const char[3]在绑定到const char[3]参数时同样有效这一事实不足为奇。这就是为什么我们必须对函数模板进行部分排序来决定调用哪个重载。 -
我实际上更希望将此类调用标记为模棱两可,因为调用错误的函数很容易出错。也许 VC++ 更符合标准的精神,而不是标准的文字。
-
好吧,我想我们正在取得进展。基于 hvd 对其已删除答案的最后评论:
[14.8.2.4]表示,在初步转换之后,类型推导按照[14.8.2.5]中的描述进行,这不是如何从函数调用中推导类型,而是从一个类型推导出模板参数。那里的规则不允许与函数参数进行相同的转换;我想它们可以被认为是用于从相应的参数推导类模板参数的相同规则。 -
@EvanCarslake 有时遗憾的是,否决票与内容无关,尤其是有问题时,因为您不必使用代表来否决问题。
标签: c++ templates c++11 ambiguous