Two-Phase Name Lookup
"C++ two-phase lookup: non-dependent names bind at definition, dependent names at instantiation — typename, template keyword, ADL, and base class pitfalls."
Two-Phase Name Lookupsince C++98Template name lookup occurs in two phases: non-dependent names are resolved at the point of template definition, while dependent names — those whose meaning changes with the template parameters — are resolved at the point of instantiation.
Overview
When the compiler sees a template definition, it cannot fully resolve names that depend on template parameters — it doesn't yet know what types those parameters will be. The standard therefore splits lookup into two phases with fundamentally different rules.
Phase 1 — at template definition: Non-dependent names must be resolvable now. A call to undefined_fn() with no template-dependent arguments is a phase 1 error even if the template is never instantiated.
Phase 2 — at point of instantiation (POI): Once the template is stamped with concrete types, dependent names are resolved. The POI is typically just after the enclosing namespace scope that triggered the instantiation. Unqualified dependent calls trigger ADL here, using the namespaces associated with the actual argument types.
template<typename T>
struct Foo {
void f() {
// Phase 1 — resolved at definition
int x = 0; // non-dependent — fine
// undefined_fn(); // phase 1 hard error even if Foo<T> is never used
// Phase 2 — resolved at instantiation
T::value; // qualified with T — dependent
this->bar(); // 'this' is T-dependent
sizeof(T); // involves template parameter
}
};MSVC conformance note: Historically, MSVC's default mode skipped most of phase 1 and deferred all lookup to instantiation, silently accepting non-portable code. Since Visual Studio 2017, /permissive- enables conforming two-phase lookup. Always compile with /permissive- on MSVC and -pedantic on GCC/Clang to surface phase 1 errors that permissive mode would hide.
typename — Disambiguating Dependent Types
When a name is both dependent and qualified (T::something), the compiler defaults to treating it as a value (a static data member or enumerator). The typename keyword overrides this and declares that the name is a type.
template<typename Container>
void f(Container& c) {
Container::iterator it; // error: compiler assumes 'iterator' is a value
typename Container::iterator it2 = c.begin(); // OK
typename Container::value_type v{};
typename Container::size_type n = c.size();
}The rule applies wherever a dependent qualified name could be either a type or a value: alias declarations, return types, parameter types, and member declarations.
template<typename T>
struct Wrapper {
using value_type = typename T::value_type; // alias declaration
typename T::reference front() { return data_[0]; } // return type
void push(typename T::const_reference val); // parameter type
private:
T data_;
};C++20 relaxation: Several contexts that unambiguously require a type — including elaborated type specifiers inside template argument lists and some positions in requires clauses — no longer require an explicit typename. This is a targeted syntactic relief, not a blanket removal. Both GCC 10+ and Clang 16+ implement it.
template — Disambiguating Dependent Template Members
When a dependent name is followed by <, the parser defaults to reading < as the less-than operator. The template keyword before the name signals that it is a template and < opens its argument list.
template<typename T>
void g(T& obj) {
obj.cast<int>(); // parsed as (obj.cast < int) > () — error
obj.template cast<int>(); // OK
T::template make<double>(); // static template member
}This appears most often in nested type spellings. The canonical example is allocator rebind:
template<typename Alloc>
class Container {
// typename → it's a type; template → rebind<> is a template member
using ByteAlloc = typename Alloc::template rebind<std::byte>::other;
// Chained: nested template type inside a dependent template
using NestedIter =
typename std::allocator_traits<Alloc>::template rebind_alloc<int>;
};The rule: whenever x.name<...> or X::name<...> has a dependent x/X, prepend template before name.
ADL in Templates
Unqualified dependent calls perform argument-dependent lookup at phase 2 using the namespaces associated with the concrete argument types. This is the mechanism behind every customizable algorithm in the standard library.
namespace geo {
struct Point { double x, y; };
void swap(Point& a, Point& b) noexcept; // custom swap
double distance(Point a, Point b);
}
template<typename T>
void sort_pair(T& a, T& b) {
using std::swap; // fallback if no ADL candidate exists
swap(a, b); // phase 2: ADL finds geo::swap when T = geo::Point
}
template<typename T>
double measure(T a, T b) {
return distance(a, b); // phase 2: ADL finds geo::distance
}To suppress ADL deliberately — wrap the name in parentheses or qualify it:
template<typename T>
void explicit_std(T& a, T& b) {
(swap)(a, b); // parens suppress ADL — only std::swap considered
std::swap(a, b); // qualified call also suppresses ADL
}Writing std::swap(a, b) directly is a common mistake: it suppresses the type's custom swap and falls back to the generic move-based one, which may be slower or incorrect for types with invariants.
this-> for Members of Dependent Bases
Names in a dependent base class are invisible to unqualified lookup in phase 1 — the compiler cannot examine Base<T> until T is known and doesn't speculate.
template<typename T>
struct Base {
void helper() { /* ... */ }
int value = 0;
virtual void hook() {}
};
template<typename T>
struct Derived : Base<T> {
void f() {
helper(); // error: Base<T> not searched in phase 1
this->helper(); // OK: makes the call dependent — deferred to phase 2
Base<T>::helper(); // OK: explicit qualification — but suppresses virtual dispatch
this->value; // OK
this->hook(); // OK, virtual dispatch preserved
}
};Prefer this->member over Base<T>::member for inherited members unless you specifically want to bypass virtual dispatch. Both satisfy the dependent lookup requirement, but only this-> preserves polymorphism.
Best Practices
- Write
typenamebefore every dependent qualified name that denotes a type — don't rely on compiler extensions or inference. - Write
.template/::templatewhenever a dependent name is immediately followed by<for template arguments. - Use
this->member(notBase<T>::member) to access inherited members in a template derived class unless suppressingvirtualdispatch is intentional. - Always pair unqualified customization-point calls with a
usingdeclaration (using std::swap;) immediately before the call, not at file scope. - Compile with
/permissive-(MSVC) or-pedantic(GCC/Clang) to catch non-dependent name errors that permissive two-phase implementations would silently defer.
Common Pitfalls
Specialization declared after instantiation. A specialization must be visible before the point at which the template is instantiated with that type, or the primary template is silently used:
template<typename T> void process(T);
template<typename T>
void dispatch(T t) { process(t); }
void use() {
dispatch(42); // instantiates dispatch<int>, which binds process<int>
// from the primary — the specialization below is too late
}
template<> void process<int>(int); // must appear BEFORE dispatch<int> is instantiatedtypename on non-dependent qualified names is either a no-op or an error depending on context. Don't scatter it defensively where names are not dependent.
Missing template in complex nested spellings:
// Wrong — '<' parsed as less-than:
typename Alloc::rebind<int>::other x;
// Right:
typename Alloc::template rebind<int>::other x;Fully qualified calls disable ADL silently. Writing ::process(obj) or ns::process(obj) instead of the unqualified process(obj) means no ADL fires. The call compiles but ignores any type-specific overload in the argument's associated namespace.
See Also
- Templates — template fundamentals and instantiation model
- CRTP — pattern relying on dependent base member access
- Concepts — C++20 constraints that reduce
typenameceremony in some contexts - if constexpr — C++17 conditional compilation that still obeys phase 1 for non-dependent branches
- Structured Bindings — another context where dependent name rules surface