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");
}

那么,在范围内fPa<double>::Pe<BBB>不是依赖名呢?

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

毕竟,为什么这两个引用的编译器会有这种不同的行为呢?

任何人都可以澄清解决这个难题吗?

4

1 回答 1

3

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

qualified-id旨在引用不是当前实例化(14.6.2.1)成员的类型并且其nested-name-specifier引用依赖类型时,应以关键字为前缀typename,形成类型名-说明符。如果typename-specifier中的qualified-id不表示类型,则程序格式错误。

这个问题围绕着两个qualified-id

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

首先,什么是依赖类型?根据[temp.dep.type]:

一个类型是依赖的,如果它是
- 一个模板参数,
- 一个未知特化的成员,
- 一个嵌套类或枚举,它是当前实例化的依赖成员,
- 一个 cv 限定类型,其中 cv 非限定类型是依赖的,
— 从任何依赖类型构造的复合类型,
— 元素类型依赖或绑定(如果有)依赖于值的数组类型,
模板名称是模板参数或任何模板参数都是依赖类型或依赖于类型或值的表达式,或者
— 由decltype(表达式) 表示,其中表达式取决于类型(14.6.2.2)。

Pa<double>(第一个示例的嵌套名称说明符)不是依赖类型,因为它不符合任何要点。由于我们不符合该标准,因此我们不需要为typename关键字添加前缀。

Pa<AAA>然而,它一个依赖类型,因为它是一个简单模板 ID,其中一个模板参数是一个依赖类型(AAA因为它是一个模板参数,所以它通常是一个依赖类型)。

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

一个名称指的是当前实例化,如果它是
- [...]
- 在主类模板或主类模板的成员的定义中,类模板的名称后跟主模板的模板参数列表(如下所述)包含在 <> 中(或等效的模板别名特化)”——在类模板的嵌套类的定义中,作为当前实例化成员引用的嵌套类的名称,或

在这种情况下,当前实例化是Pa<AAA>(或,也,Pa)。和:

一个名称是当前实例化的成员,如果它是 [...] 一个限定 ID,其中嵌套名称说明符指代当前实例化,并且在查找时指代一个类的至少一个成员即当前实例化或其非依赖基类。

Pe当前实例化的成员也是如此。因此,虽然nested-name-specifier ofPa<AAA>::Pe<int>是依赖类型,但它是当前实例化成员的类型,因此您不需要关键字typename。请注意,它本身Pa<AAA>::Pe<int> 一个依赖类型(它是一个嵌套类,是当前实例化的依赖成员),但这本身确实意味着该typename关键字是必需的。

gcc 在这里不接受 typename 的事实:

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

因为它想要

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

是一个错误。

于 2015-09-03T14:40:48.053 回答