8

I encountered the following problem by compiling the following example:

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

Link to Compiler Explorer: https://godbolt.org/z/js4XaP

The code compiles with GCC 9.2 and MSVC 19.22 but not with clang 9.0.0. Clang states that a template keyword is required. If static constexpr int RayDim = 3; is moved into int func() const clang accepts it.

As stated as a comment in the code block, is the template keyword required for yF.head<1>()?

Jodebo
  • 125
  • 6
  • I think you may be running into a clang-specific implementation issue where matching template types to parameters isn't fully implemented yet: https://clang.llvm.org/cxx_status.html#p0522 – Eddy Luten Nov 13 '19 at 23:07
  • I think `Matrix` is not a dependent type and so the keyword is not required. I may have time for an answer later. – aschepler Nov 14 '19 at 00:44
  • @EddyLuten P0522 is about template template parameters, and there aren't any in this example. – aschepler Nov 14 '19 at 00:48
  • A simplified example that seems to be related: https://godbolt.org/z/KpXpYs – Evg Nov 14 '19 at 06:50

2 Answers2

1

The template keyword should not be required here, so clang is incorrect to reject the program.

All C++ Standard section and paragraph numbers and quotes below are the same for C++17 draft N4659 and the current linked C++20 draft.

The requirement for template after a . or -> or :: token when naming a member template is in [temp.names]/4. The paragraph first lists cases where the keyword is not allowed, then cases where it is optional and doesn't make a difference, then:

In all other contexts, when naming a template specialization of a member of an unknown specialization ([temp.dep.type]), the member template name shall be prefixed by the keyword template.

A "member of an unknown specialization" is a member of a dependent type which is not "the current instantiation". So the question is whether Matrix<RayDim> is a dependent type. For that, we look at [temp.dep.type]/9:

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 function type whose exception specification 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 is a pack expansion, or
  • denoted by decltype(expression), where expression is type-dependent.

Matrix<RayDim> is clearly not a template parameter, any kind of member, cv-qualified, an array type, a function type, or specified by decltype. It is a compound type, but it uses only a template-name and an expression, so is not constructed from any other type.

That leaves the simple-template-id case. The template name Matrix is not a template parameter. The template argument RayDim is an expression, so now to see if it is type-dependent or value-dependent.

"Type-dependent" is defined in [temp.dep.expr]. Only paragraph 3 can apply to a lone identifier like RayDim:

An id-expression is type-dependent if it contains

  • an identifier associated by name lookup with one or more declarations declared with a dependent type,
  • an identifier associated by name lookup with a non-type template-parameter declared with a type that contains a placeholder type,
  • an identifier associated by name lookup with a variable declared with a type that contains a placeholder type ([dcl.spec.auto]) where the initializer is type-dependent,
  • an identifier associated by name lookup with one or more declarations of member functions of the current instantiation declared with a return type that contains a placeholder type,
  • an identifier associated by name lookup with a structured binding declaration whose brace-or-equal-initializer is type-dependent,
  • the identifier __func__ ([dcl.fct.def.general]), where any enclosing function is a template, a member of a class template, or a generic lambda,
  • a template-id that is dependent,
  • a conversion-function-id that specifies a dependent type, or
  • a nested-name-specifier or a qualified-id that names a member of an unknown specialization;

or if it names a dependent member of the current instantiation that is a static data member of type "array of unknown bound of T" for some T ([temp.static]).

RayDim certainly does not contain any __func__, template-id, conversion-function-id, nested-name-specifier, or qualified-id. Name lookup finds the class template's static member declaration. That declaration of RayDim is certainly not a template-parameter, a member function, or a structured binding declaration, and its type const int is certainly not a dependent type or array type and does not contain a placeholder type. So RayDim is not type-dependent.

"Value-dependent" is defined in [temp.dep.constexpr]. The only cases that can apply to a lone identifier like RayDim are in paragraph 2:

An id-expression is value-dependent if:

  • it is type-dependent,
  • it is the name of a non-type template parameter,
  • it names a static data member that is a dependent member of the current instantiation and is not initialized in a member-declarator,
  • it names a static member function that is a dependent member of the current instantiation, or
  • it is a constant with literal type and is initialized with an expression that is value-dependent.

From above, RayDim is not type-dependent. It is certainly not a template parameter or a member function. It is a static data member and dependent member of the current instantiation, but it is initialized in the member-declarator. That is, the "= 3" appears inside the class definition, not in a separate member definition. It is a constant with literal type, but its initializer 3 is not value-dependent.

So RayDim is not value-dependent or type-dependent. Therefore Matrix<RayDim> is not a dependent type, yF.head is not a member of an unknown instantiation, and the template keyword before head is optional, not required. (It is permitted since it's not in a "type-only context" and head does in fact name a member template.)

Community
  • 1
  • 1
aschepler
  • 70,891
  • 9
  • 107
  • 161
0

Disclaimer

this is not an answer, but rather a long comment

My skills are too low to fully understand the standard but here are a few things I discovered from experimenting with the code. Everything that follows is based on my (far from perfect) understanding of the matter and probably needs some reviews.

Investigation

First off, I went to a fully defined standard version (C++17) so we operate on a well defined implementation.

Looking at this code it seems like MSVC still has some problems (with its lookup, I guess?) when it comes to template instantiation and redefinition. I wouldn't trust MSVC that much in our scenario.

That beeing said, lets think about why we could need the template keyword at

return yF.template head<1>();

Dependent names

Sometimes, inside templates, we have to help the compiler decide whether a name refers to

  1. a value int T::x = 0,
  2. a type struct T::x {};, or
  3. a template template <typename U> T::foo<U>();

If we refer to a value, we do nothing. If we refer to a type, we have to use typename. And if we refer to a template we use template. More on that subject can be found here.

I don't understand the standard specification when it comes to the actual definition of a dependent name but here are some observations.

Observations

Lets take a look at the reference code

template <int N>
struct Matrix 
{
    template <int Idx>
    int head() { return Idx; }
};
 

template <typename T>
struct Test 
{    
    static constexpr int RayDim = 3;

    int func() const 
    {
        Matrix<RayDim> yF;
        return yF.head<1>(); // clang complains, gcc and msvc are ok
    }
};


struct Empty {};

int test() 
{
    Test<Empty> t;
    return t.func();
}

Usually RayDim should be a dependent name (because it's inside the template Test) which would cause Matrix<RayDim> to be a dependent name aswell. For now, lets assume that Matrix<RayDim> actually is a dependent name. This makes Matrix<RayDim>::head a dependent name aswell. Since Matrix<RayDim>::head is a templated function it is a template in itself and the rules of dependent names from above apply, requiring us to use the template keyword. This is what clang is complaining about.

However, since RayDim is defined inside Test and func is also defined inside the same template and not a templated function in itself, I don't think RayDim actually is a dependent name in the context of func. Furthermore, RayDim doesn't rely on the template arguments of Test. In this case, Matrix<RayDim> and Matrix<RayDim>::head respectively, would become non-dependent names, which allows us to omit the template keyword. This is why gcc (and msvc) compile.

If we were to template RayDim aswell, like here

template <typename>
static constexpr int RayDim = 3;

gcc would treat it as a dependent name aswell (which is correct, since there might be a template specialization later so we don't know at that point). Meanwhile, msvc happily accepts everything we throw at it.

Conclusion

It seems like it's boiling down to whether RayDim is a dependent name in the context of Test<T>::func or not. Clang thinks it is, gcc doesn't. From some more test it looks like msvc sides with clang on this one. But it's also kinda doing it's own thing, so who knows?

I would side with gcc here as I see no possible way of RayDim becoming dependent at the point where func is instantiated.

Community
  • 1
  • 1
Timo
  • 9,269
  • 2
  • 28
  • 58