Understanding Dependent Names and Typename in C++
In C++ template programming, the compiler encounters identifiers
whose definitions depend on the template arguments supplied at
instantiation. This article explains what dependent names are, the
syntactic ambiguities they introduce during the initial compilation
phase, and why the typename disambiguator is required by
the C++ standard to instruct the compiler to treat a dependent qualified
name as a type rather than an object or expression.
What is a Dependent Name?
A dependent name is any identifier inside a template whose meaning or existence depends directly or indirectly on a template parameter. These names cannot be fully resolved when the template is first parsed because the exact template arguments are unknown until instantiation.
Names in templates fall into two categories:
- Non-dependent names: Names that do not rely on
template parameters (such as
int,std::string, or local variables with fixed types). The compiler resolves these during phase one of template compilation. - Dependent names: Names that rely on template
parameters, such as
T::value,T::iterator, orstd::vector<T>::size_type.
Because C++ uses a two-phase name lookup model, the compiler checks
the template for general syntax errors during phase one (definition
time), but defers resolving dependent names until phase two
(instantiation time), when concrete types are substituted for
T.
The Parsing Ambiguity Problem
C++ is not a context-free grammar; the compiler must know whether an identifier represents a type or a non-type (such as a variable, constant, or function) to construct an Abstract Syntax Tree (AST) correctly during phase one.
Consider the following line inside a template:
template <typename T>
void process() {
T::SubType * ptr;
}Without knowing what T is, the expression
T::SubType * ptr; has two valid syntactic
interpretations:
- A pointer declaration: If
T::SubTypeis a type, this declares a variable namedptrthat points to aT::SubType. - A multiplication expression: If
T::SubTypeis a static member variable or enum constant, this multipliesT::SubTypeby a variable namedptrdeclared in an outer scope.
Because the compiler must parse and validate syntax before
instantiating the template, it cannot wait to see what T
is. To resolve this ambiguity, the C++ standard specifies a default
rule: any dependent qualified name is assumed to be a non-type
(a value or member function) unless explicitly stated
otherwise.
Why the
typename Disambiguator is Required
Because the compiler assumes dependent names refer to values,
attempting to declare a pointer or create an object using
T::SubType results in a compilation error. The
typename keyword acts as a mandatory disambiguator,
instructing the compiler to parse the identifier as a type.
The corrected code requires typename prefixing the
dependent qualified name:
template <typename T>
void process() {
typename T::SubType* ptr; // Informs the compiler that SubType is a type
}A common example occurs when accessing nested types within standard library containers:
template <typename Container>
void printFirst(const Container& c) {
// Requires typename because Container::const_iterator depends on Container
typename Container::const_iterator it = c.begin();
if (it != c.end()) {
// ...
}
}Without typename, the compiler treats
Container::const_iterator as a static data member, causing
a syntax failure when parsing the variable declaration.
Modern C++ and C++20 Contextual Improvements
Prior to C++20, developers were required to place
typename before dependent types almost everywhere, even in
contexts where the identifier could only reasonably be parsed as a
type.
C++20 introduced P0634R3, which relaxes the requirement in contexts
where non-type names are syntactically impossible. In C++20 and later,
you do not need typename in:
usingtype aliases:using Type = T::SubType;newexpressions:new T::SubType;- Base class specifiers:
template <typename T> struct Derived : T::Base {}; - Trailing return types:
auto func() -> T::SubType;
However, inside function bodies and expressions where an operation
could be interpreted as arithmetic or variable manipulation, the
typename prefix remains strictly required.