GCC和clang(SFINAE)之间的过载分辨率行为差异

HC4*_*ica 18 c++ gcc templates clang overload-resolution

GCC接受以下代码:

template <typename T>
struct meta
{
    typedef typename T::type type;
};

struct S {};

template <typename T>
typename meta<T>::type foo(T, S);

int foo(int, int);      

int main()
{
    foo(0, 0);
}
Run Code Online (Sandbox Code Playgroud)

但是clang拒绝它并出现以下错误:

test.cpp:4:22: error: type 'int' cannot be used prior to '::' because it has no members
    typedef typename T::type type;
                     ^
test.cpp:10:10: note: in instantiation of template class 'meta<int>' requested here
typename meta<T>::type foo(T, S);
         ^
test.cpp:10:24: note: while substituting deduced template arguments into function template 'foo' [with T = int]
typename meta<T>::type foo(T, S);
                       ^
Run Code Online (Sandbox Code Playgroud)

这似乎表明GCC和clang在重载决策期间执行某些操作的顺序不同.尝试实例化候选模板的返回类型之前, GCC似乎抛弃了候选模板,因为第二个参数(Svs. int)中的类型不匹配,而clang似乎反过来这样做.

谁是对的?

我相信这个问题对模板库的作者有重要意义.具体来说,如果clang是正确的,模板的作者foo将不得不做额外的工作来将错误转换为替换失败.

编辑:请注意,以下稍微简单的示例被GCC和clang拒绝,但有类似的错误:

template <typename T>
struct meta
{
    typedef typename T::type type;
};

template <typename T>
typename meta<T>::type foo(T);

int foo(int);      

int main()
{
    foo(0);
}
Run Code Online (Sandbox Code Playgroud)

建议GCC知道"只有函数类型及其模板参数类型的直接上下文中的无效类型和表达式才会导致演绎失败".此示例与原始示例之间的区别在于原始示例中存在第二个函数参数,在此基础上,GCC在尝试对返回类型执行替换之前抛出模板候选.我认为问题是,GCC是否以该顺序执行是正确的,还是应该在考虑参数类型中的匹配之前尝试对返回类型执行替换.

更新:Luc Danton的回答使我确信clang拒绝代码是正确的.因此我提交了GCC错误.

Ric*_*ith 11

Clang和g ++都是正确的.

每吕克丹东的回答,编译器允许推断T = intfoo函数模板.然后,在将该值替换为声明时foo,需要隐式实例化meta<int>,并且在替换的直接上下文之外导致错误(因此SFINAE不适用).因此,Clang拒绝此代码是正确的.

但是,[temp.inst] p7说:

如果重载解析过程可以在不实例化类模板定义的情况下确定要调用的正确函数,则未指定该实例化是否实际发生.

因为非模板foo与调用中的参数完全匹配,所以编译器可以确定函数模板特化永远不会是最好的可行函数,因此不需要执行参数推导和替换.因此,g ++是不正确的拒绝此代码.


Luc*_*ton 5

C++ 03使用这种措辞作为规范的一部分,通常称为SFINAE(14.8.2模板参数推导[temp.deduct],第2段):

[...]如果模板参数或函数模板的函数类型中的替换导致类型无效,则类型推导失败.[...]

相比之下,C++ 11使用了这个措辞(14.8.2模板参数推导[temp.deduct],第8段):

如果替换导致无效的类型或表达式,则类型推导失败.[...]只有函数类型的直接上下文中的无效类型和表达式及其模板参数类型才会导致演绎失败.[...]

重点是我的.据我所知,C++ 11中的措辞得到了改进,明确地概述了应该导致SFINAE(所谓的软错误)和不应该(硬错误)的结果.这篇2008年的论文是当时正在进行的讨论的一个例子,并导致了现行规则.

考虑到这一点,可能会出现这样的情况:根据C++ 03,一个实现可能是正确的接受你的代码(甚至可能它应该).我怀疑C++ 11实现应该拒绝它:error(int::type)是在上下文中meta<int>,而不是foo<int>.

  • @ HighCommander4来自13.3.1候选函数和参数列表[over.match.funcs],第7段:"在候选者是函数模板的每种情况下,使用模板参数推导生成候选函数模板特化(14.8.3,14.8) .2).那些候选者然后以通常的方式作为候选函数处理.给定的名称可以引用一个或多个函数模板,也可以引用一组重载的非模板函数.在这种情况下,候选函数由每个函数模板都与一组非模板候选函数相结合." (3认同)
  • @Nawaz这些规则控制着重载决策.考虑每个候选人(并且在涉及功能模板的情况下,实例化相关专业化,这可能导致SFINAE或产生硬错误)*在选择特定功能之前*. (2认同)