21

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由于第二个参数 ( vs. 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 拒绝代码是正确的。我相应地提交了一个 GCC 错误

4

2 回答 2

11

Clang 和 g++ 在这里都是正确的。

根据 Luc Danton 的回答,允许编译器为T = int函数foo模板进行推断。然后,在将该值替换为 的声明期间foo,需要隐式实例化meta<int>,这会导致替换的直接上下文之外的错误(因此 SFINAE 不适用)。所以 Clang 拒绝这个代码是正确的。

但是,[temp.inst]p7说:

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

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

于 2013-07-07T06:00:16.267 回答
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 实现应该拒绝它:错误 ( int::type) 在 的上下文中meta<int>,而不是在foo<int>.

于 2012-08-18T05:17:10.727 回答