#081 – The typename keyword in nested template types

The typename keyword has two jobs in C++ that look unrelated until you recognise they’re the same job at different depths. The familiar use is in template parameter lists — template <typename T> — where it announces a type parameter. The trickier use is inside template definitions, where a qualified name like C::iterator could syntactically be either a nested type or a static member, and only the programmer knows which.

Pre-C++20, the compiler refused to guess: if you meant a type, you had to write typename C::iterator explicitly. C++20 finally made the keyword optional in most contexts where a type is unambiguously expected, but the rule that tells you when you still need it is worth understanding — and it’s the same rule the standard reference described 25 years ago, just relaxed a little.

Three things to take away:

  • dependent qualified name — like C::iterator where C is a template parameter — is ambiguous between “nested type” and “static value.” typename resolves the ambiguity in favour of “type.”
  • Pre-C++20, typename was required at every such use inside a template; C++20’s Down with typename! (P0634) made it optional in contexts where the syntactic position already demands a type.
  • The keyword is also used to introduce template type parameters (template <typename T>), where it’s the near-synonym of class — same idea, declaring that a name refers to a type.

Why the compiler can’t always tell

The standard reference makes the rule precise: “Qualified dependent names are resolved according to a simple rule: if the name is prefaced with typename, it is a type; otherwise, it is not a type.” Read literally, that sounds arbitrary. The context that explains it is C++’s two-phase name lookup: the compiler parses a template definition without knowing what its parameters will be, then re-checks names at instantiation time once the parameters are known.

In phase one, the compiler sees code like this:

template <typename C>
void process(C& c){
    C::iterator * x;     // ???
}

What is C::iterator * x? It depends entirely on what C turns out to be:

  • If C::iterator is a type, the line is a variable declaration: x is a pointer to that type.
  • If C::iterator is a static value (say, a static int member), the line is an expression statement multiplying the value by x.

The two readings are syntactically indistinguishable. The compiler has to commit to one during parsing, before it knows the type of C. The committee’s design choice was that in the absence of explicit guidance, the compiler treats the name as not a type — meaning the line above parses as a multiplication, not a declaration. To get the variable- declaration reading, you write typename to override the default:

template <typename C>
void process(C& c){
    typename C::iterator x;   // unambiguous: x is of type C::iterator
}

The rule generalises: anywhere inside a template definition where a qualified dependent name appears in a context that could be a type or a value, the compiler defaults to “value” unless typename says otherwise.

The canonical example

A function that erases elements matching a predicate is the textbook case:

template <typename C, typename Pred>
void erase_if(C& c, Pred pred){
    for (typename C::iterator it = c.begin(); it != c.end(); ) {
        if (pred(*it))  it = c.erase(it);
        else            ++it;
    }
}

Without typename, the loop’s initialiser is unparseable — C::iterator defaults to a value and C::iterator it becomes nonsense. The keyword resolves it. The rest of the loop runs unchanged.

The same pattern shows up in container-library glue code, where nested typedefs proliferate:

template <typename Container>
class Wrapper {
    using value_type      = typename Container::value_type;
    using iterator        = typename Container::iterator;
    using const_iterator  = typename Container::const_iterator;
    using size_type       = typename Container::size_type;
    // ...
};

Each typename says “the qualified name to my right is a type.” The verbosity is annoying. C++20 finally addressed it.

C++20’s relaxation: P0634, “Down with typename!”

The core observation behind P0634 is that in many contexts, the compiler already knows a type is required by the syntactic position. There is no other thing a name could be there. The proposal made typename optional in those contexts:

  • Type alias declarations (using and typedef)
  • Function return types
  • Function parameter types
  • Variable declarations where the type position is unambiguous
  • Template type arguments
  • Base class specifiers
  • The destination of static_castconst_cast, and friends

Under C++20, the wrapper above can drop the keyword entirely:

template <typename Container>
class Wrapper {
    using value_type      = Container::value_type;     // OK in C++20
    using iterator        = Container::iterator;       // OK in C++20
    using const_iterator  = Container::const_iterator; // OK in C++20
    using size_type       = Container::size_type;      // OK in C++20
};

The using keyword already tells the compiler that what follows the = is a type — the redundancy of writing typename was exactly that, redundancy.

Where the keyword is still required is where the position remains genuinely ambiguous — most importantly, inside function bodies when the name appears in a value-or-type position:

template <typename C>
void process(C& c){
    // Still required in C++20:
    typename C::iterator x = c.begin();
}

Here the line could be a multiplication (C::iterator * x with * as a multiply, then comparison) or a declaration. The compiler can’t tell from syntax alone, so the default-to-value rule still applies, and typename is still mandatory.

The other typename: template parameter declarations

The keyword also appears in template parameter lists:

template <typename T>
void f(T value);

Here typename T declares T as a type parameter of the template — a placeholder for whatever type the caller supplies at instantiation. In this position, typename and class are near-synonyms; the choice is purely stylistic.

template <class T>      void f(T);     // equivalent to typename T
template <typename T>   void g(T);

A historical note: the typename spelling was added later than the class spelling, partly because using class here was misleading — T doesn’t have to be a class type, and ranking which spelling is “more correct” became a minor holy war. Modern style guides generally prefer typename because it doesn’t suggest “T must be a class,” but both spellings remain legal and ubiquitous in existing code.

Practical guidance

Three rules cover essentially every real-world case:

  1. Use typename for dependent qualified names that name a type. Inside templates, when you write C::something and something is a type, write typename in front of it unless your project’s minimum C++ standard is 20 or later and you’ve verified the context is one where it’s optional.
  2. For C++20 codebases, you can drop typename in declaration positions. using aliases, function signatures, and template arguments will accept the bare qualified name. Inside function bodies, keep typename anyway — the rule there is unchanged, and the inconsistency isn’t worth the saved keystrokes.
  3. For template parameter lists, pick typename or class and be consistent. They mean the same thing. Most modern C++ style guides prefer typename for clarity.

The defensive habit — write typename everywhere it’s ever needed — is the right one for code that has to compile on older standards or unfamiliar compilers. It costs a few characters per declaration and saves you from needing to remember which exact contexts P0634 covers.

Takeaway

typename exists because the compiler parses templates without knowing their parameters, and a qualified name like C::iterator is ambiguous between a type and a value. The keyword resolves the ambiguity in favour of “type.” Inside template definitions, write it before any dependent qualified name you mean as a type; in template parameter lists, use it (or class) to declare a type parameter.

C++20’s Down with typename! proposal made the keyword optional in the contexts where the compiler already knows a type is required — declarations, return types, base classes, template arguments — but it remains mandatory inside function bodies and other places where the syntax genuinely could go either way. The rule is simple: when a qualified name in a template names a type and the compiler can’t already prove it from context, say so with typename.