我通过编译以下示例遇到了以下问题:

template <int N>
class Matrix {
public:
    template <int Idx>
    int head() {
        return Idx;
    }
};

template <typename T>
class Test {
    static constexpr int RayDim = 3;
public:
    int func() const {
        Matrix<RayDim> yF;
        return yF.head<1>();
        //        ^ is template keyword required here?
    }
};

struct Empty {};

void test() {
    Test<Empty> t;
}

链接到编译器资源管理器:
https://godbolt.org/z/js4XaP

该代码使用GCC 9.2和MSVC 19.22进行编译,但不能使用clang 9.0.0进行编译。 Clang指出需要template关键字。如果将static constexpr int RayDim = 3;移入int func() const,则clang接受它。

如代码块中的注释所述,yF.head<1>()是否需要template关键字?

最佳答案

此处不需要template关键字,因此clang不正确,无法拒绝该程序。
对于C++ 17草案N4659和当前链接的C++ 20草案,下面的所有C++标准部分和段落编号以及引号均相同。
命名成员模板时,在template.-> token 之后需要::[temp.names]/4中。该段首先列出了不允许使用关键字的情况,然后列出了可选的且没有区别的情况,然后:

“未知特化的成员”是非“当前实例化”的从属类型的成员。因此,问题是Matrix<RayDim>是否为从属类型。为此,我们来看[temp.dep.type]/9:
Matrix<RayDim>显然不是模板参数,任何类型的成员,cv限定,数组类型,函数类型或不是decltype指定的。它是一种复合类型,但是它仅使用模板名称和表达式,因此不会从任何其他类型构造。
剩下的就是简单模板ID的情况。模板名称Matrix不是模板参数。模板参数RayDim是一个表达式,因此现在来看它是类型依赖还是值依赖。
[temp.dep.expr]中定义了“取决于类型”。只有第3段可以适用于像RayDim这样的单独标识符:
RayDim当然不包含任何__func__,template-id,conversion-function-id,nested-name-specifier或qualified-id。名称查找可找到类模板的静态成员声明。 RayDim的声明当然不是模板参数,成员函数或结构化绑定(bind)声明,并且其类型const int肯定不是从属类型或数组类型,并且不包含占位符类型。因此,RayDim与类型无关。
[temp.dep.constexpr]中定义了“依赖于值”。第2段中仅适用于像RayDim这样的单独标识符的情况:

从上面看,RayDim与类型无关。它当然不是模板参数或成员函数。它是当前实例的静态数据成员和从属成员,但已在成员声明器中初始化。也就是说,“= 3”出现在类定义内,而不是在单独的成员定义内。它是具有文字类型的常量,但是其初始值设定项3与值无关。
因此,RayDim不依赖于值或类型。因此,Matrix<RayDim>不是从属类型,yF.head不是未知实例化的成员,并且template之前的head关键字是可选的,不是必需的。 (这是允许的,因为它不在“仅类型上下文”中,并且head实际上确实命名了成员模板。)

10-04 18:41