The question could be two fold: why do we want two phase lookup in the first place, and given that we have two phase lookup, why are the interpretation of the tokens fixed during the first phase. The first is the harder question to answer, as it is a design decision in the language and as such it has its advantages and disadvantages and depending on where you stand the ones or the others will have more weight.
The second part, which is what you are interested in, is actually much simpler. Why, in a C++ language with two phase lookup are the token meaning fixed during the first phase and cannot be left to be interpreted in the second phase. The reason is that C++ has a contextual grammar, and the interpretation of the tokens is highly dependent on the context. Without fixating the meaning of the tokens during the first phase you won't even know what names need to be looked up in the first place.
Consider a slightly modified version of your original code, where the literal 5 is substituted by a constant expression, and assuming that you did not need to provide the template
or typename
keywords that bit you the last time:
const int b = 5;
template<typename T>
struct Derived : public Base<T> {
void Foo() {
Base<T>::Bar<false>(b); // [1]
std::cout << b; // [2]
}
};
What are the possible meanings of [1] (ignoring the fact that in C++ this is determined by adding typename
and template
)?
Bar
is a static template function that takes a single bool
as template argument and an integer as argument. b
is a non-dependent name that refers to the constant 5. *
Bar
is a nested template type that takes a single bool
as template argument. b
is an instance of that type defined inside the function Derived<T>::Foo
and not used.
Bar
is a static member variable of a type X
for which there is a comparison operator<
that takes a bool
and yields as result an object of type U
that can be compared with operator>
with an integer.
Now the question is how do we proceed resolving the names before the template arguments are substituted in (i.e. during the first phase). If we are in case 1. or 3. then b
needs to be looked up and the result can be substituted in the expression. In the first case yielding your original code: Base<T>::template Bar<false>(5)
, in the latter case yielding operator>( operator<( Base<T>::Bar,false ), 5 )
. In the third case (2.) the code after the first phase would be exactly the same as the original code: Base<T>::Bar<false> b;
(removing the extra ()
).
The meaning of the second line [2] is then dependent on how we interpreted the first one [1]. In the 2. case it represents a call to operator<<( std::cout, Base<T>::Bar<false> & )
, while in the other two cases it represents operator<<( std::cout, 5 )
. Again the implications extend beyond what type is the second argument, as in the 2. case name b
within Derived<T>::Foo
is dependent, and thus it cannot be resolved during the first phase but rather postponed to the second phase (where it will also affect lookup by adding the namespaces of Base
and the instantiating type T
to the Argument Dependent Lookup).
As the example shows, the interpretation of the tokens impact the meaning of the names, and that in turn affects what the rest of the code means, what names are dependent or not and thus what else needs to be looked up or not during the first phase. At the same time, the compiler does perform checks during the first pass, and if the tokens could be reinterpreted during the second pass, then the checks and the results of the lookup during the first pass would be rendered useless (imagine that during the first pass b
had been substituted with 5
only to find out that we are in case 2. during the second phase!), and everything would have to be checked during the second phase.
The existence of two phase lookup depends on the tokens being interpreted and it's meaning selected during the first phase. The alternative is a single pass lookup as VS does.
* I am simplifying the cases here, in the Visual Studio compiler, that does not implement two-phase lookup, b
could also be a member of Base<T>
for the currently instantiating type T
(i.e. it can be a dependent name)