可以在out-line成员定义的类型说明符中省略typename吗?

时间:2013-08-20 20:41:36

标签: c++ templates language-lawyer

在测试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的类型实际上与声明匹配。

我希望看到一个诊断建议使用typenametemplate

这可以归结为假设第一个例子中的铿锵行为是无意的。

3 个答案:

答案 0 :(得分:1)

clang和gcc是正确的。

编译器知道A<T1>::B<T2>引用类型,B<T2>是模板,A<T1>::B<T2>::f是当前实例化的成员。因此,typenametemplate关键字不是必需的。

从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>::fA<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 将 typenametemplate 的这种使用变为可选(尽管编译器并未完成新规则的实现)。事实证明,编译器必须做一些类似于 GCC 和 Clang 已经在这里做的事情来支持外部构造函数定义。