Skip to content
C++
Language
since C++98
Advanced

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++98

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

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

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

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

cpp
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:

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

cpp
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:

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

cpp
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 typename before every dependent qualified name that denotes a type — don't rely on compiler extensions or inference.
  • Write .template / ::template whenever a dependent name is immediately followed by < for template arguments.
  • Use this->member (not Base<T>::member) to access inherited members in a template derived class unless suppressing virtual dispatch is intentional.
  • Always pair unqualified customization-point calls with a using declaration (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:

cpp
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 instantiated

typename 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:

cpp
// 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 typename ceremony 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