再谈typename和template关键字

3

我仔细阅读了许多关于这个主题的答案,但是我仍然无法确定在嵌套模板类的非模板函数作用域中确切地需要或不需要这两个关键字。

我的参考编译器是GNU g++ 4.9.2和clang 3.5.0。

它们在以下代码中表现出了不同的行为,我在其中加入了嵌入式注释来尝试解释发生了什么。

#include <iostream>

// a simple template class with a public member template struct
template <class Z>
class Pa
{
// anything
public:
    template <class U>
    struct Pe  // a nested template
    {
        // anything
        void f(const char *); // a non-template member function
    };

    template <class U> friend struct Pe;
};

// definition of the function f
template <class AAA>
template <class BBB>
void Pa<AAA> :: Pe<BBB> :: f(const char* c)
{
    Pa<AAA> p; // NO typename for both clang and GNU...

    // the following line is ACCEPTED by both clang and GNU
    // without both template and typename keywords
    // However removing comments from typename only
    // makes clang still accepting the code while GNU doesn't
    // accept it anymore. The same happens if the comments   of template
    // ONLY are removed.
    //  
    // Finally both compilers accept the line when both typename AND
    // template are present...
    /*typename*/ Pa<AAA>::/*template*/ Pe<BBB> q;

    // in the following clang ACCEPTS typename, GNU doesn't:
    /*typename*/ Pa<AAA>::Pe<int> qq;

    // the following are accepted by both compilers
    // no matter whether both typename AND template
    // keywords are present OR commented out:
    typename Pa<int>::template Pe<double> qqq;
    typename Pa<double>::template Pe<BBB>  qqqq;
    std::cout << c << std::endl; // just to do something...
}

int main()
{
    Pa<char>::Pe<int> pp;
    pp.f("bye");
}

那么,在 f 的作用域中,Pa<double>::Pe<BBB> 是一个相关名称吗?

那么 Pa<AAA>::Pe<int> 呢?

最后,为什么这两个编译器的行为不同呢?

有人能解释一下这个谜题吗?


据我理解你的问题,你想知道为什么它们表现不同,而在这种情况下,“hardly”是错误的词。实际上,你所描述的是它们几乎没有表现相同 ;) - 463035818_is_not_a_number
GCC 似乎无法处理名称,只要你加入 typenametemplate,就会“忘记” P<AAA> 是周围的模板。我认为 GCC 在这里有一个错误,并建议提交 PR。这里不需要 typenametemplate - Johannes Schaub - litb
1
最近放宽了允许在严格不需要的情况下使用 typename 的规则。可能是在发布 gcc 4.9 后。可以解释一下第三个例子吗? - Bo Persson
1
Pa<double>::Pe<BBB>是一个依赖名称,因为它的类型取决于BBB。但这并不意味着您必须插入typename。对于Pa<AAA>::Pe<int>Pa<AAA>也是如此。在所有这些情况下,编译器可以自行确定Pe是一个模板,而Pe<int>/Pe<BBB>是一个类型。请参见https://dev59.com/dHRB5IYBdhLWcg3weXOX#17579889。 - Johannes Schaub - litb
1个回答

3
在[temp.res]中的重要规则是:

当带有nested-name-specifierqualified-id意图引用当前实例化不是其成员的类型(14.6.2.1),并且其nested-name-specifier指向一个相关类型时,它必须以关键字typename为前缀形成一个typename-specifier。如果typename-specifier中的qualified-id不能表示类型,则程序是非法的。

问题围绕两个qualified-id展开:
Pa<double>::Pe<BBB>
Pa<AAA>::Pe<int>

首先,什么是依赖类型?根据[temp.dep.type]:
- 如果它是模板参数、 - 如果它是未知特化的成员、 - 如果它是当前实例化的依赖成员的嵌套类或枚举、 - 如果它是带cv修饰的类型,而其cv非修饰类型是依赖类型、 - 如果它是由任何依赖类型构造的复合类型、 - 如果它是数组类型,其元素类型是依赖类型或其大小(如果有)是值依赖的、 - 如果在简单模板ID中模板名称是模板参数或任何模板参数是依赖类型或类型依赖性表达式、 - 或者如果它是decltype(expression),其中expression是类型依赖性(14.6.2.2)。 Pa (第一个示例的嵌套名规定符)不是依赖类型,因为它不符合任何要点。既然我们不符合这个标准,我们就不需要在typename关键字前加前缀。
然而,Pa是依赖类型,因为它是简单模板ID,其中一个模板参数是依赖类型(AAA是依赖类型因为它是模板参数)。
那么,“当前实例化的成员”是什么?
如果它符合以下要求,名称将引用“当前实例化”:
- ..., - 在主类模板或主类模板的成员定义中,类模板名称后跟封闭在<>中的主模板的模板参数列表(如下所述)(或等效的模板别名特化) - 在类模板的嵌套类的定义中,被作为当前实例化的成员引用的嵌套类的名称,
在这种情况下,当前实例化是Pa(或者也可以是Pa)。并且:
如果一个限定符ID引用当前实例化,并且在查找时引用类的至少一个成员是当前实例化或其非依赖基类的成员,则该名称是当前实例化的成员。
因此,Pe 是当前实例的成员。因此,尽管 Pa<AAA>::Pe<int> 的嵌套名称限定符是一个相关类型,但它是当前实例的成员类型,因此您不需要使用关键字 typename。请注意,Pa<AAA>::Pe<int> 本身是一个相关类型(它是当前实例的成员的嵌套类),但这并不意味着需要使用 typename 关键字。
事实上,gcc 在这里不接受 typename 的原因是:
/*typename*/ Pa<AAA>::Pe<int> qq;

因为它需要

typename Pa<AAA>::template Pe<int> qq;

是一个错误。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接