The problem you have encountered is due to name lookup rules for dependent base classes. 14.6/8 has:
When looking for the declaration of a name used in a template definition, the usual lookup rules (3.4.1,
3.4.2) are used for nondependent names. The lookup of names dependent on the template parameters is
postponed until the actual template argument is known (14.6.2).
(This is not really "2-phase lookup" - see below for an explanation of that.)
The point about 14.6/8 is that as far as the compiler is concerned NO_ZEROFILL
in your example is an identifier and is not dependent on the template parameter. It is therefore looked up as per the normal rules in 3.4.1 and 3.4.2.
This normal lookup doesn't search inside Base<T>
and so NO_ZEROFILL is simply an undeclared identifier. 14.6.2/3 has:
In the definition of a class template or a member of a class template, if a base class of the class template
depends on a template-parameter, the base class scope is not examined during unqualified name lookup
either at the point of definition of the class template or member or during an instantiation of the class template
or member.
When you qualify NO_ZEROFILL
with Base<T>::
in essence you are changing it from being a non dependent name to a dependent one and when you do that you delay its lookup until the template is instantiated.
Side note: What is 2-phase lookup:
void bar (int);
template <typename T>
void foo (T const & t) {
bar (t);
}
namespace NS
{
struct A {};
void bar (A const &);
}
int main ()
{
NS::A a;
foo (a);
}
The above example is compiled as follows. The compiler parses the function body of foo
and see that there is a call to bar
which has a dependent argument (ie. one that is dependent on the template parameter). At this point the compiler looks up bar as per 3.4.1 and this is the "phase 1 lookup". The lookup will find the function void bar (int)
and that is stored with the dependent call until later.
When the template is then instantiated (as a result of the call from main
), the compiler then performs an additional lookup in the scope of the argument, this is the "phase 2 lookup". This case that results in finding void NS::bar(A const &)
.
The compiler has two overloads for bar
and it selects between them, in the above case calling void NS::bar(A const &)
.