12

typename在测试clang是否需要时,我遇到了这种奇怪的行为。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;

通常,应编写一个限定 ID A<T1>::B<T2>(其中是从属名称) 。gcc/clang 的行为是否不正确,或者在这种特殊情况下,一般规则(引用如下)是否存在例外?A<T1>typename A<T1>::template B<T2>

可以说这A<T1>不是从属名称,或者是B<T2>指当前实例化的成员。但是,在解析类型说明符时,不可能知道当前实例化是A<T1>. 要求实现猜测A<T1>是当前实例化似乎是有问题的。

14.6 名称解析 [temp.res]

在模板声明或定义中使用并且依赖于模板参数的名称被假定为不命名类型,除非适用的名称查找找到类型名称或该名称由关键字 typename 限定。

14.2 模板特化的名称 [temp.names]

当成员模板特化的名称出现在后缀表达式之后.->之后,或者在限定 ID 中的嵌套名称说明符之后,并且后缀表达式或嵌套名称说明符中的对象或指针表达式出现在qualified-id 依赖于模板参数 (14.6.2) 但不引用当前实例化的成员 (14.6.2.1),成员模板名称必须以关键字 template 为前缀。否则,该名称被假定为命名非模板。

为了进一步调查 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实际上与声明匹配。

相反,我希望看到建议使用typenameor的诊断template

这证明了第一个示例中 clang 的行为是无意的假设。

4

3 回答 3

1

MSVC 是正确的。

我对 C++11 标准的阅读表明这typename是必需的。

如果没有typename关键字,则假定从属名称不命名类型。

14.6 名称解析 [temp.res]

2) 在模板声明或定义中使用并且依赖于模板参数的名称被假定为不命名类型,除非适用的名称查找找到类型名称或该名称由关键字 typename 限定。

3) 当qualified-id 旨在引用不是当前实例化成员的类型并且其nested-name-specifier 引用依赖类型时,应以关键字typename 作为前缀

7) 在类模板的定义中或在 declarator-id 之后的类模板成员的定义中,当引用声明类型的类模板的先前声明成员的名称时,不需要关键字 typename . [注意:此类名称可以使用非限定名称查找,类成员查找到当前实例化,或者当对象表达式的类型是当前实例化时类成员访问表达式查找

14.6.2.1 依赖类型 [temp.dep.type]

名称指的是当前实例化,如果它是

  • 在主类模板或主类模板成员的定义中,类模板的名称后跟主模板的模板参数列表(如下所述),括在 <>

A<T1>用于定义一个成员时A,它指的是当前实例化。在解析定义的f类型名时,A<T1>::可以通过类成员名查找到当前实例化中找到。

但是,当 C++ 解析器A<T1>在成员函数定义的返回类型中遇到 - 在 declarator-id 之前 - 它还没有遇到封闭类的名称。此时解析器无法确定是否A引用了封闭类。

出于这个原因——无论是否A<T1>命名当前实例——标准都不允许typename在声明符ID之前的类模板成员的定义中省略。

Vaughn Cato 的这个例子证明了 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'
{
}
于 2014-12-22T11:56:15.197 回答
1

clang 和 gcc 是正确的。

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

从 v14.6.2.1p4 开始:

一个名称是当前实例化的成员,如果它是

一个限定 ID,其中嵌套名称说明符引用当前实例化,并且在查找时引用当前实例化的至少一个成员

A<T1>::B<T2>是一个qualified-id,并且A<T1>::是引用当前实例化的nested-name-specifier。我们知道,A<T1>::指的是从 14.6.2.1p1 开始的当前实例化:

名称指的是当前实例化,如果它是

— 在主类模板或主类模板成员的定义中,类模板的名称后跟主模板的模板参数列表(如下所述),括在 <>(或等效的模板别名特化)中),

在您的代码中,我们定义了主类模板的成员,即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>确实指的是如上所述的当前实例化。

于 2013-08-21T12:54:46.073 回答
1

多年后,C++20 呈现了这种使用typenametemplate可选(尽管编译器还没有完成新规则的实现)。事实证明,编译器必须做一些类似于 GCC 和 Clang 已经在做的事情,以支持外联构造函数定义。

于 2021-08-06T00:18:44.510 回答