function模板的部分sorting – 模糊的调用

考虑一下这个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"); } 

好的,那么select哪个超载? 在将编译器输出的bean溢出之前,让我们尝试对此进行推理。

(所有参考章节均适用于C ++ 11,ISO / IEC 14882:2011的最终标准文档。)

#1的 T推导到int ,从#2的 N推导到3 ,两个专业都是候选者,两者都是可行的,到目前为止这么好。 哪一个最好?

首先,考虑将函数参数与函数参数相匹配所需的隐式转换。 对于第一个参数,在任何情况下( 身份转换 ),无处不在,都不需要转换 ,所以这两个函数同样好。 对于第二个,参数types是const char[3] ,并且这两个转换是:

  • 对于#1 ,按照[13.3.3.1.1]数组到指针的转换 ,类的左值转换 ; 这个转换类别在根据[13.3.3.2]比较转换序列时被忽略,所以这与基于此目的的标识转换基本相同;
  • 对于#2 ,参数是引用types,直接绑定到参数,所以根据[13.3.3.1.4] ,这又是标识转换

再次,没有运气:这两个function仍然同样好。 两者都是模板专业化,我们现在必须看哪个函数模板(如果有的话) 更专业[14.5.6.2][14.8.2.4] )。

编辑3:下面的描述是接近,但不是很准确。 看到我的答案,我相信是对过程的正确描述。

  • #1为参数,以#2为参数的模板参数推导:我们发明了一个值M代替NT推导为intconst char*作为参数可以从char[M]types的参数初始化,一切正常。 据我所知, #2对于所有types都至less和#1一样专业。
  • #2为参数,以#1为参数的模板参数推导:我们发明了一个typesU来代替T ,一个inttypes的参数不能从Utypes的参数(无关types)初始化,一个types为char[N]不能从types为const char*的参数初始化,并且非参数N值不能从参数中推导出来,所以…一切都失败了。 据我所知, #1对于所有涉及的types至less不像#2那么专业。

编辑1:上面已根据Columbo和dyp的意见进行了编辑,以反映在此情况下在尝试模板参数推演之前删除引用的事实。

编辑2:根据来自hvd的信息,顶级cv-qualifiers也被删除。 在这种情况下,它意味着const char[N]变成char[N] ,因为数组元素上的cv-qualifiers也适用于数组本身( const array也是const array ,也就是说)。 这在C ++ 11标准中并不明显,但已经在C ++ 14中进行了澄清。

基于上面,我会说function模板的部分sorting应该select#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的智能感知(基于EDG编译器,据我所知)也标志着调用模糊。 有趣的是,VC ++编译器继续前进,编译没有错误的代码,select#2 。 (耶!它同意我的意见,所以一定是对的)

专家们明显的问题是, 为什么这个呼声不明确? 我错过了什么(在部分订购区域,我猜)?

我把我目前对这个问题的理解的细节作为答案。 我不确定这是否是最后的话,但如果需要的话,它可以作为进一步讨论的基础。 dyp,hvd和Columbo的评论对于find下面引用的各种信息是至关重要的。

正如我所怀疑的,问题在于函数模板的部分sorting规则。 部分[14.8.2.4]在部分sorting中推导模板参数 )表示,在移除引用和cv-qualifiers的初步转换之后,按照[14.8.2.5]从types中推导模板参数 )中的描述完成types推导 。 该部分与引用函数调用的部分不同 – 这将是[14.8.2.1]从函数调用中推导模板参数 )。

当从函数参数types中推导出模板参数时,有一些特殊情况是允许的; 例如,当函数参数是T[i] ,可以推导出typesT*的函数参数中使用的模板参数T ,因为在这种情况下允许数组到指针的转换。 但是, 这并不是在部分sorting中使用的演绎过程,尽pipe我们仍在讨论函数。

我想在偏序sorting时,思考模板参数演绎规则的简单方法就是说,它们与匹配类模板专业化时推导模板参数的规则相同。

清除泥浆? 也许有几个例子会有所帮助。

这是有效的,因为它使用从函数调用中推导出模板参数的规则:

 #include <iostream> #include <type_traits> template<typename T> void f(T*) { std::cout << std::is_same<T, int>::value << '\n'; } int main() { int a[3]; f(a); } 

并打印1

这不是,因为它使用从types中推导模板参数的规则:

 #include <iostream> template<typename T> struct A; template<typename T> struct A<T*> { static void f() { std::cout << "specialization\n"; } }; int main() { A<int[3]>::f(); } 

而来自Clang的错误是

 error: implicit instantiation of undefined template 'A<int [3]>' 

专门化不能使用,因为在这种情况下T*int[3]不匹配,所以编译器试图实例化主模板。

这是部分sorting中使用的第二种演绎。


让我们回到我们的函数模板声明:

 template<typename T> void f(T, const char*); //#1 template<std::size_t N> void f(int, const char(&)[N]); //#2 

我对部分顺序的描述变成:

  • #1为参数,以#2为参数的模板参数推导:我们发明了一个取代N的值MT被推导为int ,但types为const char*的参数与char[M]types的参数匹配,所以#2对于第二对types来说至less不像第一类那么专业化。
  • #2为参数,以#1为参数的模板参数推导:我们发明了一个typesU代替TintU不匹配(不同types), char[N]types的参数不匹配types参数const char* ,并且无法从参数中推导出非types模板参数N的值,所以#1对于任何一种types都不像#2那样专用。

因为为了被选中,所有types的模板都必须至less和其他模板一样专业,所以模板不会比其他模板更专业化,而且调用是模糊的。


上面的解释与Core Language Active Issue 1610 (由hvd提供的链接)中的类似问题的描述有所不同。

这里的例子是:

 template<class C> void foo(const C* val) {} template<int N> void foo(const char (&t)[N]) {} 

作者认为,直观地说,第二个模板应该select更专业化,而且目前还不存在(两个模板都没有比另一个更专业化)。

然后他解释说,原因是从const char[N]删除了const限定符,产生char[N] ,导致扣除以const C*为参数失败。

然而,根据我目前的理解,在这种情况下,扣除将失败, const或没有const 。 Clang和GCC中的当前实现证实了这一点:如果我们从两个函数模板的参数中删除const限定符并用char[3]参数调用foo() ,那么调用仍然是不明确的。 数组和指针在部分sorting中根据当前规则根本不匹配。

说了这么多,我不是委员会的成员,所以可能比我目前所了解的还要多。


更新:我最近偶然发现了另一个可以追溯到2003年的核心活动问题: 问题402 。

在那里的例子相当于1610年的例子。 关于这个问题的评论清楚地表明,这两个重载按照偏序排列algorithm是无序的,这正是因为在偏序sorting时缺乏数组到指针的衰减规则。

最后的评论是:

有些人认为这个案件是可取的,但我们认为现在不值得花时间去研究这个案子。 如果我们看一些更大的局部sorting变化,我们将再次考虑这一点。

因此,我相当确信我上面的解释是正确的。

最初,我认为你的代码问题是你没有考虑函数types的调整。 函数types调整导致带有边界的数组被解释为指向该types的指针。 我试图通过向编译器询问它通过模板静态地看到什么来find问题的解决scheme,但是我得到了更有趣的结果:

 #include <iostream> #include <type_traits> template<typename T, std::size_t N> void is_same( const T* _left, const char(&_right)[N] ) { typedef decltype(_left) LeftT; typedef decltype(_right) RightT; std::cout << std::is_same<LeftT,const char*>::value << std::endl; std::cout << std::is_same<LeftT,const char(&)[3]>::value << std::endl; std::cout << std::is_same<LeftT,const char(&)[4]>::value << std::endl; std::cout << std::is_same<RightT,const char*>::value << std::endl; std::cout << std::is_same<RightT,const char(&)[3]>::value << std::endl; std::cout << std::is_same<RightT,const char(&)[4]>::value << std::endl; } int main() { std::cout << std::boolalpha; is_same( "ab", "cd" ); return 0; } 

输出结果:真假假假假真假

编译器能够区分这种情况下的参数。

编辑1:这是一些更多的代码。 引入右值引用使得函数更加可区分。

 #include <iostream> // f template<typename _T> void f( _T, const char* ) { std::cout << "f( _T, const char* )" << std::endl; } template<std::size_t _kN> void f( int, const char(&)[_kN] ) { std::cout << "f( int, const char (&)[_kN] )" << std::endl; } // g template<typename _T> void g( _T, const char* ) { std::cout << "g( _T, const char* )" << std::endl; } template<std::size_t _kN> void g( int, const char(&&)[_kN] ) { std::cout << "g( int, const char (&&)[_kN] )" << std::endl; } // h template<std::size_t _kN> void h( int, const char(&)[_kN] ) { std::cout << "h( int, const char(&)[_kN] )" << std::endl; } template<std::size_t _kN> void h( int, const char(&&)[_kN] ) { std::cout << "h( int, const char (&&)[_kN] )" << std::endl; } int main() { //f( 7, "ab" ); // Error! //f( 7, std::move("ab") ); // Error! f( 7, static_cast<const char*>("ab") ); // OK //f( 7, static_cast<const char(&)[3]>("ab") ); // Error! //f( 7, static_cast<const char(&&)[3]>("ab") ); // Error! g( 7, "ab" ); // OK //g( 7, std::move("ab") ); // Error! g( 7, static_cast<const char*>("ab") ); // OK g( 7, static_cast<const char(&)[3]>("ab") ); // OK //g( 7, static_cast<const char (&&)[3]>("ab") ); // Error! h( 7, "ab" ); // OK (What? Why is this an lvalue?) h( 7, std::move("ab") ); // OK //h( 7, static_cast<const char*>("ab") ); // Error h( 7, static_cast<const char(&)[3]>("ab") ); // OK h( 7, static_cast<const char(&&)[3]>("ab") ); // OK return 0; }