Holiday fun with template<class> and template<typename>

Let’s start simple.

template<class T> struct S1;
template<typename T> struct S1;

These declarations are synonymous.

using X1 = S1<int>;

How about this one? (Godbolt.)

template<class T, class T::U> struct S1;
template<typename T, typename T::U> struct S2;

In this case, S1 and S2 both take one type parameter (T) and one non-type template parameter (unnamed). However, S1 will give a hard error if T’s nested type U is not a class type, whereas S2 will permit T’s nested type U to be anything — enum, reference, whatever.

struct Y1 { class U {}; };
using X1 = S1<Y1, Y1::U{}>;  // new in C++2a

struct Y2 { using U = int*; };
using X2 = S1<Y2, nullptr>;

How about this one?

template<class T::U> struct S1;
template<typename T::U> struct S2;

In this case, S1 is valid if and only if it’s preceded by a declaration of T::U. For example, T might be a namespace or a class type, and U must be a class type nested within T:

struct T { class U {}; };
template<class T::U> struct S1;
using X1 = S1<T::U{}>;  // new in C++2a

The keyword class in front of T::U is the class-key introducing an elaborated-type-specifier.

S2 is never valid, as far as I know, but GCC doesn’t complain about it. (GCC essentially treats that typename keyword as a synonym for class.)

However, if T is a template parameter, everything changes!

template<typename T, typename T::U> struct S3;

struct Y { using U = int; };
using X3 = S3<Y, 42>;

(Hat tip to Jon Kalb for this snippet.) Here, S3 is a perfectly valid template (all the way back to C++03); it takes one type parameter formally named T and one unnamed non-type parameter of type T::U. The first instance of the typename keyword is a type-parameter-key, but the second instance of the typename keyword is part of a typename-specifier instead.


Let’s throw CTAD into the mix! (Godbolt.)

template<class Policy>
struct S {
    template<typename Policy::FixedString> // INCORRECT!
    struct N {};
};

struct Y {
    template<class T> struct FixedString {
        constexpr FixedString(T) {}
    };
};
using X1 = S<Y>::N<42>;

In keeping with CTAD’s general “ignore all the rules” approach, it seems there is no way to express that S<Y>::N wants to take a non-type template parameter of type Y::FixedString<...auto...> (to borrow GCC’s way of writing CTAD placeholders).

We might try to work around this deficiency in CTAD with a layer of indirection — a local alias template (Godbolt) —

template<class Policy>
struct S {
    template<class T> using PFS = Policy::FixedString<T>;
    template<PFS> // Workaround?
    struct N {};
};

— but GCC doesn’t implement P1814 “Wording for Class Template Argument Deduction for Alias Templates” yet, so it’s full of ICEs in this area.

GCC is the only vendor that implements CTAD-in-NTTPs so far. Recall that CTAD-in-NTTPs is the “killer app” for NTTPs of class type — formerly P0732, now P1907, a feature that I consider unbaked and would rather not have in C++2a at all. For that matter, I wish CTAD had never gone into C++17, and I advise against using CTAD in any production codebase (Contra CTAD,” 2018-12-07).


Observe that class T introduces a template type parameter whose formal name is T, but class T* introduces a non-type template parameter with no formal name. Thus:

class T {} t;

template<class T> struct S1;
template<class T*> struct S2;

using X1 = S1<int>;
using X2 = S2<&t>;

Notice that class T* also has the interesting side effect of adding a declaration of class T to the current (very tiny) scope. Declaring new meanings for names that were already used as template parameter names in an outer scope (that is, “shadowing” a template parameter with some other declaration) is ill-formed, and implementations give a wide range of fun error messages if you try. Clang’s is perhaps the clearest, but even so, I almost filed a bug about it before deciding that it was correct to complain. Godbolt:

class T {} t;
class U {} u;

template<class T>
struct S {
    template<class T*, T*> struct N {};
};

using X = S<U>::N<&u, &u>;

This code is 100% ill-formed, because class T* causes a shadowing declaration of T in a scope where T already refers to a template parameter. GCC and Clang give error messages; MSVC is utterly confused by the class-key; and EDG emits a warning before proceeding to treat class T* as if the class keyword weren’t there. (So EDG successfully compiles the above code, even though ideally it shouldn’t.)

Posted 2019-12-27