C++ GCC和clang(SFINAE)之间的过载解析行为差异
GCC接受以下代码:C++ GCC和clang(SFINAE)之间的过载解析行为差异,c++,templates,gcc,clang,overload-resolution,C++,Templates,Gcc,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); } 建议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);
}
建议GCC知道“只有函数类型及其模板参数类型的直接上下文中的无效类型和表达式才能导致推断失败”。此示例与原始示例之间的区别在于原始示例中存在第二个函数参数,在此基础上,GCC在尝试对返回类型执行替换之前抛出模板候选。我认为问题是,GCC是否正确地按照这个顺序进行操作,或者在考虑参数类型中的匹配之前,它是否应该尝试对返回类型执行替换
更新:Luc Danton的回答让我相信clang拒绝代码是正确的。因此,C++03将此措辞作为通常称为SFINAE的规范的一部分(14.8.2模板参数扣除[临时扣除],第2段): […]如果在模板参数或 函数模板的函数类型导致类型无效,类型推断失败。[……] 相比之下,C++11使用了以下措辞(14.8.2模板参数扣除[临时扣除],第8段): 如果替换导致无效的类型或表达式,则类型推断将失败。[…]只有函数类型及其模板参数类型的直接上下文中的无效类型和表达式,才能导致推断失败。[……] 重点是我的。据我所知,C++11中的措辞得到了改进,明确地概述了什么应该导致SFINAE(所谓的软错误)和什么不应该导致SFINAE(硬错误)。这是当时正在进行的讨论的一个例子,导致了当前的规则
考虑到这一点,根据C++03,一个实现可能正确地接受您的代码(甚至可能应该)。但是,我怀疑C++11实现应该拒绝它:错误(
int::type
)在meta
的上下文中,而不是foo
的上下文中,在这里Clang和g++都是正确的
根据Luc Danton的回答,编译器可以为foo
函数模板推导T=int
。然后,在将该值替换为foo
声明的过程中,需要隐式实例化meta
,这会导致替换直接上下文之外的错误(因此SFINAE不适用)。所以Clang拒绝这个代码是正确的
然而,[temp.inst]p7说:
如果重载解析过程可以在不实例化类模板定义的情况下确定要调用的正确函数,则未指定该实例化是否实际发生
由于非模板
foo
与调用中的参数完全匹配,编译器可以确定函数模板专用化永远不会是最佳可行函数,因此不需要执行参数推导和替换。因此,g++不拒绝此代码是正确的。foo(int,int)
是完全匹配的,所以我相信GCC在这一点上是正确的。您是在C++03还是C++11模式下?什么版本的GCC?嘿,这是一个问题,如果GCC是正确的?请参阅我的编辑问题。我相信GCC接受该代码的原因不是因为它遵循C++03规则(如编辑中的示例所示),而是因为第二个参数类型不匹配,它抛出了模板候选,在对返回类型执行替换之前,我想知道,当函数调用存在“精确”匹配时,为什么实现会尝试实例化模板。如果这是一个实现应该做的,那么这是否意味着13.3.1候选函数和参数列表[over.match.funcs]中的函数调用将调用template T foo(T,T)
,而不是int foo(int,int)
“在每种情况下,如果候选函数是函数模板,则使用模板参数推断(14.8.3、14.8.2)生成候选函数模板专门化。然后,按照通常的方式将这些候选函数作为候选函数处理。给定的名称可以引用一个或多个函数模板,也可以引用一组重载的非模板函数。在这种情况下,从每个函数模板生成的候选函数将与一组非模板候选函数相结合。@HighCommander4“这些候选函数将作为候选函数处理”表明存在排序。@Nawaz这些规则控制重载解析。每个候选函数都会被考虑(在涉及函数模板的情况下,会实例化相关的专门化,这可能会导致SFINAE或产生硬错误)在选取特定函数之前。Hmmm。不清楚跳过类模板实例化是否可以绕过确定函数模板专门化的类型,或者只是跳过不相关的类模板成员。@aschepper这里的标准似乎很清楚:重载重新加载期间触发的任何类模板实例化都允许使用它解决方案。非常有趣!你认为标准没有明确说明这一点是一个好主意吗?这似乎创建了一个相当大的程序集,在一个符合标准的实现下编译,而不是在另一个实现下编译。
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);
^
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);
}