在测试clang是否需要typename
时,我遇到了这种奇怪的行为。 clang和gcc都接受此代码,而msvc拒绝它。
template<class T1>
struct A
{
template<class T2>
struct B
{
static B f;
static typename A<T2>::template B<T1> g;
};
};
template<class T1>
template<class T2>
typename A<T2>::template B<T1> // ok, typename/template required
A<T1>::B<T2>::g;
template<class T1>
template<class T2>
A<T1>::B<T2> // clang/gcc accept, msvc rejects missing typename
A<T1>::B<T2>::f;
通常,限定标识A<T1>::B<T2>
(其中A<T1>
是从属名称)应写为typename A<T1>::template B<T2>
。 gcc / clang的行为是否不正确,或者在这种特殊情况下是否存在一般规则的例外(引用如下)?
可以认为A<T1>
不是依赖名称,或者B<T2>
是指当前实例化的成员。但是,在解析类型说明符时,不可能知道当前实例化是A<T1>
。要求实现猜测A<T1>
是当前的实例化似乎是有问题的。
14.6名称解析[temp.res]
模板声明或定义中使用的名称,取决于模板参数 假定不命名类型,除非适用的名称查找找到类型名称或名称是合格的 通过关键字typename。
14.2模板专精的名称[temp.names]
当成员模板专精的名称出现在后缀表达式中的
.
或->
之后或之后 限定id中的嵌套名称说明符,以及后缀表达式的对象或指针表达式 qualified-id中的nested-name-specifier取决于模板参数(14.6.2),但不引用a 当前实例化的成员(14.6.2.1),成员模板名称必须以关键字为前缀 模板。否则,假定该名称命名非模板。
为了进一步调查clang在这里做了什么,我也试过了:
template<class T1>
struct C
{
template<class T2>
struct D
{
static typename A<T1>::template B<T2> f;
static typename A<T1>::template B<T2> g;
};
};
template<class T1>
template<class T2>
typename A<T1>::template B<T2> // ok, typename/template required
C<T1>::D<T2>::f;
template<class T1>
template<class T2>
A<T1>::B<T2> // clang rejects with incorrect error
C<T1>::D<T2>::g;
Clang给出error: redefinition of 'g' with a different type
,但g
的类型实际上与声明匹配。
我希望看到一个诊断建议使用typename
或template
。
这可以归结为假设第一个例子中的铿锵行为是无意的。
答案 0 :(得分:1)
clang和gcc是正确的。
编译器知道A<T1>::B<T2>
引用类型,B<T2>
是模板,A<T1>::B<T2>::f
是当前实例化的成员。因此,typename
和template
关键字不是必需的。
从v14.6.2.1p4开始:
如果名称是
,则该名称是当前实例化的成员nested-name-specifier引用的qualified-id 当前实例化,当查找时,指的是至少一个 当前实例化的成员
A<T1>::B<T2>
是一个限定id,A<T1>::
是嵌套名称说明符,它引用当前的实例化。我们知道A<T1>::
引用了14.6.2.1p1的当前实例化:
名称是指当前实例化,如果它是
- 在定义主要类模板或成员 主类模板,后面跟着的类模板的名称 主模板的模板参数列表(如下所述) 括在&lt;&gt;中(或等效的模板别名专门化),
在您的代码中,我们定义了主类模板的成员,即A<T1>::B<T2>::f
,A<T1>
是类模板的名称,后跟主模板的模板参数列表
在你的问题中,你说However, at the point of parsing the type-specifier it's not possible to know that the current instantiation is A<T1>
。但是,我不能这样做,因为名称A<T1>
确实引用了上面描述的当前实例化。
答案 1 :(得分:1)
MSVC是正确的。
我阅读C ++ 11标准表明需要typename
。
如果没有typename
关键字,则假定从属名称不命名类型。
14.6名称解析[temp.res]
2) 模板声明或定义中使用的名称,取决于模板参数 假定不命名类型,除非适用的名称查找找到类型名称或名称是合格的 通过关键字typename。
3) 当qualified-id旨在引用不是当前实例化成员的类型时 并且它的嵌套名称说明符引用依赖类型,它应以关键字typename为前缀
7) 在类模板的定义内或定义在类模板的成员之后 declarator-id ,在引用先前声明的名称时不需要关键字typename 声明类型的类模板的成员。 [注意:这些名称可以使用不合格的名称找到 查找,类成员查找当前实例化或类成员访问 表达式查找当对象表达式的类型是当前实例化时
14.6.2.1依赖类型[temp.dep.type]
名称是指当前实例化,如果它是
- 在主类模板的定义或主类模板的成员中,名称 类模板后跟主模板的模板参数列表(如下所述) 括在&lt;&gt;
中
在A<T1>
成员的定义中使用A
时,它指的是当前实例化。解析f
的定义时,可以通过类成员名称查找到当前实例化来找到由A<T1>::
限定的类型名称。
但是,当C ++解析器在成员函数定义的返回类型中遇到A<T1>
时 - 在declarator-id之前 - 它还没有遇到封闭类的名称。此时,解析器无法确定A
是否引用了封闭类。
出于这个原因 - 无论A<T1>
是否命名当前实例化 - 标准都不允许在声明者id之前在类模板成员的定义中省略typename
。
Vaughn Cato的这个example表明Clang / GCC的行为不一致,在类似的场景中需要typename
:
template <typename T>
struct A {
typedef int X;
X f();
};
template <typename T>
A<T>::X A<T>::f() // error: missing 'typename'
{
}
答案 2 :(得分:1)
多年后,C++20 将 typename
和 template
的这种使用变为可选(尽管编译器并未完成新规则的实现)。事实证明,编译器必须做一些类似于 GCC 和 Clang 已经在这里做的事情来支持外部构造函数定义。