关于C++:再次在类型名和模板关键字上

Again on typename and template keywords

我已经仔细阅读了许多关于这个主题的答案,但是我仍然不能确切地知道这两个关键字在作为嵌套模板类成员的非模板函数的作用域中是什么时候需要或不需要。

我的参考编译器是GNU G++4.9.2和Clang3.5.0。

在我把Embedded放在下面的代码中,它们的行为几乎没有什么不同试图解释发生了什么的评论。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
#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::Pe是否是从属名称?

那么Pa::Pe呢?

而且,毕竟,为什么这两个引用的编译器的行为不同?

有人能澄清解决这个难题吗?


[温度响应]中的重要规则是:

When a qualified-id is intended to refer to a type that is not a member of the current instantiation (14.6.2.1)
and its nested-name-specifier refers to a dependent type, it shall be prefixed by the keyword typename, forming
a typename-specifier. If the qualified-id in a typename-specifier does not denote a type, the program is ill-formed.

此问题将在两个限定ID之间撤销:

1
2
Pa<double>::Pe<BBB>
Pa<AAA>::Pe<int>

首先,什么是从属类型?根据[温度深度类型]:

A type is dependent if it is
— a template parameter,
— a member of an unknown specialization,
— a nested class or enumeration that is a dependent member of the current instantiation,
— a cv-qualified type where the cv-unqualified type is dependent,
— a compound type constructed from any dependent type,
— an array type whose element type is dependent or whose bound (if any) is value-dependent,
— a simple-template-id in which either the template name is a template parameter or any of the template
arguments is a dependent type or an expression that is type-dependent or value-dependent, or
— denoted by decltype(expression), where expression is type-dependent (14.6.2.2).

Pa(第一个示例的嵌套名称说明符)不是依赖类型,因为它不适合任何项目符号点。因为我们不满足这个标准,所以不需要给typename关键字加前缀。

但是,Pa是依赖类型,因为它是一个简单的模板ID,其中一个模板参数是依赖类型(AAA是依赖类型,因为它是模板参数)。

那么,"当前实例化的成员"是什么?

A name refers to the current instantiation if it is
— [...]
— in the definition of a primary class template or a member of a primary class template, the name of the class template followed by the template argument list of the primary template (as described below) enclosed in <> (or an equivalent template alias specialization)"
— in the definition of a nested class of a class template, the name of the nested class referenced as a
member of the current instantiation, or

在这种情况下,当前的实例化是Pa(或者,也是Pa)。还有:

A name is a member of the current instantiation if it is [...] A qualified-id in which the nested-name-specifier refers to the current instantiation and that, when looked up, refers to at least one member of a class that is the current instantiation or a non-dependent base class thereof.

所以Pe是当前实例化的成员。因此,虽然Pa::Pe的嵌套名称说明符是依赖类型,但它是当前实例化的成员类型,因此不需要关键字typename。注意,Pa::Pe本身是一个依赖类型(它是一个嵌套类,是当前实例化的依赖成员),但它本身并不意味着需要typename关键字。

GCC不接受这里的类型名:

1
/*typename*/ Pa<AAA>::Pe<int> qq;

因为它想要

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

是一个bug。