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);
}
但是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);
^
这似乎暗示了GCC和clang在重载解析期间执行某些操作的顺序不同。由于第二个参数(S
与int
)中的类型不匹配,GCC似乎抛弃了候选模板,而试图实例化候选模板的返回类型,虽然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);
}
建议GCC知道“只有函数类型及其模板参数类型的直接上下文中的无效类型和表达式才会导致演绎失败”。此示例与原始示例之间的区别在于原始示例中存在第二个函数参数,在此基础上,GCC在尝试对返回类型执行替换之前抛出模板候选。我认为问题是,GCC是否以该顺序执行是正确的,还是应该在考虑参数类型中的匹配之前尝试对返回类型执行替换。
更新:Luc Danton的回答使我确信clang拒绝代码是正确的。我因此filed a GCC bug。
答案 0 :(得分:10)
这里的Clang和g ++都是正确的。
Per Luc Danton的回答是,允许编译器为T = int
函数模板推导出foo
。然后,在将该值替换为foo
的声明期间,需要meta<int>
的隐式实例化,并且它会导致替换的直接上下文之外的错误(因此SFINAE不适用) 。因此,Clang拒绝此代码是正确的。
然而, [temp.inst] p7 说:
如果重载解析过程可以在不实例化类模板定义的情况下确定要调用的正确函数,则未指定该实例化是否实际发生。
因为非模板foo
与调用中的参数完全匹配,所以编译器可以确定函数模板特化永远不会是最好的可行函数,因此不需要执行参数推导和替换。因此,g ++不能拒绝此代码是正确的。
答案 1 :(得分:5)
C ++ 03使用此措辞作为规范的一部分,通常称为SFINAE(14.8.2模板参数推断[temp.deduct],第2段):
[...]如果在模板参数或中的替换 函数模板的函数类型导致类型无效,类型推导失败。 [...]
相比之下,C ++ 11使用了这个措辞(14.8.2模板参数演绎[temp.deduct],第8段):
如果替换导致无效的类型或表达式,则类型推导失败。 [...]只有函数类型的直接上下文中的无效类型和表达式及其模板参数类型才会导致演绎失败。 [...]
重点是我的。据我所知,C ++ 11中的措辞得到了改进,明确地概述了应该导致SFINAE(所谓的软错误)和不应该(硬错误)的结果。 This 2008 paper是当时正在进行的讨论的一个例子,并导致了当前的规则。
考虑到这一点,可能会出现这样的情况:根据C ++ 03,一个实现可能是正确的接受你的代码(甚至可能它应该)。我怀疑C ++ 11实现应该拒绝它:错误(int::type
)位于meta<int>
的上下文中,而不是foo<int>
。