Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Can typename be omitted in the type-specifier of an out of line member definition?

I ran into this strange behaviour when testing whether or not typename is required by clang. Both clang and gcc accept this code while msvc rejects it.

template<class T1>
struct A
{
    template<class T2>
    struct B
    {
        static B f;
        static typename A<T2>::template B<T1> g;
    };
};

template<class T1>
template<class T2>
typename A<T2>::template B<T1> // ok, typename/template required
    A<T1>::B<T2>::g;

template<class T1>
template<class T2>
A<T1>::B<T2> // clang/gcc accept, msvc rejects missing typename
    A<T1>::B<T2>::f;

In general, a qualified-id A<T1>::B<T2> (where A<T1> is a dependent name) should be written typename A<T1>::template B<T2>. Is the behaviour of gcc/clang incorrect, or is there an exception to the general rule (quoted below) in this particular case?

It could be argued that A<T1> is not a dependent name, or that B<T2> refers to a member of the current instantiation. However, at the point of parsing the type-specifier it's not possible to know that the current instantiation is A<T1>. It seems problematic to require the implementation to guess that A<T1> is the current instantiation.

14.6 Name resolution [temp.res]

A name used in a template declaration or definition and that is dependent on a template-parameter is assumed not to name a type unless the applicable name lookup finds a type name or the name is qualified by the keyword typename.

14.2 Names of template specializations [temp.names]

When the name of a member template specialization appears after . or -> in a postfix-expression or after a nested-name-specifier in a qualified-id, and the object or pointer expression of the postfix-expression or the nested-name-specifier in the qualified-id depends on a template parameter (14.6.2) but does not refer to a member of the current instantiation (14.6.2.1), the member template name must be prefixed by the keyword template. Otherwise the name is assumed to name a non-template.

To further investigate what clang is doing here, I also tried this:

template<class T1>
struct C
{
    template<class T2>
    struct D
    {
        static typename A<T1>::template B<T2> f;
        static typename A<T1>::template B<T2> g;
    };
};

template<class T1>
template<class T2>
typename A<T1>::template B<T2> // ok, typename/template required
    C<T1>::D<T2>::f;

template<class T1>
template<class T2>
A<T1>::B<T2> // clang rejects with incorrect error
    C<T1>::D<T2>::g;

Clang gives error: redefinition of 'g' with a different type, but the type of g actually matches the declaration.

I would instead expect to see a diagnostic suggesting the use of typename or template.

This gives credit to the hypothesis that clang's behaviour in the first example is unintended.

like image 460
willj Avatar asked Aug 20 '13 20:08

willj


People also ask

What is Typename in template?

In template definitions, typename provides a hint to the compiler that an unknown identifier is a type. In template parameter lists, it's used to specify a type parameter.

Is Typename a keyword in C?

" typename " is a keyword in the C++ programming language used when writing templates. It is used for specifying that a dependent name in a template definition or declaration is a type.

How do I restrict a template type in C++?

There are ways to restrict the types you can use inside a template you write by using specific typedefs inside your template. This will ensure that the compilation of the template specialisation for a type that does not include that particular typedef will fail, so you can selectively support/not support certain types.

What can the template parameter in C++ template definition be?

A template parameter is a special kind of parameter that can be used to pass a type as argument: just like regular function parameters can be used to pass values to a function, template parameters allow to pass also types to a function.


2 Answers

clang and gcc are correct.

The compiler knows A<T1>::B<T2> refers to a type and B<T2> is a template and that A<T1>::B<T2>::f is a member of the current instantiation. Therefore, the typename and template keywords are not necessary.

From v14.6.2.1p4:

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 the current instantiation

A<T1>::B<T2> is a qualified-id and A<T1>:: is the nested-name-specifier which refers to the current instantiation. We know that A<T1>:: refers to the current instantiation from 14.6.2.1p1:

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 your code, we have a definition of a member of a primary class template, i.e. A<T1>::B<T2>::f, and A<T1> is the name of the class template followed by the template argument list of the primary template.

In your question, you say However, at the point of parsing the type-specifier it's not possible to know that the current instantiation is A<T1>. However, I cannot follow that because the name A<T1> does refer to the current instantiation as stated above.

like image 125
Jesse Good Avatar answered Oct 02 '22 14:10

Jesse Good


MSVC is correct.

My reading of the C++11 standard suggests that typename is required.

Without the typename keyword, a dependent name is assumed not to name a type.

14.6 Name resolution [temp.res]

2) A name used in a template declaration or definition and that is dependent on a template-parameter is assumed not to name a type unless the applicable name lookup finds a type name or the name is qualified by the keyword typename.

3) When a qualified-id is intended to refer to a type that is not a member of the current instantiation and its nested-name-specifier refers to a dependent type, it shall be prefixed by the keyword typename

7) Within the definition of a class template or within the definition of a member of a class template following the declarator-id, the keyword typename is not required when referring to the name of a previously declared member of the class template that declares a type. [Note: such names can be found using unqualified name lookup, class member lookup into the current instantiation, or class member access expression lookup when the type of the object expression is the current instantiation

14.6.2.1 Dependent types [temp.dep.type]

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

When A<T1> is used in the definition of a member of A, it refers to the current instantiation. When parsing the definition of f a type name qualified by A<T1>:: can be found by class member name lookup into the current instantiation.

However, when the C++ parser encounters A<T1> in the return-type of a member function definition - before the declarator-id - it has not yet encountered the name of the enclosing class. The parser cannot determine whether or not A refers to the enclosing class at this point.

For this reason - regardless of whether or not A<T1> names the current instantiation - the standard does not permit omission of typename within the definition of a member of a class template before the declarator-id.

This example by Vaughn Cato demonstrates that the behaviour of Clang/GCC is inconsistent, and requires typename in a similar scenario:

template <typename T>
struct A {
    typedef int X;
    X f();
};

template <typename T>
A<T>::X A<T>::f() // error: missing 'typename'
{
}
like image 36
willj Avatar answered Oct 02 '22 13:10

willj