Why doesn't a derived template class have access to a base template class' identifiers?
Solution 1
That's two-phase lookup for you.
Base<T>::NO_ZEROFILL
(all caps identifiers are boo, except for macros, BTW) is an identifier that depends on T
.
Since, when the compiler first parses the template, there's no actual type substituted for T
yet, the compiler doesn't "know" what Base<T>
is. So it cannot know any identifiers you assume to be defined in it (there might be a specialization for some T
s that the compiler only sees later) and you cannot omit the base class qualification from identifiers defined in the base class.
That's why you have to write Base<T>::NO_ZEROFILL
(or this->NO_ZEROFILL
). That tells the compiler that NO_ZEROFILL
is something in the base class, which depends on T
, and that it can only verify it later, when the template is instantiated. It will therefore accept it without trying to verify the code.
That code can only be verified later, when the template is instantiated by supplying an actual parameter for T
.
Solution 2
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 &)
.
Solution 3
Seems to compile ok in vs 2008. Have you tried:
public:
Derived( bool initZero = Base<T>::NO_ZEROFILL );
Related videos on Youtube
cheshirekow
Updated on August 30, 2020Comments
-
cheshirekow almost 4 years
Consider:
template <typename T> class Base { public: static const bool ZEROFILL = true; static const bool NO_ZEROFILL = false; } template <typename T> class Derived : public Base<T> { public: Derived( bool initZero = NO_ZEROFILL ); // NO_ZEROFILL is not visible ~Derived(); }
I am not able compile this with GCC g++ 3.4.4 (cygwin).
Prior to converting these to class templates, they were non-generic and the derived class was able to see the base class's static members. Is this loss of visibility in a requirement of the C++ spec or is there a syntax change that I need to employ?
I understand that each instantiation of
Base<T>
will have it's own static member "ZEROFILL
" and "NO_ZEROFILL
", thatBase<float>::ZEROFILL
andBase<double>::ZEROFILL
are different variables, but i don't really care; the constant is there for readability of the code. I wanted to use a static constant because that is more safe in terms of name conflicts rather than a macro or global.-
George Robinson about 6 yearsBecause of the two-phase name lookup (which not all compilers use by default). There are 4 solutions to this problem: 1) Use the prefix
Base<T>::NO_ZEROFILL
, 2) Use the prefixthis->NO_ZEROFILL
, 3) Add a statementusing Base<T>::NO_ZEROFILL
, 4) Use a global compiler switch that enables the permissive mode. The pros & cons of these solutions are described in stackoverflow.com/questions/50321788/…
-
-
josesuero almost 15 yearsaw, you beat me to it by 20 seconds. +1
-
sbi almost 15 yearsActually, VC doesn't do two-phase lookup. That's why it compiles there. And that's why it is a bad idea to create a template lib using VC -- you'll have a lot of stuff to fix when you need it on any other compiler.
-
sbi almost 15 years@jalf: So for once, I'm the first.
:^>
Feel free to improve on it. -
cheshirekow almost 15 yearsInteresting. Do inherited non-static members then require the same qualification? i.e. Base<T>::memberFunction()
-
sbi almost 15 years@ceshirekow: Yes, basically everything behind a
::
, where there's something depending on a template parameter before the::
, is "dependent", and this is one of the issues arising from this. (See for an explanation of why thetypename
is necessary sometimes, if you want to learn more about this. It has the same reason and you'll find good explanations everywhere.) -
Richard Corden almost 15 years@sbi: Are you sure this is called 2-phase lookup? I always thought that term was just for argument dependent lookup. Do you have a reference in the standard for that?
-
josesuero almost 15 yearsthe fixes are generally fairly trivial though. It's mostly a matter of inserting a lot of
typename
's, and fixing the occasional 2-phase lookup. -
sbi almost 15 years@Richard: Um, I think I explained that
NO_ZEROFILL
is indeed argument-dependent. Depending onT
, any specialization ofBase<T>
could defineNO_ZEROFILL
to be anything -- or not define it at all. -
MSalters almost 15 yearsNo, "Argument dependent lookup" is the official name for Koenig lookup. When resolving foo(a,b), the name foo is looked up in the namespaces of typeof(a) and typeof(b). Nothing to do with templates.
-
sbi almost 15 years@MSalters: Sorry for the confusion. What I meant is that
NO_ZEROFILL
depends on a template argument. In that, it is argument-dependent. However, argument-dependent lookup is of course an already occupied term, so I shouldn't have used it. -
sbi almost 15 years@jalf: That's true except where it isn't. Among others, I've run into very nasty inter-dependency problems that weren't discovered with VC because VC would only really parse the template when it was instantiated -- and by then all dependent entities were in scope. During the first parse in a proper two-phase lookup, this fell to pieces and it took quite a while and a liberal sprinkling of the programmer's universal cure (indirection) to untangle the mess. The code was a lot harder to understand after that, and it would probably have been designed differently had the problem been known earlier.
-
Johannes Schaub - litb over 13 years+1, nice explanation :) Too bad you seem to have stopped answering on stackoverflow. I loved your elaborative and intensive discussions.
-
Johannes Schaub - litb over 13 years@MSalters what @Richard was referring to was two phase lookup when unqualified dependent function names are looked up at instantiation. It's called "two phase lookup" because a part of the function name lookup is done at the definition context and another part (the part about ADL) is done when instantiating. But the term "two phase lookup" is not a standards-defined term, so people use that term differently.
-
Richard Corden over 13 yearsIt's nice to be missed! I hope to get back to answering questions in the near future.
-
stefan about 9 yearsPlease properly format your code. Explanation on why code works and where the problem in the original code is, is usually a good thing too.
-
kevin about 8 yearsThere is a similar question, and I think the answer there is good. stackoverflow.com/questions/1120833/…
-
sbi about 8 years@kevin That answer mostly tells how to work around the problem, only linking to some explanation. I consider that inferior, as external links might "rot". My answer attempts to explain the underlying problem. I have added the
this->
syntax to my answer (even though I'm not sure it's applicable for a member function default argument). Would that help? -
kevin about 8 years@sbi Absolutely. Thanks.
-
Carousel almost 8 years@RichardCorden I'm not fully understanding your side note to illustrate the "2-phase lookup". As my understanding, the function name
bar
in the function body offoo
is a dependent name, because its parameter is type-dependent. Shouldn't the name lookup ofbar
infoo
also be postponed until the actual template argument is known? Actually, clang++ 3.8.1 doesn't yield any error about the name lookup even if I comment out the linevoid bar (int);
. So I doubt the "phase 1 lookup" forbar
is actually performed. -
Richard Corden almost 8 years@Carousel As the name is dependent an error is not expected during phase 1 lookup. Try the following: Add a new template overload above
foo(T const &)
,template <typename T> void bar (T);
. Change the overload innamespace NS
have the same signature:template <typename T> void bar (T);
. Phase 1 lookup finds 2 overloads forbar
, and phase 2 findsNS::bar
. The compiler is unable to select a best match between thebar(T)
functions. Now move the globalbar(T)
belowfoo
. The code will compile successfully because phase 1 only findsbar(int)
.