查看以下代码(仅供娱乐)
namespace N
{
template<typename T>
struct K
{
};
}
template<typename T>
struct X
{
typename T::template K<T> *p; //should give error
//N::K<int> has no template member named `K`
};
int main()
{
X<N::K<int> > l;
}
代码在 g++(4.5.1) 和 Clang 上编译,而 Comeau 和 Intel C++ 给出(类似)错误。
我在 Comeau 上遇到的错误是:
"ComeauTest.c", line 13: error: class "N::K<int>" has no member "K"
typename T::template K<T> *p;
^
detected during instantiation of class "X<T> [with T=N::K<int>]" at
line 18
"ComeauTest.c", line 13: error: expected an identifier
typename T::template K<T> *p;
^
detected during instantiation of class "X<T> [with T=N::K<int>]" at
line 18
所以我的问题是“代码示例格式错误吗?”据我说"is"。这是否意味着这是 g++/Clang 中的另一个错误?
最佳答案
为什么 GCC 和 Clang 认为他们是对的
K
,即注入(inject)的类名,在 K<int>
范围内具有双重性质.您可以在没有模板参数的情况下使用它。那么它指的是K<int>
(到它自己的类型)。
它也可以跟一个模板参数列表。 IMO 可以合理地说您需要在它前面加上 template
由于解析器与 <
的歧义接下来。然后它引用由模板参数确定的指定类型。
因此它可以被视为成员模板和嵌套类型,具体取决于它是否后跟模板参数列表。当然,K
不是真正的成员模板。无论如何,注入(inject)类名的双重性质在我看来更像是一种黑客行为。
标准中有一个示例如下:
template <class T> struct Base { };
template <class T> struct Derived: Base<int>, Base<char> {
typename Derived::Base b; // error: ambiguous
typename Derived::Base<double> d; // OK
};
人们可能倾向于由此得出结论,即您可以省略 template
。 .标准说
For a template-name to be explicitly qualified by the template arguments, the name must be known to refer to a template.
我看不出这不适用于 T::K<T>
.如果 T
是依赖类型,那么你可以向后靠,因为你不知道 K
指在解析它时,所以要理解代码,你只需要能够在它前面加上 template
.注意 n3225 也有这个例子,但这不是一个缺陷:你可以正式离开 template
如果您在 C++0x 中查找模板自己的范围(称为“当前实例化”)。
所以到目前为止,Clang 和 GCC 都很好。
为什么 Comeau 是对的
为了更复杂,我们将不得不考虑 K<int>
的构造函数。 .隐式声明了一个默认构造函数和一个复制构造函数。一个名字K<int>::K
将引用 K<int>
的构造函数除非使用的名称查找将忽略函数(构造函数)名称。将typename T::K
忽略函数名? 3.4.4/3 谈到了详细的类型说明符,typename ...
是以下之一:
If the name is a qualified-id, the name is looked up according its qualifications, as described in 3.4.3, but ignoring any non-type names that have been declared.
但是,typename ...
使用不同的查找。 14.6/4 说
The usual qualified name lookup (3.4.3) is used to find the qualified-id even in the presence of typename.
3.4.3 的通常限定查找不会忽略非类型名称,如 14.6/4 所附示例所示。因此,我们将找到由 3.4.3.1/1a 指定的构造函数(仅在 not 忽略非类型时才发生这种情况的额外扭曲是由后来的缺陷报告添加的,所有流行的 C++03 编译器实现了):
If the nested-name-specifier nominates a class C, and the name specified after the nested-name-specifier, when looked up in C, is the injected-class-name of C (clause 9), the name is instead considered to name the constructor of class C. Such a constructor name shall be used only in the declarator-id of a constructor definition that appears outside of the class definition.
所以最后,我认为 Comeau 对这一点的诊断是正确的,因为您尝试将模板参数列表放在非模板上,并且还违反了最后一部分中引用的要求(您在其他地方使用了该名称)。
让我们通过派生的类访问注入(inject)的名称来更改它,这样就不会发生构造函数名称转换,并且您真的访问该类型,以便您真的 可以附加模板参数:
// just replace struct X with this:
template<typename T>
struct X
{
struct Derived : T { };
typename Derived::template K<T> *p;
};
现在一切都与 comeau 一起编译了!请注意,我已经针对这个确切的事情做了问题报告。见 Incorrect constructor name resolution .顺便说一句,如果您在 K
中声明默认构造函数,如果你使用 T::K<int>
,你可以看到 comeau 给出更好的错误信息。
"ComeauTest.c", line 13: error: overloaded function "N::K<T>::K [with T=int]" is
not a template
typename T::template K<T> *p;
关于c++ - g++/Clang 中的另一个错误? [C++ 模板很有趣],我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/4420828/