Dependent Names
Names in a template whose meaning depends on a template parameter, subject to two-phase lookup and requiring typename or template disambiguation.
Dependent Namesince C++98A dependent name is any name inside a template whose meaning depends on a template parameter, causing the compiler to defer its lookup and binding until the template is instantiated with concrete arguments.
Overview
Templates introduce a split in when names are resolved. The standard mandates two-phase name lookup: non-dependent names are bound the moment the compiler parses the template definition; dependent names are bound only at instantiation when concrete template arguments are known.
A name is dependent if it:
- Is qualified by a type that depends on a template parameter (
T::member) - Appears in a context where argument types depend on template parameters (enabling ADL at phase two)
- Is accessed through a pointer or reference to a dependent type (
ptr->memberwhereptrhas typeT*)
Non-dependent names are blind to declarations made after the template definition, even if instantiation happens after those declarations:
void log(double) { /* ... */ }
template<typename T>
struct Engine {
void run() {
log(1); // non-dependent: bound to log(double) at definition time
}
};
void log(int) { /* ... */ } // declared after template definition
// Engine<int>{}.run() still calls log(double), not log(int)This is well-defined and intentional—templates must not silently change behaviour as new declarations accumulate in surrounding translation units.
Syntax
The typename Disambiguator
When a dependent qualified name refers to a type, the compiler cannot determine—during phase one—whether T::A denotes a type, a static value, or a template. The standard defaults to treating it as a value. The typename keyword instructs the compiler to treat the name as a type:
template<typename Container>
void fill_sorted(Container& c) {
typename Container::iterator begin = c.begin(); // typename required
typename Container::value_type sentinel{}; // typename required
// ...
}Without typename, the compiler parses Container::iterator begin as a multiplication expression (Container::iterator times begin), producing a confusing hard error.
C++20 relaxation: In grammatical positions where only a type is valid, typename is no longer required. These include alias declarations, return type positions, template type arguments, and a handful of other contexts:
// C++20: typename optional in alias and return-type positions
template<typename T>
using ValueType = T::value_type; // no typename needed (C++20)
template<typename T>
T::size_type size_of(const T& c) { // no typename needed in return type (C++20)
return c.size();
}Before C++20, both require typename. Code targeting C++17 or earlier must always prefix dependent type names with typename, even in these positions.
The template Disambiguator
A parallel ambiguity affects dependent template names. When the compiler sees obj.name<X>, it cannot tell whether < opens a template argument list or is the less-than operator. The template keyword, placed immediately before the member name after ., ->, or ::, resolves the ambiguity:
template<typename Alloc>
void allocate_block(Alloc& a) {
// Without template keyword, a.allocate<int>(n) is parsed as
// (a.allocate < int) > (n), which is ill-formed.
auto* p = a.template allocate<int>(16);
}
template<typename T>
struct Registry {
template<typename K>
T* lookup(K key);
};
template<typename T, typename K>
T* find(Registry<T>& reg, K key) {
return reg.template lookup<T>(key); // reg is dependent; template keyword required
}Dependent Base Class Members
Unqualified lookup does not examine dependent base classes during phase one. Inherited members of a base that depends on a template parameter are invisible to plain unqualified name lookup inside a derived class template:
template<typename T>
struct Base {
void init() {}
int value = 0;
};
template<typename T>
struct Derived : Base<T> {
void setup() {
// init(); // ERROR: Base<T> is dependent, not examined in phase 1
// int x = value; // ERROR: same reason
this->init(); // OK: this->init is a dependent expression, resolved in phase 2
Base<T>::init(); // OK: qualified dependent name, also resolved in phase 2
int x = this->value;
}
};this-> is the idiomatic fix. It converts an unqualified non-dependent lookup into a dependent member access, deferring resolution until instantiation when the concrete base class is known.
Examples
Iterating with a Dependent Iterator Type
template<typename Container>
void print_all(const Container& c) {
for (typename Container::const_iterator it = c.begin(); it != c.end(); ++it) {
std::cout << *it << '\n';
}
// C++11 range-for or auto avoids the typename spelling entirely:
for (const auto& elem : c) {
std::cout << elem << '\n';
}
}Calling a Templated Member on a Dependent Type
template<typename Stream>
void write_header(Stream& s) {
s.template put<uint32_t>(0xCAFEBABE);
s.template put<uint16_t>(1);
s.template put<uint16_t>(0);
}CRTP Requiring this-> for Base Members
template<typename Derived>
struct Comparable {
bool operator!=(const Derived& other) const {
// Uses Derived's operator== through static_cast; no dependent lookup issue here.
return !static_cast<const Derived*>(this)->operator==(other);
}
};
template<typename T>
struct Point : Comparable<Point<T>> {
T x, y;
bool operator==(const Point& other) const {
return x == other.x && y == other.y;
}
};Alias Templates Eliminating typename Noise (C++11)
Before C++11, rebinding allocators required typename at every use site. Alias templates introduced in C++11 make the rebound type non-dependent from the consumer's perspective:
// Old pattern: nested typedef requires typename at every use site
template<typename T, typename Alloc>
struct OldRebind {
typedef typename Alloc::template rebind<T>::other type;
};
// Caller must write: typename OldRebind<int, MyAlloc>::type
// C++11 alias template: non-dependent at use site, no typename required
template<typename T, typename Alloc>
using ReboundAlloc = typename Alloc::template rebind<T>::other;
// Caller writes: ReboundAlloc<int, MyAlloc>Common Pitfalls
Missing typename on nested types: Compiler errors like "expected primary-expression before 'it'" or "expression is not a constant" inside a template often indicate a missing typename. The parser is treating the qualified name as a value expression.
Missing template on member templates: Compilers typically emit "expected primary-expression before <" when < is parsed as less-than instead of a template argument opener. Adding template before the member name resolves it.
Assuming base class members are visible: Unlike non-template inheritance, a class template does not implicitly search its dependent base class for unqualified names. Every use of an inherited member in a derived template must go through this-> or a qualified name—this catches many engineers by surprise when migrating non-template code to templates.
Phase-one violations with no diagnostic required: If a non-dependent name's meaning differs between the definition context and the instantiation context, the program is ill-formed with no diagnostic required (IFNDR). Many compilers silently accept this in permissive modes, creating latent portability bugs that surface only on stricter toolchains or under -std=c++17 with -pedantic.
Best Practices
- Prefer alias templates over nested
typedefs when authoring library APIs—consumers avoidtypenamenoise at every use site. - Access all inherited members through
this->in derived class templates unconditionally. It documents intent, satisfies strict compilers, and is trivially verified by a grep. - In C++20 codebases, rely on the relaxed
typenamerules in return-type and alias positions, but keeptypenamein expression contexts where it aids readability. - When a template function calls a templated member on a dependent type, document the requirement explicitly—e.g.,
// requires T::serialize<U>(archive)—so callers understand thetemplatekeyword at the call site. - Prefer
autofor local variables over spelling out dependent type names liketypename Container::iterator; reserve explicittypenamefor function signatures and type aliases whereautois not available.
Deep Dive: typename Disambiguation
The Type-vs-Value Ambiguity
The root cause is that the C++ grammar is ambiguous without knowing whether a qualified name denotes a type. Consider:
template<typename T>
void process() {
T::value_type * ptr; // ambiguous without typename
}The compiler cannot tell in phase one whether this is:
- A pointer declaration:
(T::value_type) * ptr— ifvalue_typeis a type - A multiplication expression:
(T::value_type) * (ptr)— ifvalue_typeis a static variable
The standard's default is to assume non-type (i.e., the multiplication interpretation). To declare a pointer, you must disambiguate:
template<typename T>
void process() {
typename T::value_type * ptr; // unambiguous: pointer declaration
}Concrete Error Without typename
template<typename Container>
size_t count_positive(const Container& c) {
Container::const_iterator it = c.begin(); // ERROR (C++17 and earlier)
// ^~~ compiler sees this as: (Container::const_iterator) * (it = c.begin())
// i.e., a multiplication, which is ill-formed as a statement
return 0;
}Typical compiler error (GCC/Clang): error: need 'typename' before 'Container::const_iterator' because 'Container' is a dependent scope
The fix — prefix with typename:
template<typename Container>
size_t count_positive(const Container& c) {
typename Container::const_iterator it = c.begin(); // OK
size_t n = 0;
for (; it != c.end(); ++it)
if (*it > 0) ++n;
return n;
}Where typename Is Required (Before C++20)
typename is required whenever a dependent qualified name is used as a type in an expression context — i.e., anywhere the compiler cannot grammatically infer that a type is expected:
template<typename T>
struct Traits {
// Data member declaration — typename required
typename T::value_type default_val;
// Local variable — typename required
void foo() {
typename T::size_type n = 0;
}
// In a template argument that is a type — typename required
std::vector<typename T::value_type> storage;
};Two pre-C++20 exceptions where typename was already not required:
- Base class specifier:
template<typename T> struct D : T::Base { }— base class position unambiguously expects a type - Constructor member initializer id:
Foo() : T::Base() { }— also unambiguously a type
Where typename Is Forbidden
You cannot write typename on the name of a dependent base class in the base-class list:
template<typename T>
struct Derived : typename T::Base { }; // ERROR: typename forbidden here
// ^^^^^^^^ ill-formed
template<typename T>
struct Derived : T::Base { }; // OK: base-class position implies typeDeep Dive: template Disambiguation
The Less-Than Ambiguity
When a name is accessed through a dependent expression (via ., ->, or ::) and is followed by <, the compiler faces another ambiguity: is < the start of a template argument list, or the less-than operator?
template<typename T>
void example(T obj) {
obj.convert<int>(0);
// Parsed as: (obj.convert < int) > (0)
// i.e., two comparisons, not a template call
}The template keyword signals that the < is a template argument list opener:
template<typename T>
void example(T obj) {
obj.template convert<int>(0); // OK: template member call
}Syntax Rules for template
The template keyword can appear after ., ->, or :: when the expression to the left is type-dependent or value-dependent:
template<typename T>
void all_forms(T& obj, T* ptr) {
obj.template method<int>(); // dot access
ptr->template method<int>(); // arrow access
T::template static_method<int>(); // scope resolution
}It can also appear in the middle of a qualified name chain before a class template used as a scope:
// typename and template used together for a nested template type
template<typename T>
void nested(T& t) {
typename T::template rebind<int>::other storage;
// ^^^^^^^^ template disambiguates rebind<int>
// ^^^^^^^^ typename disambiguates ::other as a type
}When Is template Required?
The rule: template is required when all of the following hold:
- The name appears after
.,->, or:: - The left-hand side of the accessor is a dependent expression (its type depends on a template parameter)
- The name being called is a function template (takes template arguments)
If the left-hand side is not dependent, the compiler already knows at phase one whether the name is a template and template is not needed:
std::vector<int> v;
v.emplace_back<int>(42); // OK without 'template' — v is not dependent
// (though emplace_back rarely takes explicit args)CRTP and Dependent Base Class Lookup
Why CRTP Forces this->
The Curiously Recurring Template Pattern (CRTP) inherits from a base parameterized on the derived type itself. This creates a dependent base class — and the standard explicitly excludes dependent bases from unqualified name lookup during phase one.
template<typename Derived>
struct Printable {
void print() const {
// Access derived data through CRTP downcast
const Derived& self = static_cast<const Derived&>(*this);
std::cout << self.to_string() << '\n';
}
};
template<typename T>
struct Value : Printable<Value<T>> {
T data;
std::string to_string() const {
return std::to_string(data);
}
void display() {
print(); // ERROR: 'print' is not found — Printable<Value<T>> is dependent
this->print(); // OK: this-> makes the lookup dependent (phase 2)
}
};The compiler rejects print() during phase one because Printable<Value<T>> depends on T — it could be specialized for some T to not have print at all. By writing this->print(), the call becomes a dependent expression and is deferred to phase two, where the concrete instantiation is known and the base class is fully available.
Why the Standard Excludes Dependent Bases
The exclusion exists to prevent silent ambiguity. If unqualified lookup searched dependent bases, then a specialization could silently inject or remove names, changing the meaning of code in the derived template without any error:
template<typename T> struct MaybeHasX { };
template<> struct MaybeHasX<int> { int x = 42; };
int x = 0; // global x
template<typename T>
struct Reader : MaybeHasX<T> {
int read() { return x; } // which x? global always — no silent override
};
// If lookup searched dependent bases:
// Reader<double>{}.read() → returns global 0
// Reader<int>{}.read() → might silently return member 42 — surprising!The standard mandates that x is non-dependent here and is always bound to the global x at definition time. To read the member, you must explicitly qualify it: this->x or MaybeHasX<T>::x.
Three Ways to Access Dependent Base Members
All three techniques make the name dependent, deferring lookup to phase two:
template<typename T>
struct Base {
using size_type = std::size_t;
size_type count = 0;
void reset() { count = 0; }
};
template<typename T>
struct Derived : Base<T> {
// Option 1: this-> (idiomatic for member function calls and data)
void clear() { this->reset(); }
// Option 2: qualified name (explicit, slightly more verbose)
void clear2() { Base<T>::reset(); }
// Option 3: using declaration in class body (makes name non-dependent at use site)
using Base<T>::reset;
using typename Base<T>::size_type; // typename required for type names
void clear3() { reset(); } // now found via 'using'
};Option 3 (a using declaration) is best when a base member is accessed frequently — it avoids repeating this-> or Base<T>:: at every call site. However, it must be placed in the class body and repeated for each name.
C++20: Fewer typename Requirements
P0634 "Down with typename!"
C++20 adopted proposal P0634, which removed the typename requirement in all grammatical positions where only a type is valid. The compiler can statically determine from syntax alone that a type is expected in these contexts, making typename redundant.
Contexts where typename is now optional in C++20:
| Context | C++17 | C++20 |
|---|---|---|
using alias member declarations | typename T::type required | T::type sufficient |
| Non-static data member declarations | typename T::type required | T::type sufficient |
| Function parameter types | typename T::type required | T::type sufficient |
| Trailing return type | typename T::type required | T::type sufficient |
| Default template type argument | typename T::type required | T::type sufficient |
static_cast / cast type-id | typename T::type required | T::type sufficient |
Before and After C++20
// C++17: typename noise throughout
template<typename T>
struct Iterator17 {
using value_type = typename T::value_type;
using difference_type = typename T::difference_type;
using pointer = typename T::pointer;
using reference = typename T::reference;
typename T::pointer ptr_;
explicit Iterator17(typename T::pointer p) : ptr_(p) { }
typename T::reference operator*() const { return *ptr_; }
};// C++20: typename omitted where the grammar implies a type
template<typename T>
struct Iterator20 {
using value_type = T::value_type;
using difference_type = T::difference_type;
using pointer = T::pointer;
using reference = T::reference;
T::pointer ptr_;
explicit Iterator20(T::pointer p) : ptr_(p) { }
T::reference operator*() const { return *ptr_; }
};Where typename Is Still Required in C++20
C++20 did not remove typename from expression contexts, where the type-vs-value ambiguity remains genuine:
template<typename T>
void still_needs_typename(T& obj) {
// Expression context: ambiguous between type and value
typename T::value_type local{}; // still required in C++20
// But in declaration/type contexts it's gone:
T::value_type local2{}; // also OK in C++20 — declaration context
}The practical rule for C++20: typename is still required inside function bodies when the dependent type name appears in an expression (e.g., constructing a temporary, casting). In declaration contexts (data members, using declarations, parameter types, return types), it is optional.
Compiler Support for P0634
P0634 support arrived at different times across compilers:
- GCC: 9.0 (released 2019) — full support
- MSVC: Visual Studio 2019 16.10 — full support
- Clang: 16 (released 2023) — full support
Code written for C++20 and targeting all three major compilers should avoid relying on P0634 unless building with GCC 9+ / MSVC 16.10+ / Clang 16+.
See Also
reference/language/adl— argument-dependent lookup runs at phase two for dependent names but not phase one; understanding both together prevents subtle overload surprisesreference/language/class-template— template parameter declarations, explicit instantiation, and the instantiation model that drives two-phase lookupreference/language/decltype— an alternative to spelling out dependent type names;decltypeexpressions may themselves be dependentreference/language/auto— type deduction sidesteps mosttypenameboilerplate for local variables inside template bodies