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