【问题标题】:Can typename be omitted in the type-specifier of an out of line member definition?行外成员定义的类型说明符中可以省略 typename 吗?
【发布时间】:2013-08-23 01:36:06
【问题描述】:

在测试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;

一般来说,合格的 id A&lt;T1&gt;::B&lt;T2&gt;(其中A&lt;T1&gt; 是一个从属名称)应该写成typename A&lt;T1&gt;::template B&lt;T2&gt;。 gcc/clang 的行为是否不正确,或者在这种特殊情况下,一般规则(下面引用)是否存在例外?

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

14.6 名称解析 [temp.res]

在模板声明或定义中使用并且依赖于模板参数的名称是 假定不命名类型,除非适用的名称查找找到类型名称或名称是合格的 通过关键字类型名。

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

当成员模板特化的名称出现在后缀表达式中的 .-&gt; 之后或之后 限定 ID 中的嵌套名称说明符,以及后缀表达式的对象或指针表达式或 限定 ID 中的嵌套名称说明符取决于模板参数 (14.6.2) 但不引用 当前实例化的成员(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 的诊断。

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

【问题讨论】:

  • 我个人会添加 typename... 但现在还没有动力深入研究标准 :)
  • @dribeas 不用担心;)。您现在一定已经厌倦了所有这些语言律师问题!
  • 无论是对还是错,只要找到 gcc 和 clang 接受的 any 代码,您都应该获得支持,但 VC++ 由于缺少 typename 而拒绝.

标签: c++ templates language-lawyer


【解决方案1】:

clang 和 gcc 是正确的。

编译器知道A&lt;T1&gt;::B&lt;T2&gt; 是一个类型,B&lt;T2&gt; 是一个模板,A&lt;T1&gt;::B&lt;T2&gt;::f 是当前实例化的成员。因此,typenametemplate 关键字不是必需的。

从 v14.6.2.1p4 开始:

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

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

A&lt;T1&gt;::B&lt;T2&gt; 是一个限定 ID,A&lt;T1&gt;:: 是嵌套名称说明符,它引用当前实例化。我们知道A&lt;T1&gt;:: 指的是来自 14.6.2.1p1 的当前实例化:

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

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

在您的代码中,我们定义了主类模板的成员,即A&lt;T1&gt;::B&lt;T2&gt;::fA&lt;T1&gt; 是类模板的名称,后跟主模板的模板参数列表。

在你的问题中,你说However, at the point of parsing the type-specifier it's not possible to know that the current instantiation is A&lt;T1&gt;。但是,我无法理解这一点,因为名称 A&lt;T1&gt; 确实指的是上述当前实例化。

【讨论】:

  • 需要typename(和template)的规则使得解析器可以知道依赖名称是类型还是模板。解析器通过标记流前进,并且必须在遇到标记时决定名称是否是类型(或模板)。为了确定 A&lt;T1&gt;::B 是否在没有 typename 关键字的情况下命名类模板,解析器必须向前看(可能无限)数量的标记,以发现 A&lt;T1&gt; 是当前实例化。
  • @willj:虽然您评论的第一部分是正确的,但第二部分不是。名称的某些形式标识当前实例化。在这种情况下,A&lt;T1&gt;::A&lt;T1&gt; 都命名了当前实例化。编译器通过语法识别当前实例化,示例见 14.6.2.1p3。
  • 请参阅this example 和错误消息。编译器知道 A&lt;T1&gt;::B 命名一个类模板,因为它已经查找过了。
  • 如果你说的是真的,为什么这不起作用? coliru.stacked-crooked.com/…
  • 您断言 template&lt;class T1&gt; template&lt;class T2&gt; A&lt;T1&gt; 命名当前实例化,这似乎与 clang 所做的假设相同。在我的第二个示例中,当紧跟 ::B&lt;T2&gt; C&lt;T1&gt;::D&lt;T2&gt;::g 时会发生什么?在这种情况下,clang 会中断,因为 A&lt;T1&gt; 命名当前实例的假设被证明是不正确的。
【解决方案2】:

MSVC 是正确的。

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

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

14.6 名称解析 [temp.res]

2) 在模板声明或定义中使用并且依赖于模板参数的名称是 假定不命名类型,除非适用的名称查找找到类型名称或名称是合格的 通过关键字类型名。

3) 当限定 ID 旨在引用不是当前实例化成员的类型时 并且它的nested-name-specifier 指的是一个依赖类型,它应该以关键字typename 为前缀

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

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

一个名字指的是当前的实例化如果它是

  • 在主类模板或主类模板成员的定义中, 类模板后跟主模板的模板参数列表(如下所述) 包含在

A&lt;T1&gt; 用于定义A 的成员时,它指的是当前实例化。在解析f 的定义时,可以通过在当前实例中查找类成员名称找到由A&lt;T1&gt;:: 限定的类型名称。

但是,当 C++ 解析器在成员函数定义的返回类型中遇到 A&lt;T1&gt; 时 - 在声明符 ID 之前 - 它还没有遇到封闭类的名称。此时解析器无法确定A 是否引用了封闭类。

出于这个原因 - 无论 A&lt;T1&gt; 是否命名当前实例 - 标准都不允许在声明符 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'
{
}

【讨论】:

    【解决方案3】:

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

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2022-01-14
      • 2023-03-16
      • 2014-09-30
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2021-12-27
      • 1970-01-01
      相关资源
      最近更新 更多