C++ Template Instantiation and Unqualified Lookup
In C++, the resolution of unqualified names within a template depends heavily on whether those names are dependent on template parameters and when instantiation occurs. C++ evaluates templates using a mechanism called two-phase name lookup. Under this model, the instantiation context plays a specific, restricted role: it impacts unqualified lookups exclusively for dependent function calls evaluated via Argument-Dependent Lookup (ADL), while standard lexical lookup remains anchored to the point of template definition.
Two-Phase Name Lookup
When a compiler encounters a template, it splits name lookup into two distinct phases:
- Phase 1 (Point of Definition): The compiler parses the template when it is first encountered. Names that do not depend on any template parameter (non-dependent names) are resolved immediately using normal unqualified lookup rules. If a non-dependent name cannot be resolved here, the program is ill-formed, regardless of what names appear later in the code.
- Phase 2 (Point of Instantiation): When the template is instantiated with concrete type arguments, the compiler resolves names that depend on template parameters (dependent names).
How Instantiation Context Affects Unqualified Lookup
For dependent names, the unqualified lookup mechanism is split into two lookup paths: ordinary unqualified lookup and Argument-Dependent Lookup (ADL).
1. Ordinary Unqualified Lookup
Ordinary unqualified lookup for dependent names considers only declarations that were visible at the point of template definition. The instantiation context does not introduce new names into the ordinary lexical scope. If a function is declared after the template definition in the same namespace, ordinary unqualified lookup will fail to find it.
2. Argument-Dependent Lookup (ADL)
ADL inspects the namespaces associated with the actual template arguments passed during instantiation. Unlike ordinary lookup, ADL searches for matching declarations at both:
- The point of template definition.
- The point of instantiation (POI).
Consequently, the instantiation context impacts unqualified lookups solely by expanding the set of declarations visible through ADL. Overloads declared in an argument’s associated namespace after the template definition—but before the point of instantiation—are added to the candidate set.
Code Demonstration
#include <iostream>
// Phase 1: Template Definition
template <typename T>
void execute(T value) {
// 'process' is an unqualified, dependent call
process(value);
}
// User-defined type declared after the template definition
namespace Custom {
struct Data {};
// Defined after 'execute', but in the associated namespace of 'Data'
void process(Data) {
std::cout << "Custom::process(Data) called via ADL\n";
}
}
int main() {
Custom::Data d;
// Phase 2: Point of Instantiation
execute(d);
}In this example:
- When
execute<T>is defined,process(value)cannot be resolved becausevaluedepends onT. - In
main(),execute<Custom::Data>is instantiated. The point of instantiation occurs whereexecute(d)is called. - Ordinary unqualified lookup finds no
processfunction in scope. - ADL looks into namespace
Custom(the associated namespace ofCustom::Data) at the point of instantiation and successfully discoversvoid process(Data).
If process were an un-associated free function declared
outside of namespace Custom after the definition of
execute, ADL would not apply, ordinary lookup would find
nothing, and compilation would fail.
The Point of Instantiation (POI)
The exact location of the POI determines which declarations the instantiation context contributes:
- For a function template specialization, the POI is placed immediately following the namespace-scope declaration or definition that caused the instantiation.
- If multiple POIs exist across a translation unit, declarations visible at any of those POIs are considered, though implementations typically anchor lookup to the earliest valid POI in the translation unit.
Because the instantiation context alters the candidate pool via ADL, declarations introduced prior to template instantiation can alter overload resolution or cause ambiguity if multiple viable overloads become visible.